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