Merge pull request #534 from plebbit/development

Development
This commit is contained in:
Tom (plebeius.eth)
2024-09-10 19:20:06 +02:00
committed by GitHub
57 changed files with 489 additions and 143 deletions
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "نمط",
"enable": "تفعيل",
"order": "ترتيب",
"text": "نص"
"text": "نص",
"filtered_threads": "المواضيع المفلترة"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "প্যাটার্ন",
"enable": "সক্রিয় করুন",
"order": "অর্ডার",
"text": "টেক্সট"
"text": "টেক্সট",
"filtered_threads": "ফিল্টার করা থ্রেড"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Vzor",
"enable": "Povolit",
"order": "Pořadí",
"text": "Text"
"text": "Text",
"filtered_threads": "Filtr threads"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Mønster",
"enable": "Aktiver",
"order": "Rækkefølge",
"text": "Tekst"
"text": "Tekst",
"filtered_threads": "Filtrerede tråde"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Muster",
"enable": "Aktivieren",
"order": "Reihenfolge",
"text": "Text"
"text": "Text",
"filtered_threads": "Gefilterte Threads"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Μοτίβο",
"enable": "Ενεργοποιήστε",
"order": "Σειρά",
"text": "Κείμενο"
"text": "Κείμενο",
"filtered_threads": "Φιλτραρισμένα θέματα"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Pattern",
"enable": "Enable",
"order": "Order",
"text": "Text"
"text": "Text",
"filtered_threads": "Filtered threads"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Patrón",
"enable": "Habilitar",
"order": "Orden",
"text": "Texto"
"text": "Texto",
"filtered_threads": "Hilos filtrados"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "الگو",
"enable": "فعال کردن",
"order": "ترتیب",
"text": "متن"
"text": "متن",
"filtered_threads": "رشته‌های فیلتر شده"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Malli",
"enable": "Ota käyttöön",
"order": "Järjestys",
"text": "Teksti"
"text": "Teksti",
"filtered_threads": "Suodatetut ketjut"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Pattern",
"enable": "I-enable",
"order": "Kaayusan",
"text": "Teksto"
"text": "Teksto",
"filtered_threads": "Na-filter na mga thread"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Modèle",
"enable": "Activer",
"order": "Ordre",
"text": "Texte"
"text": "Texte",
"filtered_threads": "Threads filtrés"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "תבנית",
"enable": "הפעל",
"order": "סדר",
"text": "טקסט"
"text": "טקסט",
"filtered_threads": "אשכולות ממוינים"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "पैटर्न",
"enable": "सक्षम करें",
"order": "क्रम",
"text": "पाठ"
"text": "पाठ",
"filtered_threads": "फ़िल्टर किए गए थ्रेड्स"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Minta",
"enable": "Engedélyez",
"order": "Rendelés",
"text": "Szöveg"
"text": "Szöveg",
"filtered_threads": "Szűrt szálak"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Pola",
"enable": "Aktifkan",
"order": "Urutan",
"text": "Teks"
"text": "Teks",
"filtered_threads": "Thread yang difilter"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Modello",
"enable": "Abilita",
"order": "Ordine",
"text": "Testo"
"text": "Testo",
"filtered_threads": "Thread filtrati"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "パターン",
"enable": "有効にする",
"order": "順序",
"text": "テキスト"
"text": "テキスト",
"filtered_threads": "フィルタリングされたスレッド"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "패턴",
"enable": "사용",
"order": "순서",
"text": "텍스트"
"text": "텍스트",
"filtered_threads": "필터링된 스레드"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "पॅटर्न",
"enable": "सक्षम करा",
"order": "आदेश",
"text": "पाठ"
"text": "पाठ",
"filtered_threads": "फिल्टर केलेले ध糹रे"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Patroon",
"enable": "Inschakelen",
"order": "Volgorde",
"text": "Tekst"
"text": "Tekst",
"filtered_threads": "Gefilterde threads"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Mønster",
"enable": "Aktiver",
"order": "Rekkefølge",
"text": "Tekst"
"text": "Tekst",
"filtered_threads": "Filtrerte tråder"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Wzór",
"enable": "Włącz",
"order": "Kolejność",
"text": "Tekst"
"text": "Tekst",
"filtered_threads": "Filtrowane wątki"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Padrão",
"enable": "Ativar",
"order": "Ordem",
"text": "Texto"
"text": "Texto",
"filtered_threads": "Threads filtrados"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Model",
"enable": "Activare",
"order": "Ordine",
"text": "Text"
"text": "Text",
"filtered_threads": "Fire filtrate"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Шаблон",
"enable": "Включить",
"order": "Порядок",
"text": "Текст"
"text": "Текст",
"filtered_threads": "Отфильтрованные темы"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Model",
"enable": "Aktivizo",
"order": "Rendit",
"text": "Tekst"
"text": "Tekst",
"filtered_threads": "Tema të filtruar"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Mönster",
"enable": "Aktivera",
"order": "Ordning",
"text": "Text"
"text": "Text",
"filtered_threads": "Filtrerade trådar"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "ప్యాటర్న్",
"enable": "చేర్చు",
"order": "ఆర్డర్",
"text": "పాఠం"
"text": "పాఠం",
"filtered_threads": "ఫిల్టర్ చేయబడిన థ్రెడ్లు"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "รูปแบบ",
"enable": "เปิดใช้งาน",
"order": "ลำดับ",
"text": "ข้อความ"
"text": "ข้อความ",
"filtered_threads": "กระทู้ที่กรองแล้ว"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Desen",
"enable": "Etkinleştir",
"order": "Sıra",
"text": "Metin"
"text": "Metin",
"filtered_threads": "Filtrelenmiş başlıklar"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Шаблон",
"enable": "Увімкнути",
"order": "Порядок",
"text": "Текст"
"text": "Текст",
"filtered_threads": "Відфільтровані треди"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "نمونہ",
"enable": "فعال کریں",
"order": "ترتیب",
"text": "متن"
"text": "متن",
"filtered_threads": "فلٹر کیے گئے تھریڈز"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "Mẫu",
"enable": "Kích hoạt",
"order": "Thứ tự",
"text": "Văn bản"
"text": "Văn bản",
"filtered_threads": "Các chủ đề đã lọc"
}
+2 -1
View File
@@ -231,5 +231,6 @@
"pattern": "模式",
"enable": "启用",
"order": "顺序",
"text": "文本"
"text": "文本",
"filtered_threads": "已过滤的帖子"
}
+1 -1
View File
@@ -46,8 +46,8 @@ const BoardLayout = () => {
{isMobile
? (subplebbitAddress || isInAllView || isInSubscriptionsView || pendingPost?.subplebbitAddress) && (
<>
<PostForm key={key} />
<MobileBoardButtons />
<PostForm key={key} />
</>
)
: (subplebbitAddress || isInAllView || isInSubscriptionsView || pendingPost?.subplebbitAddress) && (
@@ -11,6 +11,32 @@
margin: 5px 2px;
}
.mobileBoardButtons .secondRow {
margin-top: -5px;
padding-bottom: 20px;
}
.mobileBoardButtons {
margin-top: -3px;
}
.mobileBoardButtons .autoCheckbox {
margin: 0 3px 0 0;
position: relative;
top: -2px;
border-radius: 0;
height: 10px;
width: 10px;
}
.disabledButton {
opacity: 0.5 !important;
}
.disabledButton:hover {
cursor: not-allowed !important;
}
.mobileBoardButtons a, .desktopBoardButtons a {
all: unset;
}
@@ -53,6 +79,20 @@
padding-top: 15px;
}
.filteredThreadsCount {
text-transform: none !important;
}
.mobileBoardButtons {
text-transform: capitalize;
}
.mobileBoardButtons .filteredThreadsCount {
display: block;
margin-top: 3px;
margin-bottom: 15px;
}
@media (max-width: 640px) {
.desktopBoardButtons {
display: none;
+84 -1
View File
@@ -11,6 +11,9 @@ import styles from './board-buttons.module.css';
import Tooltip from '../tooltip';
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
import _ from 'lodash';
import useIsMobile from '../../hooks/use-is-mobile';
import useCatalogFiltersStore from '../../stores/use-catalog-filters-store';
import { useEffect } from 'react';
interface BoardButtonsProps {
address?: string | undefined;
@@ -85,6 +88,49 @@ const RefreshButton = () => {
);
};
const UpdateButton = () => {
const { t } = useTranslation();
const isMobile = useIsMobile();
return (
<>
{/* TODO: Implement update button once available in API */}
{isMobile ? (
<button className={`button ${styles.disabledButton}`} disabled>
{t('update')}
</button>
) : (
<button className={`button ${styles.disabledButton}`} disabled>
{t('update')}
</button>
)}
</>
);
};
const AutoButton = () => {
const { t } = useTranslation();
const isMobile = useIsMobile();
return (
<>
{isMobile ? (
<button className='button'>
<label>
<input type='checkbox' className={styles.autoCheckbox} checked disabled />
{t('Auto')}
</label>
</button>
) : (
<label>
{' '}
<input type='checkbox' className={styles.autoCheckbox} checked disabled /> {t('Auto')}
</label>
)}
</>
);
};
const SortOptions = () => {
const { t } = useTranslation();
const { sortType, setSortType } = useSortingStore();
@@ -176,6 +222,7 @@ export const TimeFilter = ({ isInAllView, isInCatalogView, isInSubscriptionsView
};
export const MobileBoardButtons = () => {
const { t } = useTranslation();
const params = useParams();
const location = useLocation();
const isInAllView = isAllView(location.pathname, params);
@@ -187,6 +234,14 @@ export const MobileBoardButtons = () => {
const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any });
const subplebbitAddress = params?.subplebbitAddress || accountComment?.subplebbitAddress;
const { filteredCount, resetFilteredCount } = useCatalogFiltersStore();
useEffect(() => {
if (subplebbitAddress) {
resetFilteredCount();
}
}, [subplebbitAddress, resetFilteredCount]);
return (
<div className={`${styles.mobileBoardButtons} ${!isInCatalogView ? styles.addMargin : ''}`}>
{isInPostView || isInPendingPostPage ? (
@@ -194,6 +249,10 @@ export const MobileBoardButtons = () => {
<ReturnButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} />
<CatalogButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} />
<SubscribeButton address={subplebbitAddress} />
<div className={styles.secondRow}>
<UpdateButton />
<AutoButton />
</div>
</>
) : (
<>
@@ -204,6 +263,12 @@ export const MobileBoardButtons = () => {
)}
{!(isInAllView || isInSubscriptionsView) && <SubscribeButton address={subplebbitAddress} />}
<RefreshButton />
{isInCatalogView && filteredCount > 0 && (
<span className={styles.filteredThreadsCount}>
{' '}
{t('filtered_threads')}: <strong>{filteredCount}</strong>
</span>
)}
{isInCatalogView && (
<>
<hr />
@@ -247,6 +312,7 @@ const PostPageStats = () => {
};
export const DesktopBoardButtons = () => {
const { t } = useTranslation();
const params = useParams();
const location = useLocation();
const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any });
@@ -257,6 +323,14 @@ export const DesktopBoardButtons = () => {
const isInPostView = isPostPageView(location.pathname, params);
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
const { filteredCount, resetFilteredCount } = useCatalogFiltersStore();
useEffect(() => {
if (subplebbitAddress) {
resetFilteredCount();
}
}, [subplebbitAddress, resetFilteredCount]);
return (
<>
<hr />
@@ -266,7 +340,10 @@ export const DesktopBoardButtons = () => {
[
<ReturnButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} />
] [
<CatalogButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} />]
<CatalogButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} />] [
<UpdateButton />
] [
<AutoButton />]
<span className={styles.rightSideButtons}>
<PostPageStats />
</span>
@@ -285,6 +362,12 @@ export const DesktopBoardButtons = () => {
</>
)}
[<RefreshButton />]
{isInCatalogView && filteredCount > 0 && (
<span className={styles.filteredThreadsCount}>
{' '}
{t('filtered_threads')}: <strong>{filteredCount}</strong>
</span>
)}
<span className={styles.rightSideButtons}>
{isInCatalogView && (
<>
@@ -80,6 +80,7 @@
.content img, .content video, .content iframe, .content audio {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.content img {
@@ -128,8 +129,15 @@
padding: 3px 20px 5px 20px;
}
.mediaDesktopOp {
max-width: calc(100% - 40px);
box-sizing: border-box;
}
.mediaDesktopOp img, .mediaDesktopOp video, .mediaDesktopOp iframe, .mediaDesktopOp audio {
padding: 3px 20px 5px 0;
padding: 3px 20px 5px 20px;
max-width: 100%;
box-sizing: border-box;
}
.fileDeleted {
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Comment } from '@plebbit/plebbit-react-hooks';
import styles from './comment-media.module.css';
import { CommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail } from '../../lib/utils/media-utils';
import { CommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils';
import { getHostname } from '../../lib/utils/url-utils';
import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
import useIsMobile from '../../hooks/use-is-mobile';
@@ -109,6 +109,7 @@ const Media = ({ commentMediaInfo, isReply, setShowThumbnail }: MediaProps) => {
const { thumbnail, type, url } = commentMediaInfo || {};
const isMobile = useIsMobile();
const mediaClass = isMobile ? styles.mediaMobile : isReply ? styles.mediaDesktopReply : styles.mediaDesktopOp;
const mediaDimensions = getMediaDimensions(commentMediaInfo);
return (
<span className={mediaClass}>
@@ -128,7 +129,8 @@ const Media = ({ commentMediaInfo, isReply, setShowThumbnail }: MediaProps) => {
<a href={url} target='_blank' rel='noopener noreferrer'>
{url && url.length > 30 ? url.slice(0, 30) + '...' : url}
</a>{' '}
({getDisplayMediaInfoType(type, t)})
({getDisplayMediaInfoType(type, t)}
{mediaDimensions && `, ${mediaDimensions}`})
</div>
)}
{isMobile && (type === 'iframe' || type === 'video' || type === 'audio') && (
+10 -4
View File
@@ -4,7 +4,7 @@ import { Link, useLocation, useParams } from 'react-router-dom';
import { Comment, useAccount, useAuthorAvatar, useComment, useEditedComment } from '@plebbit/plebbit-react-hooks';
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
import styles from '../../views/post/post.module.css';
import { getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail } from '../../lib/utils/media-utils';
import { getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils';
import { hashStringToColor, getTextColorForBackground } from '../../lib/utils/post-utils';
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
import { isValidURL } from '../../lib/utils/url-utils';
@@ -16,6 +16,7 @@ import useAuthorAddressClick from '../../hooks/use-author-address-click';
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
import useHide from '../../hooks/use-hide';
import usePostCidForPendingPost from '../../hooks/use-post-cid';
import useReplies from '../../hooks/use-replies';
import useStateString from '../../hooks/use-state-string';
import CommentMedia from '../comment-media';
@@ -70,9 +71,11 @@ const PostInfo = ({ openReplyModal, post, postReplyCount = 0, roles, isHidden }:
// comment.author.shortAddress is undefined while the comment publishing state is pending, use account instead
// in anon mode, use the newly generated signer.address instead, which will be comment.author.address
const { anonMode } = useAnonMode();
const { currentAnonSignerAddress } = useAnonModeStore();
const { getThreadSigner, currentAnonSignerAddress } = useAnonModeStore();
const postCidForSigner = usePostCidForPendingPost(parentCid);
const anonSignerAddress = postCidForSigner ? getThreadSigner(postCidForSigner)?.address || currentAnonSignerAddress : null;
const account = useAccount();
const pendingShortAddress = anonMode ? currentAnonSignerAddress && Plebbit.getShortAddress(currentAnonSignerAddress) : account?.author?.shortAddress;
const pendingShortAddress = anonMode ? anonSignerAddress && Plebbit.getShortAddress(anonSignerAddress) : account?.author?.shortAddress;
const handleUserAddressClick = useAuthorAddressClick();
const numberOfPostsByAuthor = document.querySelectorAll(`[data-author-address="${shortAddress}"][data-post-cid="${postCid}"]`).length;
@@ -229,6 +232,8 @@ const PostMedia = ({ post }: PostProps) => {
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
const [showThumbnail, setShowThumbnail] = useState(true);
const mediaDimensions = getMediaDimensions(commentMediaInfo);
return (
<div className={styles.file}>
<div className={styles.fileText}>
@@ -236,7 +241,8 @@ const PostMedia = ({ post }: PostProps) => {
<a href={url} target='_blank' rel='noopener noreferrer'>
{spoiler ? _.capitalize(t('spoiler')) : url && url.length > 30 ? url.slice(0, 30) + '...' : url}
</a>{' '}
({type && _.lowerCase(getDisplayMediaInfoType(type, t))})
({type && _.lowerCase(getDisplayMediaInfoType(type, t))}
{mediaDimensions && `, ${mediaDimensions}`})
{!showThumbnail && (type === 'iframe' || type === 'video' || type === 'audio') && (
<span>
{' '}
@@ -16,6 +16,7 @@
.postFormMobile {
text-align: center;
margin-top: -2px;
padding-bottom: 7px;
}
.closed {
-1
View File
@@ -449,7 +449,6 @@ const PostForm = () => {
{showForm ? t('close_post_form') : isInPostView ? t('post_a_reply') : t('start_new_thread')}
</button>
{showForm && <PostFormTable closeForm={() => setShowForm(false)} postCid={postCid} />}
<hr />
</>
)}
</div>
+5 -2
View File
@@ -14,6 +14,7 @@ import useAnonMode from '../../hooks/use-anon-mode';
import useAuthorAddressClick from '../../hooks/use-author-address-click';
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
import useHide from '../../hooks/use-hide';
import usePostCidForPendingPost from '../../hooks/use-post-cid';
import useReplies from '../../hooks/use-replies';
import useStateString from '../../hooks/use-state-string';
import CommentMedia from '../comment-media';
@@ -51,9 +52,11 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P
// comment.author.shortAddress is undefined while the comment publishing state is pending, use account instead
// in anon mode, use the newly generated signer.address instead, which will be comment.author.address
const { anonMode } = useAnonMode();
const { currentAnonSignerAddress } = useAnonModeStore();
const { getThreadSigner, currentAnonSignerAddress } = useAnonModeStore();
const postCidForSigner = usePostCidForPendingPost(parentCid);
const anonSignerAddress = postCidForSigner ? getThreadSigner(postCidForSigner)?.address || currentAnonSignerAddress : null;
const account = useAccount();
const pendingShortAddress = anonMode ? currentAnonSignerAddress && Plebbit.getShortAddress(currentAnonSignerAddress) : account?.author?.shortAddress;
const pendingShortAddress = anonMode ? anonSignerAddress && Plebbit.getShortAddress(anonSignerAddress) : account?.author?.shortAddress;
const stateString = useStateString(post);
@@ -3,6 +3,7 @@ import { createPortal } from 'react-dom';
import { Link, useNavigate } from 'react-router-dom';
import { Comment, useAccount } from '@plebbit/plebbit-react-hooks';
import { useFloating, offset, shift, size, autoUpdate, Placement } from '@floating-ui/react';
import useAnonModeStore from '../../stores/use-anon-mode-store';
import useIsMobile from '../../hooks/use-is-mobile';
import styles from '../../views/post/post.module.css';
import { Post } from '../../views/post';
@@ -143,6 +144,8 @@ const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, i
);
const account = useAccount();
const { getThreadSigner } = useAnonModeStore();
const threadSigner = quotelinkReply?.postCid ? getThreadSigner(quotelinkReply?.postCid) : null;
const replyQuotelink = (
<>
@@ -155,7 +158,7 @@ const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, i
onClick={(e) => handleClick(e, quotelinkReply?.cid, quotelinkReply?.subplebbitAddress)}
>
{quotelinkReply?.shortCid && `c/${quotelinkReply?.shortCid}`}
{quotelinkReply?.author?.address === account?.author?.address && ' (You)'}
{(quotelinkReply?.author?.address === account?.author?.address || quotelinkReply?.author?.address === threadSigner?.address) && ' (You)'}
</Link>
<br />
{hoveredCid === quotelinkReply?.cid &&
@@ -252,6 +255,8 @@ const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, is
);
const account = useAccount();
const { getThreadSigner } = useAnonModeStore();
const threadSigner = quotelinkReply?.postCid ? getThreadSigner(quotelinkReply?.postCid) : null;
const replyQuotelink = (
<>
@@ -262,7 +267,7 @@ const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, is
onMouseLeave={() => handleMouseLeave(quotelinkReply?.cid)}
>
{quotelinkReply?.shortCid && `c/${quotelinkReply?.shortCid}`}
{quotelinkReply?.author?.address === account?.author?.address && ' (You)'}
{(quotelinkReply?.author?.address === account?.author?.address || quotelinkReply?.author?.address === threadSigner?.address) && ' (You)'}
</span>
{quotelinkReply?.shortCid && (
<Link
@@ -9,6 +9,7 @@
white-space: pre-line;
max-width: 400px;
color: #fff;
overflow: hidden;
text-align: center;
}
+5
View File
@@ -31,6 +31,11 @@
margin-right: -7px;
}
.disabledButton {
opacity: 0.5;
cursor: not-allowed !important;
}
.searchBar input[type="text"] {
outline: none;
margin: 2px;
+4 -5
View File
@@ -85,28 +85,27 @@ const TopBarDesktop = () => {
{renderSubplebbits(projectsSubs)}
{renderSubplebbits(interestsSubs)}
{renderSubplebbits(randomSubs)}
{renderSubplebbits(internationalSubs)}
{/* [
{renderSubplebbits(internationalSubs)}[
<Link
className={styles.disabledButton}
to='boards/create'
onClick={(e) => {
e.preventDefault();
alert('work in progress');
}}
>
Create
</Link>
] [
<Link
className={styles.disabledButton}
to='boards'
onClick={(e) => {
e.preventDefault();
alert('work in progress');
}}
>
Vote
</Link>
] */}
]
</span>
<span className={styles.navTopRight}>
[<Link to={!location.pathname.endsWith('settings') ? location.pathname.replace(/\/$/, '') + '/settings' : location.pathname}>{t('settings')}</Link>] [
+43
View File
@@ -0,0 +1,43 @@
import { useEffect, useReducer } from 'react';
import { useComment } from '@plebbit/plebbit-react-hooks';
type State = {
resolvedPostCid: string | null;
currentParentCid: string | undefined;
};
type Action = { type: 'SET_POST_CID'; payload: string } | { type: 'SET_PARENT_CID'; payload: string };
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'SET_POST_CID':
return { ...state, resolvedPostCid: action.payload };
case 'SET_PARENT_CID':
return { ...state, currentParentCid: action.payload };
default:
return state;
}
};
const usePostCidForPendingPost = (initialParentCid: string | undefined): string | null => {
const [state, dispatch] = useReducer(reducer, {
resolvedPostCid: null,
currentParentCid: initialParentCid,
});
const comment = useComment({ commentCid: state.currentParentCid });
useEffect(() => {
if (comment) {
if (comment.postCid) {
dispatch({ type: 'SET_POST_CID', payload: comment.postCid });
} else if (comment.parentCid) {
dispatch({ type: 'SET_PARENT_CID', payload: comment.parentCid });
}
}
}, [comment]);
return state.resolvedPostCid;
};
export default usePostCidForPendingPost;
+11 -5
View File
@@ -5,6 +5,7 @@ import useThemeStore from '../stores/use-theme-store';
import useDefaultSubplebbits from './use-default-subplebbits';
import useInitialTheme from './use-initial-theme';
import { nsfwTags } from '../views/home/home';
import { useAccountComment } from '@plebbit/plebbit-react-hooks';
const themeClasses = ['yotsuba', 'yotsuba-b', 'futaba', 'burichan', 'tomorrow', 'photon'];
@@ -15,9 +16,14 @@ const updateThemeClass = (newTheme: string) => {
}
};
const useTheme = (pendingPostSubplebbitAddress?: string): [string, (theme: string) => void] => {
const useTheme = (): [string, (theme: string) => void] => {
const location = useLocation();
const params = useParams<{ subplebbitAddress: string }>();
const pendingPostParams = useParams<{ accountCommentIndex?: string }>();
const pendingPostCommentIndex = pendingPostParams?.accountCommentIndex ? parseInt(pendingPostParams.accountCommentIndex) : undefined;
const pendingPost = useAccountComment({ commentIndex: pendingPostCommentIndex });
const pendingPostSubplebbitAddress = pendingPost?.subplebbitAddress;
const setThemeStore = useThemeStore((state) => state.setTheme);
const getTheme = useThemeStore((state) => state.getTheme);
const loadThemes = useThemeStore((state) => state.loadThemes);
@@ -27,7 +33,7 @@ const useTheme = (pendingPostSubplebbitAddress?: string): [string, (theme: strin
const [currentTheme, setCurrentTheme] = useState(initialTheme);
const getCurrentTheme = useCallback(() => {
const subplebbitAddress = params?.subplebbitAddress;
const subplebbitAddress = params?.subplebbitAddress || pendingPostSubplebbitAddress;
const isInAllView = isAllView(location.pathname, params);
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
@@ -44,7 +50,7 @@ const useTheme = (pendingPostSubplebbitAddress?: string): [string, (theme: strin
}
return storedTheme || initialTheme;
}, [location.pathname, params, getTheme, subplebbits, initialTheme]);
}, [location.pathname, params, getTheme, subplebbits, initialTheme, pendingPostSubplebbitAddress]);
useEffect(() => {
const newTheme = getCurrentTheme();
@@ -60,7 +66,7 @@ const useTheme = (pendingPostSubplebbitAddress?: string): [string, (theme: strin
const setSubplebbitTheme = useCallback(
async (newTheme: string) => {
const subplebbitAddress = params?.subplebbitAddress;
const subplebbitAddress = params?.subplebbitAddress || pendingPostSubplebbitAddress;
const isInAllView = isAllView(location.pathname, params);
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
@@ -78,7 +84,7 @@ const useTheme = (pendingPostSubplebbitAddress?: string): [string, (theme: strin
setCurrentTheme(newTheme);
updateThemeClass(newTheme);
},
[location.pathname, params, setThemeStore, subplebbits],
[location.pathname, params, setThemeStore, subplebbits, pendingPostSubplebbitAddress],
);
return [currentTheme, setSubplebbitTheme];
+41 -2
View File
@@ -9,6 +9,7 @@ export interface CommentMediaInfo {
type: string;
thumbnail?: string;
patternThumbnailUrl?: string;
post?: Comment;
}
export const getDisplayMediaInfoType = (type: string, t: any) => {
@@ -106,8 +107,46 @@ export const getCommentMediaInfo = (comment: Comment): CommentMediaInfo | undefi
}
const linkInfo = comment.link ? getLinkMediaInfo(comment.link) : undefined;
if (linkInfo) {
linkInfo.thumbnail = comment.thumbnailUrl || linkInfo.thumbnail;
return linkInfo;
return {
...linkInfo,
thumbnail: comment.thumbnailUrl || linkInfo.thumbnail,
post: comment,
};
}
return;
};
export const getMediaDimensions = (commentMediaInfo: CommentMediaInfo | undefined): string => {
if (!commentMediaInfo) return '';
const { type, url, post } = commentMediaInfo;
if (type === 'iframe' && url) {
const embedUrl = new URL(url);
if (canEmbed(embedUrl)) {
// hardcoded dimensions from embed.module.css
if (embedUrl.hostname.includes('youtube.com') || embedUrl.hostname.includes('youtu.be')) {
return '800x450';
} else if (embedUrl.hostname.includes('instagram.com')) {
return '360x420';
} else if (embedUrl.hostname.includes('reddit.com')) {
return '500x520';
} else if (embedUrl.hostname.includes('tiktok.com')) {
return '400x780';
} else if (embedUrl.hostname.includes('x.com') || embedUrl.hostname.includes('twitter.com')) {
return '550x580';
} else if (embedUrl.hostname.includes('soundcloud.com')) {
return '700x166';
}
}
} else if (type === 'audio') {
return '700x240'; // hardcoded dimensions from embed.module.css
} else if (type === 'image' || type === 'video' || type === 'gif') {
// media dimensions calculated by API
if (post?.linkWidth && post?.linkHeight) {
return `${post.linkWidth}x${post.linkHeight}`;
}
}
return '';
};
+61 -5
View File
@@ -1,5 +1,7 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { Comment } from '@plebbit/plebbit-react-hooks';
import { getCommentMediaInfo, getHasThumbnail } from '../lib/utils/media-utils';
interface FilterItem {
text: string;
@@ -16,15 +18,23 @@ interface CatalogFiltersStore {
filterText: string;
setFilterText: (value: string) => void;
filterItems: FilterItem[];
setFilterItems: (items: FilterItem[]) => void; // New method to set all filter items at once
saveAndApplyFilters: (items: FilterItem[]) => void; // Updated to accept items
setFilterItems: (items: FilterItem[]) => void;
saveAndApplyFilters: (items: FilterItem[]) => void;
filter: ((comment: Comment) => boolean) | undefined;
updateFilter: () => void;
initializeFilter: () => void;
filteredCount: number;
resetFilteredCount: () => void;
}
const useCatalogFiltersStore = create(
persist<CatalogFiltersStore>(
(set) => ({
(set, get) => ({
showTextOnlyThreads: false,
setShowTextOnlyThreads: (value: boolean) => set({ showTextOnlyThreads: value }),
setShowTextOnlyThreads: (value: boolean) => {
set({ showTextOnlyThreads: value, filteredCount: 0 });
get().updateFilter();
},
showAdultBoards: false,
setShowAdultBoards: (value: boolean) => set({ showAdultBoards: value }),
showGoreBoards: false,
@@ -38,13 +48,59 @@ const useCatalogFiltersStore = create(
},
saveAndApplyFilters: (items: FilterItem[]) => {
const nonEmptyItems = items.filter((item) => item.text.trim() !== '');
set({ filterItems: nonEmptyItems });
set({ filterItems: nonEmptyItems, filteredCount: 0 });
get().updateFilter();
},
filter: undefined,
updateFilter: () => {
const filteredCids = new Set<string>();
set((state) => ({
filter: (comment: Comment) => {
const { showTextOnlyThreads, filterItems } = state;
const shouldShow = (() => {
if (!showTextOnlyThreads && !getHasThumbnail(getCommentMediaInfo(comment), comment?.link)) {
return false;
}
const title = comment?.title?.toLowerCase() || '';
const content = comment?.content?.toLowerCase() || '';
return !filterItems
.filter((item) => item.enabled)
.some((item) => {
const text = item.text.toLowerCase();
return title.includes(text) || content.includes(text);
});
})();
if (!shouldShow && !filteredCids.has(comment.cid)) {
filteredCids.add(comment.cid);
set((state) => ({ filteredCount: state.filteredCount + 1 }));
}
return shouldShow;
},
}));
},
initializeFilter: () => {
const { updateFilter } = get();
updateFilter();
},
filteredCount: 0,
resetFilteredCount: () => set({ filteredCount: 0 }),
}),
{
name: 'catalog-filters-storage',
onRehydrateStorage: () => (state) => {
if (state) {
state.updateFilter();
}
},
},
),
);
useCatalogFiltersStore.getState().updateFilter();
export default useCatalogFiltersStore;
+10 -10
View File
@@ -168,19 +168,19 @@ const Board = () => {
subplebbitAddress={postSubplebbitAddress}
/>
)}
{((description && description.length > 0) || isInAllView) && (
<SubplebbitDescription
avatarUrl={suggested?.avatarUrl}
subplebbitAddress={subplebbitAddress}
createdAt={createdAt}
description={description}
shortAddress={shortAddress}
title={title}
/>
)}
{feed.length !== 0 ? (
<>
{rules && rules.length > 0 && <SubplebbitRules subplebbitAddress={subplebbitAddress} createdAt={createdAt} rules={rules} />}
{((description && description.length > 0) || isInAllView) && (
<SubplebbitDescription
avatarUrl={suggested?.avatarUrl}
subplebbitAddress={subplebbitAddress}
createdAt={createdAt}
description={description}
shortAddress={shortAddress}
title={title}
/>
)}
<Virtuoso
increaseViewportBy={{ bottom: 1200, top: 1200 }}
totalCount={combinedFeed.length}
@@ -5,37 +5,53 @@ import { isAllView, isCatalogView } from '../../../lib/utils/view-utils';
import useCatalogFiltersStore from '../../../stores/use-catalog-filters-store';
import styles from './catalog-filters.module.css';
const FiltersTable = () => {
const FiltersTable = ({
localFilterItems,
setLocalFilterItems,
onSave,
}: {
localFilterItems: any[];
setLocalFilterItems: React.Dispatch<React.SetStateAction<any[]>>;
onSave: () => void;
}) => {
const { t } = useTranslation();
const { filterItems, saveAndApplyFilters } = useCatalogFiltersStore();
const [localFilterItems, setLocalFilterItems] = useState(filterItems);
const { saveAndApplyFilters } = useCatalogFiltersStore();
const handleAddFilter = useCallback(() => {
setLocalFilterItems((prev) => [...prev, { text: '', enabled: true }]);
}, []);
}, [setLocalFilterItems]);
const handleSave = useCallback(() => {
const nonEmptyFilters = localFilterItems.filter((item) => item.text.trim() !== '');
saveAndApplyFilters(nonEmptyFilters);
}, [saveAndApplyFilters, localFilterItems]);
onSave();
}, [saveAndApplyFilters, localFilterItems, onSave]);
const updateLocalFilterItem = useCallback((index: number, item: any) => {
setLocalFilterItems((prev) => prev.map((f, i) => (i === index ? item : f)));
}, []);
const updateLocalFilterItem = useCallback(
(index: number, item: any) => {
setLocalFilterItems((prev) => prev.map((f, i) => (i === index ? item : f)));
},
[setLocalFilterItems],
);
const removeLocalFilterItem = useCallback((index: number) => {
setLocalFilterItems((prev) => prev.filter((_, i) => i !== index));
}, []);
const removeLocalFilterItem = useCallback(
(index: number) => {
setLocalFilterItems((prev) => prev.filter((_, i) => i !== index));
},
[setLocalFilterItems],
);
const moveLocalFilterItemUp = useCallback((index: number) => {
if (index === 0) return;
setLocalFilterItems((prev) => {
const newItems = [...prev];
[newItems[index - 1], newItems[index]] = [newItems[index], newItems[index - 1]];
return newItems;
});
}, []);
const moveLocalFilterItemUp = useCallback(
(index: number) => {
if (index === 0) return;
setLocalFilterItems((prev) => {
const newItems = [...prev];
[newItems[index - 1], newItems[index]] = [newItems[index], newItems[index - 1]];
return newItems;
});
},
[setLocalFilterItems],
);
return (
<table className={styles.filtersTable}>
@@ -99,12 +115,38 @@ const FiltersTable = () => {
const FiltersModal = ({ closeModal }: { closeModal: () => void }) => {
const { t } = useTranslation();
const { showAdultBoards, setShowAdultBoards, showGoreBoards, setShowGoreBoards, showTextOnlyThreads, setShowTextOnlyThreads } = useCatalogFiltersStore();
const { showAdultBoards, setShowAdultBoards, showGoreBoards, setShowGoreBoards, showTextOnlyThreads, setShowTextOnlyThreads, filterItems, saveAndApplyFilters } =
useCatalogFiltersStore();
const [localShowAdultBoards, setLocalShowAdultBoards] = useState(showAdultBoards);
const [localShowGoreBoards, setLocalShowGoreBoards] = useState(showGoreBoards);
const [localShowTextOnlyThreads, setLocalShowTextOnlyThreads] = useState(showTextOnlyThreads);
const [localFilterItems, setLocalFilterItems] = useState(filterItems);
const location = useLocation();
const params = useParams();
const isInCatalogView = isCatalogView(location.pathname, params);
const isInAllView = isAllView(location.pathname, params);
const handleSave = useCallback(() => {
setShowAdultBoards(localShowAdultBoards);
setShowGoreBoards(localShowGoreBoards);
setShowTextOnlyThreads(localShowTextOnlyThreads);
const nonEmptyFilters = localFilterItems.filter((item) => item.text.trim() !== '');
saveAndApplyFilters(nonEmptyFilters);
closeModal();
}, [
localShowAdultBoards,
localShowGoreBoards,
localShowTextOnlyThreads,
localFilterItems,
setShowAdultBoards,
setShowGoreBoards,
setShowTextOnlyThreads,
saveAndApplyFilters,
closeModal,
]);
return (
<>
<div className={styles.overlay} onClick={closeModal} />
@@ -117,7 +159,7 @@ const FiltersModal = ({ closeModal }: { closeModal: () => void }) => {
{isInCatalogView && (
<div>
<label className='capitalize'>
<input type='checkbox' checked={!showTextOnlyThreads} onChange={(e) => setShowTextOnlyThreads(!e.target.checked)} />
<input type='checkbox' checked={!localShowTextOnlyThreads} onChange={(e) => setLocalShowTextOnlyThreads(!e.target.checked)} />
{t('hide_threads_without_images')}
</label>
</div>
@@ -126,20 +168,20 @@ const FiltersModal = ({ closeModal }: { closeModal: () => void }) => {
<div className={styles.nsfwLabels}>
<div>
<label>
<input type='checkbox' checked={!showGoreBoards} onChange={(e) => setShowGoreBoards(!e.target.checked)} />
<input type='checkbox' checked={!localShowGoreBoards} onChange={(e) => setLocalShowGoreBoards(!e.target.checked)} />
hide gore content
</label>
</div>
<div>
<label>
<input type='checkbox' checked={!showAdultBoards} onChange={(e) => setShowAdultBoards(!e.target.checked)} />
<input type='checkbox' checked={!localShowAdultBoards} onChange={(e) => setLocalShowAdultBoards(!e.target.checked)} />
hide adult content
</label>
</div>
</div>
)}
</div>
{isInCatalogView && <FiltersTable />}
{isInCatalogView && <FiltersTable localFilterItems={localFilterItems} setLocalFilterItems={setLocalFilterItems} onSave={handleSave} />}
</div>
</>
);
+13 -39
View File
@@ -1,9 +1,8 @@
import { useEffect, useMemo, useRef } from 'react';
import { useLocation, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Comment, useAccount, useFeed, useSubplebbit, useBlock } from '@plebbit/plebbit-react-hooks';
import { useAccount, useFeed, useSubplebbit, useBlock } from '@plebbit/plebbit-react-hooks';
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
import useCatalogFeedRows from '../../hooks/use-catalog-feed-rows';
import useDefaultSubplebbits from '../../hooks/use-default-subplebbits';
@@ -21,25 +20,6 @@ import styles from './catalog.module.css';
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
const threadsWithImagesFilter = (comment: Comment) => {
const commentMediaInfo = getCommentMediaInfo(comment);
const hasThumbnail = getHasThumbnail(commentMediaInfo, comment?.link);
return hasThumbnail;
};
const textFilter = (comment: Comment, filterItems: any[]) => {
if (!filterItems) return true;
if (filterItems.length === 0) return true;
const titleLower = comment?.title?.toLowerCase() || '';
const contentLower = comment?.content?.toLowerCase() || '';
return filterItems.every((item) => {
if (!item.enabled) return true;
const pattern = item.text.toLowerCase();
return !(titleLower.includes(pattern) || contentLower.includes(pattern));
});
};
const Catalog = () => {
const { t } = useTranslation();
const location = useLocation();
@@ -47,7 +27,7 @@ const Catalog = () => {
const isInAllView = isAllView(location.pathname, useParams());
const defaultSubplebbits = useDefaultSubplebbits();
const { showAdultBoards, showGoreBoards, showTextOnlyThreads, filterItems } = useCatalogFiltersStore();
const { filter, showAdultBoards, showGoreBoards } = useCatalogFiltersStore();
const account = useAccount();
const subscriptions = account?.subscriptions;
@@ -80,31 +60,25 @@ const Catalog = () => {
const columnWidth = imageSize === 'Large' ? 270 : 180;
const columnCount = Math.floor(useWindowWidth() / columnWidth);
// postPerPage based on columnCount for optimized feed, dont change value after first render
// eslint-disable-next-line
const postsPerPage = useMemo(() => (columnCount <= 2 ? 10 : columnCount === 3 ? 15 : columnCount === 4 ? 20 : 25), []);
const postsPerPage = columnCount <= 2 ? 10 : columnCount === 3 ? 15 : columnCount === 4 ? 20 : 25;
const { timeFilterSeconds } = useTimeFilter();
const { sortType } = useSortingStore();
const feedOptions: any = useMemo(
() => ({
const feedOptions = useMemo(() => {
const options: any = {
subplebbitAddresses,
sortType,
postsPerPage: isInAllView || isInSubscriptionsView ? 10 : postsPerPage,
filter: (comment: Comment) => {
if (!showTextOnlyThreads && !threadsWithImagesFilter(comment)) {
return false;
}
return textFilter(comment, filterItems);
},
}),
[subplebbitAddresses, sortType, isInAllView, isInSubscriptionsView, postsPerPage, showTextOnlyThreads, filterItems],
);
filter,
};
if (isInAllView || isInSubscriptionsView) {
feedOptions.newerThan = timeFilterSeconds;
}
if (isInAllView || isInSubscriptionsView) {
options.newerThan = timeFilterSeconds;
}
return options;
}, [subplebbitAddresses, sortType, isInAllView, isInSubscriptionsView, postsPerPage, timeFilterSeconds, filter]);
const { feed, hasMore, loadMore, reset } = useFeed(feedOptions);
-3
View File
@@ -4,7 +4,6 @@ import { useAccountComment } from '@plebbit/plebbit-react-hooks';
import { isSettingsView } from '../../lib/utils/view-utils';
import { Post } from '../post';
import SettingsModal from '../../components/settings-modal';
import useTheme from '../../hooks/use-theme';
const PendingPost = () => {
const { accountCommentIndex } = useParams<{ accountCommentIndex?: string }>();
@@ -15,8 +14,6 @@ const PendingPost = () => {
const params = useParams();
const isInSettingsView = isSettingsView(location.pathname, params);
useTheme(post?.subplebbitAddress);
useEffect(() => window.scrollTo(0, 0), []);
useEffect(() => {
+2
View File
@@ -160,6 +160,7 @@
.postDesktop .postMessage, .replyDesktop .postMessage {
padding: 1em 40px;
overflow: hidden;
}
.postDesktop .abbr {
@@ -328,6 +329,7 @@
.postMobile .postMessage {
padding: 10px;
font-size: var(--post-mobile-content-font-size);
overflow: hidden;
word-break: break-word;
}