mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "نمط",
|
"pattern": "نمط",
|
||||||
"enable": "تفعيل",
|
"enable": "تفعيل",
|
||||||
"order": "ترتيب",
|
"order": "ترتيب",
|
||||||
"text": "نص"
|
"text": "نص",
|
||||||
|
"filtered_threads": "المواضيع المفلترة"
|
||||||
}
|
}
|
||||||
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "প্যাটার্ন",
|
"pattern": "প্যাটার্ন",
|
||||||
"enable": "সক্রিয় করুন",
|
"enable": "সক্রিয় করুন",
|
||||||
"order": "অর্ডার",
|
"order": "অর্ডার",
|
||||||
"text": "টেক্সট"
|
"text": "টেক্সট",
|
||||||
|
"filtered_threads": "ফিল্টার করা থ্রেড"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "Muster",
|
"pattern": "Muster",
|
||||||
"enable": "Aktivieren",
|
"enable": "Aktivieren",
|
||||||
"order": "Reihenfolge",
|
"order": "Reihenfolge",
|
||||||
"text": "Text"
|
"text": "Text",
|
||||||
|
"filtered_threads": "Gefilterte Threads"
|
||||||
}
|
}
|
||||||
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "Μοτίβο",
|
"pattern": "Μοτίβο",
|
||||||
"enable": "Ενεργοποιήστε",
|
"enable": "Ενεργοποιήστε",
|
||||||
"order": "Σειρά",
|
"order": "Σειρά",
|
||||||
"text": "Κείμενο"
|
"text": "Κείμενο",
|
||||||
|
"filtered_threads": "Φιλτραρισμένα θέματα"
|
||||||
}
|
}
|
||||||
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "Pattern",
|
"pattern": "Pattern",
|
||||||
"enable": "Enable",
|
"enable": "Enable",
|
||||||
"order": "Order",
|
"order": "Order",
|
||||||
"text": "Text"
|
"text": "Text",
|
||||||
|
"filtered_threads": "Filtered threads"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "الگو",
|
"pattern": "الگو",
|
||||||
"enable": "فعال کردن",
|
"enable": "فعال کردن",
|
||||||
"order": "ترتیب",
|
"order": "ترتیب",
|
||||||
"text": "متن"
|
"text": "متن",
|
||||||
|
"filtered_threads": "رشتههای فیلتر شده"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "תבנית",
|
"pattern": "תבנית",
|
||||||
"enable": "הפעל",
|
"enable": "הפעל",
|
||||||
"order": "סדר",
|
"order": "סדר",
|
||||||
"text": "טקסט"
|
"text": "טקסט",
|
||||||
|
"filtered_threads": "אשכולות ממוינים"
|
||||||
}
|
}
|
||||||
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "पैटर्न",
|
"pattern": "पैटर्न",
|
||||||
"enable": "सक्षम करें",
|
"enable": "सक्षम करें",
|
||||||
"order": "क्रम",
|
"order": "क्रम",
|
||||||
"text": "पाठ"
|
"text": "पाठ",
|
||||||
|
"filtered_threads": "फ़िल्टर किए गए थ्रेड्स"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "Modello",
|
"pattern": "Modello",
|
||||||
"enable": "Abilita",
|
"enable": "Abilita",
|
||||||
"order": "Ordine",
|
"order": "Ordine",
|
||||||
"text": "Testo"
|
"text": "Testo",
|
||||||
|
"filtered_threads": "Thread filtrati"
|
||||||
}
|
}
|
||||||
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "パターン",
|
"pattern": "パターン",
|
||||||
"enable": "有効にする",
|
"enable": "有効にする",
|
||||||
"order": "順序",
|
"order": "順序",
|
||||||
"text": "テキスト"
|
"text": "テキスト",
|
||||||
|
"filtered_threads": "フィルタリングされたスレッド"
|
||||||
}
|
}
|
||||||
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "패턴",
|
"pattern": "패턴",
|
||||||
"enable": "사용",
|
"enable": "사용",
|
||||||
"order": "순서",
|
"order": "순서",
|
||||||
"text": "텍스트"
|
"text": "텍스트",
|
||||||
|
"filtered_threads": "필터링된 스레드"
|
||||||
}
|
}
|
||||||
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "पॅटर्न",
|
"pattern": "पॅटर्न",
|
||||||
"enable": "सक्षम करा",
|
"enable": "सक्षम करा",
|
||||||
"order": "आदेश",
|
"order": "आदेश",
|
||||||
"text": "पाठ"
|
"text": "पाठ",
|
||||||
|
"filtered_threads": "फिल्टर केलेले ध糹रे"
|
||||||
}
|
}
|
||||||
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "Patroon",
|
"pattern": "Patroon",
|
||||||
"enable": "Inschakelen",
|
"enable": "Inschakelen",
|
||||||
"order": "Volgorde",
|
"order": "Volgorde",
|
||||||
"text": "Tekst"
|
"text": "Tekst",
|
||||||
|
"filtered_threads": "Gefilterde threads"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "Model",
|
"pattern": "Model",
|
||||||
"enable": "Activare",
|
"enable": "Activare",
|
||||||
"order": "Ordine",
|
"order": "Ordine",
|
||||||
"text": "Text"
|
"text": "Text",
|
||||||
|
"filtered_threads": "Fire filtrate"
|
||||||
}
|
}
|
||||||
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "Шаблон",
|
"pattern": "Шаблон",
|
||||||
"enable": "Включить",
|
"enable": "Включить",
|
||||||
"order": "Порядок",
|
"order": "Порядок",
|
||||||
"text": "Текст"
|
"text": "Текст",
|
||||||
|
"filtered_threads": "Отфильтрованные темы"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "ప్యాటర్న్",
|
"pattern": "ప్యాటర్న్",
|
||||||
"enable": "చేర్చు",
|
"enable": "చేర్చు",
|
||||||
"order": "ఆర్డర్",
|
"order": "ఆర్డర్",
|
||||||
"text": "పాఠం"
|
"text": "పాఠం",
|
||||||
|
"filtered_threads": "ఫిల్టర్ చేయబడిన థ్రెడ్లు"
|
||||||
}
|
}
|
||||||
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "รูปแบบ",
|
"pattern": "รูปแบบ",
|
||||||
"enable": "เปิดใช้งาน",
|
"enable": "เปิดใช้งาน",
|
||||||
"order": "ลำดับ",
|
"order": "ลำดับ",
|
||||||
"text": "ข้อความ"
|
"text": "ข้อความ",
|
||||||
|
"filtered_threads": "กระทู้ที่กรองแล้ว"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "Шаблон",
|
"pattern": "Шаблон",
|
||||||
"enable": "Увімкнути",
|
"enable": "Увімкнути",
|
||||||
"order": "Порядок",
|
"order": "Порядок",
|
||||||
"text": "Текст"
|
"text": "Текст",
|
||||||
|
"filtered_threads": "Відфільтровані треди"
|
||||||
}
|
}
|
||||||
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "نمونہ",
|
"pattern": "نمونہ",
|
||||||
"enable": "فعال کریں",
|
"enable": "فعال کریں",
|
||||||
"order": "ترتیب",
|
"order": "ترتیب",
|
||||||
"text": "متن"
|
"text": "متن",
|
||||||
|
"filtered_threads": "فلٹر کیے گئے تھریڈز"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -231,5 +231,6 @@
|
|||||||
"pattern": "模式",
|
"pattern": "模式",
|
||||||
"enable": "启用",
|
"enable": "启用",
|
||||||
"order": "顺序",
|
"order": "顺序",
|
||||||
"text": "文本"
|
"text": "文本",
|
||||||
|
"filtered_threads": "已过滤的帖子"
|
||||||
}
|
}
|
||||||
+1
-1
@@ -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;
|
||||||
|
|||||||
@@ -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') && (
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>] [
|
||||||
|
|||||||
@@ -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
@@ -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];
|
||||||
|
|||||||
@@ -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 '';
|
||||||
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user