diff --git a/package.json b/package.json index 751ee020..0ce8b2c1 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "@capacitor/status-bar": "7.0.1", "@capawesome/capacitor-android-edge-to-edge-support": "7.2.2", "@floating-ui/react": "0.26.1", - "@plebbit/plebbit-react-hooks": "https://github.com/plebbit/plebbit-react-hooks.git#0bfd516d2bfae47bfc255988a2e845f849adcf12", + "@plebbit/plebbit-react-hooks": "https://github.com/plebbit/plebbit-react-hooks.git#a828bd598bcc392ed379136feaee0a7b1c8cd23f", "@react-spring/web": "9.7.5", "@types/node": "20.8.2", "@types/react": "18.2.25", diff --git a/public/translations/ar/default.json b/public/translations/ar/default.json index 0fbafc72..1bf818c2 100644 --- a/public/translations/ar/default.json +++ b/public/translations/ar/default.json @@ -291,5 +291,9 @@ "continue": "متابعة", "loading_editor": "جاري تحميل المحرر...", "editor_fallback_warning": "فشل تحميل محرر الأكواد. يتم استخدام حقل نص عادي بدلاً منه.", - "return_to_settings": "العودة إلى الإعدادات" + "return_to_settings": "العودة إلى الإعدادات", + "import_account_backup": "استيراد نسخة احتياطية للحساب", + "account_auto_generated": "تم إنشاء هذا الحساب تلقائياً بواسطة 5chan، دون الحاجة للتسجيل.", + "download_backup": "تحميل النسخة الاحتياطية", + "boards_you_moderate_nav": "المنتديات التي تقوم بالإشراف عليها" } diff --git a/public/translations/bn/default.json b/public/translations/bn/default.json index 6f54cc9f..a8e3bfb9 100644 --- a/public/translations/bn/default.json +++ b/public/translations/bn/default.json @@ -291,5 +291,9 @@ "continue": "চালিয়ে যান", "loading_editor": "এডিটর লোড হচ্ছে...", "editor_fallback_warning": "কোড এডিটর লোড করতে ব্যর্থ হয়েছে। পরিবর্তে একটি সাধারণ টেক্সট এরিয়া ব্যবহার করা হচ্ছে।", - "return_to_settings": "সেটিংসে ফিরে যান" + "return_to_settings": "সেটিংসে ফিরে যান", + "import_account_backup": "অ্যাকাউন্ট ব্যাকআপ আমদানি করুন", + "account_auto_generated": "এই অ্যাকাউন্টটি 5chan দ্বারা স্বয়ংক্রিয়ভাবে তৈরি হয়েছে, সাইনআপের প্রয়োজন নেই।", + "download_backup": "ব্যাকআপ ডাউনলোড করুন", + "boards_you_moderate_nav": "আপনি যা মডারেট করেন এমন বোর্ড" } diff --git a/public/translations/cs/default.json b/public/translations/cs/default.json index 00be218e..7be46bed 100644 --- a/public/translations/cs/default.json +++ b/public/translations/cs/default.json @@ -291,5 +291,9 @@ "continue": "Pokračovat", "loading_editor": "Načítání editoru...", "editor_fallback_warning": "Načtení editoru kódu selhalo. Místo toho se používá jednoduché textové pole.", - "return_to_settings": "Zpět na nastavení" + "return_to_settings": "Zpět na nastavení", + "import_account_backup": "Importovat zálohu účtu", + "account_auto_generated": "Tento účet byl automaticky vygenerován 5chanem, registrace není nutná.", + "download_backup": "Stáhnout zálohu", + "boards_you_moderate_nav": "Fóra, která moderujete" } diff --git a/public/translations/da/default.json b/public/translations/da/default.json index 3224660e..b2d7ef74 100644 --- a/public/translations/da/default.json +++ b/public/translations/da/default.json @@ -291,5 +291,9 @@ "continue": "Fortsæt", "loading_editor": "Indlæser editor...", "editor_fallback_warning": "Kodeeditoren kunne ikke indlæses. Bruger et simpelt tekstfelt i stedet.", - "return_to_settings": "Tilbage til indstillinger" + "return_to_settings": "Tilbage til indstillinger", + "import_account_backup": "Importer kontobackup", + "account_auto_generated": "Denne konto blev automatisk oprettet af 5chan, ingen tilmelding påkrævet.", + "download_backup": "Download sikkerhedskopi", + "boards_you_moderate_nav": "Boards du modererer" } diff --git a/public/translations/de/default.json b/public/translations/de/default.json index 1d7f4b24..dd9245b1 100644 --- a/public/translations/de/default.json +++ b/public/translations/de/default.json @@ -291,5 +291,9 @@ "continue": "Weiter", "loading_editor": "Editor wird geladen...", "editor_fallback_warning": "Der Code-Editor konnte nicht geladen werden. Es wird stattdessen ein einfaches Textfeld verwendet.", - "return_to_settings": "Zurück zu den Einstellungen" + "return_to_settings": "Zurück zu den Einstellungen", + "import_account_backup": "Kontosicherung importieren", + "account_auto_generated": "Dieses Konto wurde automatisch von 5chan erstellt, keine Anmeldung erforderlich.", + "download_backup": "Sicherung herunterladen", + "boards_you_moderate_nav": "Von dir moderierte Boards" } diff --git a/public/translations/el/default.json b/public/translations/el/default.json index 210e1dc4..2b276136 100644 --- a/public/translations/el/default.json +++ b/public/translations/el/default.json @@ -291,5 +291,9 @@ "continue": "Συνέχεια", "loading_editor": "Φόρτωση του επεξεργαστή...", "editor_fallback_warning": "Αποτυχία φόρτωσης του επεξεργαστή κώδικα. Χρησιμοποιείται απλό πεδίο κειμένου αντί για αυτό.", - "return_to_settings": "Επιστροφή στις Ρυθμίσεις" + "return_to_settings": "Επιστροφή στις Ρυθμίσεις", + "import_account_backup": "Εισαγωγή αντιγράφου ασφαλείας λογαριασμού", + "account_auto_generated": "Αυτός ο λογαριασμός δημιουργήθηκε αυτόματα από το 5chan, χωρίς απαιτούμενη εγγραφή.", + "download_backup": "Λήψη αντιγράφου ασφαλείας", + "boards_you_moderate_nav": "Πίνακες που διαχειρίζεστε" } diff --git a/public/translations/en/default.json b/public/translations/en/default.json index 04dacc85..a607790f 100644 --- a/public/translations/en/default.json +++ b/public/translations/en/default.json @@ -290,5 +290,9 @@ "continue": "Continue", "loading_editor": "Loading editor...", "editor_fallback_warning": "The code editor failed to load. Using a plain textarea instead.", - "return_to_settings": "Return to Settings" + "return_to_settings": "Return to Settings", + "account_auto_generated": "This account was automatically generated by 5chan, no signup required.", + "download_backup": "Download Backup", + "import_account_backup": "Import Account Backup", + "boards_you_moderate_nav": "Boards You Moderate" } diff --git a/public/translations/es/default.json b/public/translations/es/default.json index 0cf6e79c..12180d94 100644 --- a/public/translations/es/default.json +++ b/public/translations/es/default.json @@ -291,5 +291,9 @@ "continue": "Continuar", "loading_editor": "Cargando editor...", "editor_fallback_warning": "No se pudo cargar el editor de código. Se usa un campo de texto simple en su lugar.", - "return_to_settings": "Volver a Configuración" + "return_to_settings": "Volver a Configuración", + "import_account_backup": "Importar copia de seguridad de la cuenta", + "account_auto_generated": "Esta cuenta fue generada automáticamente por 5chan, no se requiere registro.", + "download_backup": "Descargar copia de seguridad", + "boards_you_moderate_nav": "Tableros que moderas" } diff --git a/public/translations/fa/default.json b/public/translations/fa/default.json index 13959cdc..b0088d55 100644 --- a/public/translations/fa/default.json +++ b/public/translations/fa/default.json @@ -291,5 +291,9 @@ "continue": "ادامه", "loading_editor": "در حال بارگذاری ویرایشگر...", "editor_fallback_warning": "بارگذاری ویرایشگر کد ناموفق بود. در عوض از یک فیلد متنی ساده استفاده می‌شود.", - "return_to_settings": "بازگشت به تنظیمات" + "return_to_settings": "بازگشت به تنظیمات", + "import_account_backup": "وارد کردن پشتیبان حساب", + "account_auto_generated": "این حساب به‌طور خودکار توسط 5chan ایجاد شده، نیازی به ثبت‌نام نیست.", + "download_backup": "دانلود پشتیبان", + "boards_you_moderate_nav": "تابلوهایی که مدیریت می‌کنید" } diff --git a/public/translations/fi/default.json b/public/translations/fi/default.json index 8cb16c6e..1af4d63d 100644 --- a/public/translations/fi/default.json +++ b/public/translations/fi/default.json @@ -209,7 +209,7 @@ "help": "Apua", "filter_and_highlights_help": "Suodattimet ja korostukset -apu", "filter_and_highlights": "Suodattimet ja korostukset", - "boards_you_moderate": "lautakunnat, joita valvot", + "boards_you_moderate": "Laudat, joita moderoit", "moderator_of_this_board": "Tämä käyttäjä on tämän taulun moderaattori", "administrator_of_this_board": "Tämä käyttäjä on tämän taulun järjestelmänvalvoja", "copy_content_id": "kopioi sisältö-ID", @@ -291,5 +291,9 @@ "continue": "Jatka", "loading_editor": "Ladataan editoria...", "editor_fallback_warning": "Koodieditorin lataus epäonnistui. Käytetään sen sijaan yksinkertaista tekstikenttää.", - "return_to_settings": "Palaa asetuksiin" + "return_to_settings": "Palaa asetuksiin", + "import_account_backup": "Tuo tilin varmuuskopio", + "account_auto_generated": "Tämä tili luotiin automaattisesti 5chanissa, rekisteröitymistä ei tarvita.", + "download_backup": "Lataa varmuuskopio", + "boards_you_moderate_nav": "Laudat, joita moderoit" } diff --git a/public/translations/fil/default.json b/public/translations/fil/default.json index 796ac3ae..a654a316 100644 --- a/public/translations/fil/default.json +++ b/public/translations/fil/default.json @@ -291,5 +291,9 @@ "continue": "Magpatuloy", "loading_editor": "Nilo-load ang editor...", "editor_fallback_warning": "Nabigo ang pag-load ng code editor. Gumagamit ng plain textarea sa halip.", - "return_to_settings": "Bumalik sa Mga Setting" + "return_to_settings": "Bumalik sa Mga Setting", + "import_account_backup": "I-import ang Backup ng Account", + "account_auto_generated": "Ang account na ito ay awtomatikong nilikha ng 5chan, walang kailangang signup.", + "download_backup": "I-download ang Backup", + "boards_you_moderate_nav": "Mga board na minomodera mo" } diff --git a/public/translations/fr/default.json b/public/translations/fr/default.json index 11ce0c08..6a17ff1c 100644 --- a/public/translations/fr/default.json +++ b/public/translations/fr/default.json @@ -209,7 +209,7 @@ "help": "Aide", "filter_and_highlights_help": "Aide sur les filtres et surlignages", "filter_and_highlights": "Filtres et surlignages", - "boards_you_moderate": "boards que vous modérez", + "boards_you_moderate": "Tableaux que vous modérez", "moderator_of_this_board": "Cet utilisateur est modérateur de ce tableau", "administrator_of_this_board": "Cet utilisateur est administrateur de ce tableau", "copy_content_id": "Copier l'ID du contenu", @@ -291,5 +291,9 @@ "continue": "Continuer", "loading_editor": "Chargement de l'éditeur...", "editor_fallback_warning": "Le chargement de l'éditeur de code a échoué. Utilisation d'un champ de texte simple à la place.", - "return_to_settings": "Retour aux paramètres" + "return_to_settings": "Retour aux paramètres", + "import_account_backup": "Importer la sauvegarde du compte", + "account_auto_generated": "Ce compte a été généré automatiquement par 5chan, aucune inscription requise.", + "download_backup": "Télécharger la sauvegarde", + "boards_you_moderate_nav": "Tableaux que vous modérez" } diff --git a/public/translations/he/default.json b/public/translations/he/default.json index d56b54e1..cbc32f87 100644 --- a/public/translations/he/default.json +++ b/public/translations/he/default.json @@ -291,5 +291,9 @@ "continue": "המשך", "loading_editor": "טוען עורך...", "editor_fallback_warning": "טעינת עורך הקוד נכשלה. משתמשים בשדה טקסט פשוט במקום.", - "return_to_settings": "חזרה להגדרות" + "return_to_settings": "חזרה להגדרות", + "import_account_backup": "ייבוא גיבוי חשבון", + "account_auto_generated": "חשבון זה נוצר אוטומטית על ידי 5chan, אין צורך בהרשמה.", + "download_backup": "הורד גיבוי", + "boards_you_moderate_nav": "לוחות שאתה מפקח עליהם" } diff --git a/public/translations/hi/default.json b/public/translations/hi/default.json index 728e4a9f..10c3d052 100644 --- a/public/translations/hi/default.json +++ b/public/translations/hi/default.json @@ -291,5 +291,9 @@ "continue": "जारी रखें", "loading_editor": "संपादक लोड हो रहा है...", "editor_fallback_warning": "कोड एडिटर लोड नहीं हो सका। इसके बजाय एक सामान्य टेक्स्ट एरिया का उपयोग किया जा रहा है।", - "return_to_settings": "सेटिंग्स पर वापस जाएं" + "return_to_settings": "सेटिंग्स पर वापस जाएं", + "import_account_backup": "खाता बैकअप आयात करें", + "account_auto_generated": "यह खाता 5chan द्वारा स्वचालित रूप से बनाया गया था, साइनअप की आवश्यकता नहीं है।", + "download_backup": "बैकअप डाउनलोड करें", + "boards_you_moderate_nav": "बोर्ड जिन्हें आप मॉडरेट करते हैं" } diff --git a/public/translations/hu/default.json b/public/translations/hu/default.json index 1326d21c..93194ec3 100644 --- a/public/translations/hu/default.json +++ b/public/translations/hu/default.json @@ -291,5 +291,9 @@ "continue": "Folytatás", "loading_editor": "Szerkesztő betöltése...", "editor_fallback_warning": "A kódszerkesztő betöltése sikertelen. Egyszerű szövegmező használata helyette.", - "return_to_settings": "Vissza a beállításokhoz" + "return_to_settings": "Vissza a beállításokhoz", + "import_account_backup": "Fiók biztonsági mentés importálása", + "account_auto_generated": "Ezt a fiókot a 5chan automatikusan hozta létre, regisztráció nem szükséges.", + "download_backup": "Biztonsági mentés letöltése", + "boards_you_moderate_nav": "A táblák, amelyeket moderálsz" } diff --git a/public/translations/id/default.json b/public/translations/id/default.json index ea3e3afe..8d7a5987 100644 --- a/public/translations/id/default.json +++ b/public/translations/id/default.json @@ -291,5 +291,9 @@ "continue": "Lanjutkan", "loading_editor": "Memuat editor...", "editor_fallback_warning": "Editor kode gagal dimuat. Menggunakan textarea biasa sebagai gantinya.", - "return_to_settings": "Kembali ke Pengaturan" + "return_to_settings": "Kembali ke Pengaturan", + "import_account_backup": "Impor Cadangan Akun", + "account_auto_generated": "Akun ini dibuat secara otomatis oleh 5chan, tidak perlu mendaftar.", + "download_backup": "Unduh cadangan", + "boards_you_moderate_nav": "Papan yang Anda moderasi" } diff --git a/public/translations/it/default.json b/public/translations/it/default.json index bc75de8e..8c2ac344 100644 --- a/public/translations/it/default.json +++ b/public/translations/it/default.json @@ -291,5 +291,9 @@ "continue": "Continua", "loading_editor": "Caricamento editor...", "editor_fallback_warning": "Il caricamento dell'editor di codice non è riuscito. Si usa un campo di testo semplice al suo posto.", - "return_to_settings": "Torna alle impostazioni" + "return_to_settings": "Torna alle impostazioni", + "import_account_backup": "Importa backup account", + "account_auto_generated": "Questo account è stato generato automaticamente da 5chan, nessuna registrazione richiesta.", + "download_backup": "Scarica backup", + "boards_you_moderate_nav": "Board che moderi" } diff --git a/public/translations/ja/default.json b/public/translations/ja/default.json index 90efeb0e..dc1b61b9 100644 --- a/public/translations/ja/default.json +++ b/public/translations/ja/default.json @@ -291,5 +291,9 @@ "continue": "続ける", "loading_editor": "エディターを読み込み中...", "editor_fallback_warning": "コードエディターの読み込みに失敗しました。代わりにプレーンテキストエリアを使用します。", - "return_to_settings": "設定に戻る" + "return_to_settings": "設定に戻る", + "import_account_backup": "アカウントバックアップをインポート", + "account_auto_generated": "このアカウントは5chanによって自動生成されました。サインアップは不要です。", + "download_backup": "バックアップをダウンロード", + "boards_you_moderate_nav": "あなたがモデレートする板" } diff --git a/public/translations/ko/default.json b/public/translations/ko/default.json index a289fb97..72b64b13 100644 --- a/public/translations/ko/default.json +++ b/public/translations/ko/default.json @@ -209,7 +209,7 @@ "help": "도움말", "filter_and_highlights_help": "필터 및 하이라이트 도움말", "filter_and_highlights": "필터 및 하이라이트", - "boards_you_moderate": "당신이 관리하는 게시판", + "boards_you_moderate": "관리하는 게시판", "moderator_of_this_board": "이 사용자는 이 게시판의 관리자입니다", "administrator_of_this_board": "이 사용자는 이 게시판의 관리자입니다", "copy_content_id": "콘텐츠 ID 복사", @@ -291,5 +291,9 @@ "continue": "계속", "loading_editor": "편집기 로딩 중...", "editor_fallback_warning": "코드 편집기 로드에 실패했습니다. 대신 일반 텍스트 영역을 사용합니다.", - "return_to_settings": "설정으로 돌아가기" + "return_to_settings": "설정으로 돌아가기", + "import_account_backup": "계정 백업 가져오기", + "account_auto_generated": "이 계정은 5chan에서 자동으로 생성되었으며, 가입이 필요하지 않습니다.", + "download_backup": "백업 다운로드", + "boards_you_moderate_nav": "당신이 관리하는 게시판" } diff --git a/public/translations/mr/default.json b/public/translations/mr/default.json index e06ac9fc..9b357ec5 100644 --- a/public/translations/mr/default.json +++ b/public/translations/mr/default.json @@ -168,7 +168,7 @@ "pending": "प्रलंबित", "failed": "असफल", "add": "अॅड करा", - "filtered_threads": "फिल्टर केलेले ध糹रे", + "filtered_threads": "फिल्टर केलेले धागे", "missing_link_confirm": "तुमचा पोस्ट डिफॉल्टनुसार 5chan वर दृश्यमान होणार नाही, कारण त्यात चित्र किंवा इतर मीडियाची लिंक नाही. तुम्ही सेटिंग्जमध्ये हा फिल्टर अक्षम करू शकता।", "no_board_selected_warning": "पोस्ट करण्यापूर्वी कृपया एक बोर्ड निवडा.", "invalid_url_alert": "दिलेला लिंक वैध URL नाही.", @@ -291,5 +291,9 @@ "continue": "सुरू ठेवा", "loading_editor": "संपादक लोड होत आहे...", "editor_fallback_warning": "कोड एडिटर लोड नाही झाला. त्याऐवजी साध्या टेक्स्ट एरियाचा वापर केला जात आहे.", - "return_to_settings": "सेटिंग्जवर परत जा" + "return_to_settings": "सेटिंग्जवर परत जा", + "import_account_backup": "खाते बॅकअप आयात करा", + "account_auto_generated": "हा खाता 5chan द्वारा स्वयंचलितपणे तयार केला गेला, साइनअप आवश्यक नाही.", + "download_backup": "बॅकअप डाउनलोड करा", + "boards_you_moderate_nav": "तुम्ही मॉडरेट करता ते बोर्ड" } diff --git a/public/translations/nl/default.json b/public/translations/nl/default.json index 9177b3aa..04060ee6 100644 --- a/public/translations/nl/default.json +++ b/public/translations/nl/default.json @@ -291,5 +291,9 @@ "continue": "Doorgaan", "loading_editor": "Editor laden...", "editor_fallback_warning": "De code-editor kon niet worden geladen. Er wordt een eenvoudig tekstveld gebruikt.", - "return_to_settings": "Terug naar Instellingen" + "return_to_settings": "Terug naar Instellingen", + "import_account_backup": "Accountback-up importeren", + "account_auto_generated": "Dit account is automatisch gegenereerd door 5chan, geen aanmelding vereist.", + "download_backup": "Back-up downloaden", + "boards_you_moderate_nav": "Boards die je beheert" } diff --git a/public/translations/no/default.json b/public/translations/no/default.json index 80d83013..f42e732d 100644 --- a/public/translations/no/default.json +++ b/public/translations/no/default.json @@ -291,5 +291,9 @@ "continue": "Fortsett", "loading_editor": "Laster editor...", "editor_fallback_warning": "Kodeeditoren kunne ikke lastes. Bruker et enkelt tekstfelt i stedet.", - "return_to_settings": "Tilbake til Innstillinger" + "return_to_settings": "Tilbake til Innstillinger", + "import_account_backup": "Importer kontobackup", + "account_auto_generated": "Denne kontoen ble automatisk opprettet av 5chan, ingen registrering nødvendig.", + "download_backup": "Last ned sikkerhetskopi", + "boards_you_moderate_nav": "Boards du modererer" } diff --git a/public/translations/pl/default.json b/public/translations/pl/default.json index 6c279b2a..afc347a1 100644 --- a/public/translations/pl/default.json +++ b/public/translations/pl/default.json @@ -291,5 +291,9 @@ "continue": "Kontynuuj", "loading_editor": "Ładowanie edytora...", "editor_fallback_warning": "Nie udało się załadować edytora kodu. Używane jest zamiast tego prostego pola tekstowego.", - "return_to_settings": "Powrót do ustawień" + "return_to_settings": "Powrót do ustawień", + "import_account_backup": "Importuj kopię zapasową konta", + "account_auto_generated": "To konto zostało automatycznie wygenerowane przez 5chan, rejestracja nie jest wymagana.", + "download_backup": "Pobierz kopię zapasową", + "boards_you_moderate_nav": "Tablice, które moderujesz" } diff --git a/public/translations/pt/default.json b/public/translations/pt/default.json index 82c4d259..bfb2a287 100644 --- a/public/translations/pt/default.json +++ b/public/translations/pt/default.json @@ -209,7 +209,7 @@ "help": "Ajuda", "filter_and_highlights_help": "Ajuda de Filtros e Realces", "filter_and_highlights": "Filtros e Destaques", - "boards_you_moderate": "boards que você modera", + "boards_you_moderate": "Quadros que você modera", "moderator_of_this_board": "Este usuário é moderador deste quadro", "administrator_of_this_board": "Este usuário é administrador deste quadro", "copy_content_id": "Copiar ID do conteúdo", @@ -291,5 +291,9 @@ "continue": "Continuar", "loading_editor": "Carregando editor...", "editor_fallback_warning": "O editor de código falhou ao carregar. Usando um campo de texto simples em vez disso.", - "return_to_settings": "Voltar às Configurações" + "return_to_settings": "Voltar às Configurações", + "import_account_backup": "Importar backup da conta", + "account_auto_generated": "Esta conta foi gerada automaticamente pelo 5chan, sem necessidade de cadastro.", + "download_backup": "Baixar backup", + "boards_you_moderate_nav": "Quadros que você modera" } diff --git a/public/translations/ro/default.json b/public/translations/ro/default.json index da274437..1b2a2871 100644 --- a/public/translations/ro/default.json +++ b/public/translations/ro/default.json @@ -291,5 +291,9 @@ "continue": "Continuă", "loading_editor": "Se încarcă editorul...", "editor_fallback_warning": "Editorul de cod nu s-a putut încărca. Se folosește un câmp de text simplu în loc.", - "return_to_settings": "Înapoi la Setări" + "return_to_settings": "Înapoi la Setări", + "import_account_backup": "Importă backup cont", + "account_auto_generated": "Acest cont a fost generat automat de 5chan, nu este nevoie de înregistrare.", + "download_backup": "Descarcă backup", + "boards_you_moderate_nav": "Forumuri pe care le moderezi" } diff --git a/public/translations/ru/default.json b/public/translations/ru/default.json index d68a2dd3..07400d6d 100644 --- a/public/translations/ru/default.json +++ b/public/translations/ru/default.json @@ -291,5 +291,9 @@ "continue": "Продолжить", "loading_editor": "Загрузка редактора...", "editor_fallback_warning": "Не удалось загрузить редактор кода. Используется вместо него обычное текстовое поле.", - "return_to_settings": "Вернуться к настройкам" + "return_to_settings": "Вернуться к настройкам", + "import_account_backup": "Импорт резервной копии аккаунта", + "account_auto_generated": "Этот аккаунт был автоматически создан 5chan, регистрация не требуется.", + "download_backup": "Скачать резервную копию", + "boards_you_moderate_nav": "Доски, которые вы модерируете" } diff --git a/public/translations/sq/default.json b/public/translations/sq/default.json index 18ca6f4b..191410b1 100644 --- a/public/translations/sq/default.json +++ b/public/translations/sq/default.json @@ -291,5 +291,9 @@ "continue": "Vazhdo", "loading_editor": "Duke ngarkuar redaktuesin...", "editor_fallback_warning": "Redaktori i kodit dështoi të ngarkohet. Duke përdorur një fushë teksti të thjeshtë në vend.", - "return_to_settings": "Kthehu te Cilësimet" + "return_to_settings": "Kthehu te Cilësimet", + "import_account_backup": "Importo backup të llogarisë", + "account_auto_generated": "Ky llogari u krijua automatikisht nga 5chan, nuk kërkohet regjistrim.", + "download_backup": "Shkarko kopjen e rezervë", + "boards_you_moderate_nav": "Bordet që moderoni" } diff --git a/public/translations/sv/default.json b/public/translations/sv/default.json index ce415876..2a69c310 100644 --- a/public/translations/sv/default.json +++ b/public/translations/sv/default.json @@ -291,5 +291,9 @@ "continue": "Fortsätt", "loading_editor": "Laddar editor...", "editor_fallback_warning": "Kodredigeraren kunde inte laddas. Använder ett enkelt textfält istället.", - "return_to_settings": "Tillbaka till inställningar" + "return_to_settings": "Tillbaka till inställningar", + "import_account_backup": "Importera kontobackup", + "account_auto_generated": "Detta konto skapades automatiskt av 5chan, ingen registrering krävs.", + "download_backup": "Ladda ner säkerhetskopia", + "boards_you_moderate_nav": "Boards du modererar" } diff --git a/public/translations/te/default.json b/public/translations/te/default.json index 5521d003..a006ec84 100644 --- a/public/translations/te/default.json +++ b/public/translations/te/default.json @@ -291,5 +291,9 @@ "continue": "కొనసాగించు", "loading_editor": "సంపాదకాన్ని లోడ్ చేస్తోంది...", "editor_fallback_warning": "కోడ్ ఎడిటర్ లోడ్ చేయడం విఫలమైంది. బదులుగా సాధారణ టెక్స్ట్ ఏరియా ఉపయోగిస్తున్నారు.", - "return_to_settings": "సెట్టింగ్‌లకు తిరిగి వెళ్ళండి" + "return_to_settings": "సెట్టింగ్‌లకు తిరిగి వెళ్ళండి", + "import_account_backup": "ఖాతా బ్యాకప్ దిగుమతి చేయండి", + "account_auto_generated": "ఈ ఖాతా 5chan చేత స్వయంచాలకంగా సృష్టించబడింది, సైన్ అప్ అవసరం లేదు.", + "download_backup": "బ్యాకప్ డౌన్‌లోడ్ చేయండి", + "boards_you_moderate_nav": "మీరు మోడరేట్ చేసే బోర్డ్లు" } diff --git a/public/translations/th/default.json b/public/translations/th/default.json index 0e147fc2..fffad5d8 100644 --- a/public/translations/th/default.json +++ b/public/translations/th/default.json @@ -291,5 +291,9 @@ "continue": "ดำเนินการต่อ", "loading_editor": "กำลังโหลดตัวแก้ไข...", "editor_fallback_warning": "โหลดตัวแก้ไขโค้ดไม่สำเร็จ กำลังใช้ช่องข้อความธรรมดาแทน", - "return_to_settings": "กลับไปที่การตั้งค่า" + "return_to_settings": "กลับไปที่การตั้งค่า", + "import_account_backup": "นำเข้ารายการสำรองของบัญชี", + "account_auto_generated": "บัญชีนี้ถูกสร้างโดยอัตโนมัติโดย 5chan ไม่ต้องสมัครสมาชิก", + "download_backup": "ดาวน์โหลดสำรอง", + "boards_you_moderate_nav": "บอร์ดที่คุณดูแล" } diff --git a/public/translations/tr/default.json b/public/translations/tr/default.json index 999f2f07..a66bc235 100644 --- a/public/translations/tr/default.json +++ b/public/translations/tr/default.json @@ -291,5 +291,9 @@ "continue": "Devam", "loading_editor": "Düzenleyici yükleniyor...", "editor_fallback_warning": "Kod düzenleyici yüklenemedi. Bunun yerine düz bir metin alanı kullanılıyor.", - "return_to_settings": "Ayarlara Dön" + "return_to_settings": "Ayarlara Dön", + "import_account_backup": "Hesap yedeğini içe aktar", + "account_auto_generated": "Bu hesap 5chan tarafından otomatik olarak oluşturuldu, kayıt gerekmez.", + "download_backup": "Yedek indir", + "boards_you_moderate_nav": "Yönettiğiniz panolar" } diff --git a/public/translations/uk/default.json b/public/translations/uk/default.json index e0fea9e7..3f3e9519 100644 --- a/public/translations/uk/default.json +++ b/public/translations/uk/default.json @@ -291,5 +291,9 @@ "continue": "Продовжити", "loading_editor": "Завантаження редактора...", "editor_fallback_warning": "Не вдалося завантажити редактор коду. Використовується прості текстове поле замість нього.", - "return_to_settings": "Повернутися до налаштувань" + "return_to_settings": "Повернутися до налаштувань", + "import_account_backup": "Імпорт резервної копії облікового запису", + "account_auto_generated": "Цей обліковий запис було автоматично створено 5chan, реєстрація не потрібна.", + "download_backup": "Завантажити резервну копію", + "boards_you_moderate_nav": "Дошки, які ви модеруєте" } diff --git a/public/translations/ur/default.json b/public/translations/ur/default.json index 25522f68..4d90f8b4 100644 --- a/public/translations/ur/default.json +++ b/public/translations/ur/default.json @@ -291,5 +291,9 @@ "continue": "جاری رکھیں", "loading_editor": "ایڈیٹر لوڈ ہو رہا ہے...", "editor_fallback_warning": "کوڈ ایڈیٹر لوڈ نہیں ہو سکا۔ اس کے بجائے ایک سادہ ٹیکسٹ ایریا استعمال کیا جا رہا ہے۔", - "return_to_settings": "ترتیبات میں واپس جائیں" + "return_to_settings": "ترتیبات میں واپس جائیں", + "import_account_backup": "اکاؤنٹ بیک اپ درآمد کریں", + "account_auto_generated": "یہ اکاؤنٹ 5chan نے خودکار طور پر بنایا ہے، سائن اپ کی ضرورت نہیں۔", + "download_backup": "بیک اپ ڈاؤن لوڈ کریں", + "boards_you_moderate_nav": "بورڈز جنہیں آپ ماڈریٹ کرتے ہیں" } diff --git a/public/translations/vi/default.json b/public/translations/vi/default.json index 3b6f1eec..ee81c8da 100644 --- a/public/translations/vi/default.json +++ b/public/translations/vi/default.json @@ -291,5 +291,9 @@ "continue": "Tiếp tục", "loading_editor": "Đang tải trình soạn thảo...", "editor_fallback_warning": "Không thể tải trình soạn thảo mã. Đang sử dụng ô văn bản thuần thay thế.", - "return_to_settings": "Quay lại Cài đặt" + "return_to_settings": "Quay lại Cài đặt", + "import_account_backup": "Nhập bản sao lưu tài khoản", + "account_auto_generated": "Tài khoản này được tạo tự động bởi 5chan, không cần đăng ký.", + "download_backup": "Tải xuống bản sao lưu", + "boards_you_moderate_nav": "Các bảng bạn điều hành" } diff --git a/public/translations/zh/default.json b/public/translations/zh/default.json index 9ff54d24..aadad4b3 100644 --- a/public/translations/zh/default.json +++ b/public/translations/zh/default.json @@ -291,5 +291,9 @@ "continue": "继续", "loading_editor": "正在加载编辑器...", "editor_fallback_warning": "代码编辑器加载失败。改用纯文本输入框。", - "return_to_settings": "返回设置" + "return_to_settings": "返回设置", + "import_account_backup": "导入账户备份", + "account_auto_generated": "此账户由 5chan 自动生成,无需注册。", + "download_backup": "下载备份", + "boards_you_moderate_nav": "您管理的板块" } diff --git a/src/components/board-buttons/board-buttons.tsx b/src/components/board-buttons/board-buttons.tsx index de58c127..b5bae65e 100644 --- a/src/components/board-buttons/board-buttons.tsx +++ b/src/components/board-buttons/board-buttons.tsx @@ -465,7 +465,7 @@ export const PostPageStats = () => { const subplebbitAddress = resolvedAddress || accountComment?.subplebbitAddress; const comment = useComment({ commentCid }); - const postCid = comment?.parentCid ?? commentCid; + const postCid = comment?.postCid ?? commentCid; const post = useComment({ commentCid: postCid }); const { closed, pinned, replyCount } = post || {}; diff --git a/src/components/board-pagination/board-pagination.module.css b/src/components/board-pagination/board-pagination.module.css index c1b35e10..625a8a0f 100644 --- a/src/components/board-pagination/board-pagination.module.css +++ b/src/components/board-pagination/board-pagination.module.css @@ -46,6 +46,8 @@ overflow: hidden; list-style: none; background-color: var(--pagelist-background-color); + border-right: 1px solid var(--pagelist-item-border-color); + border-bottom: 1px solid var(--pagelist-item-border-color); } .footerPageItem { @@ -97,7 +99,7 @@ background: transparent; list-style: none; overflow: hidden; - color: var(--boardsbar-desktop-link-text-color); + color: var(--button-desktop-text-color); } .footerPageLink { @@ -108,19 +110,24 @@ padding: 0; border: none; background: transparent; - color: var(--boardsbar-desktop-link-text-color); + color: var(--button-desktop-text-color); text-transform: capitalize; cursor: pointer; - text-decoration: var(--boardsbar-desktop-link-text-decoration); + text-decoration: var(--button-text-decoration); } .footerPageLink:hover { - color: var(--boardsbar-desktop-link-text-color-hover); - text-decoration: var(--boardsbar-desktop-link-text-decoration-hover); + color: var(--button-desktop-text-color-hover); +} + +.pagelistSeparatorLink { + composes: footerPageLink; + border-left: 1px solid var(--pagelist-item-border-color); + padding-left: 12px; + margin-left: 7px; } .footerPageCurrent { + composes: footerPageLink; font-weight: bold; - color: var(--boardsbar-desktop-link-text-color-hover); - text-decoration: var(--boardsbar-desktop-link-text-decoration); } diff --git a/src/components/board-pagination/board-pagination.tsx b/src/components/board-pagination/board-pagination.tsx index 8224d7ef..25ce137b 100644 --- a/src/components/board-pagination/board-pagination.tsx +++ b/src/components/board-pagination/board-pagination.tsx @@ -61,23 +61,15 @@ const BoardPagination = ({ basePath, currentPage, totalPages, footerStyle = fals ] )} - {pageNumbers.map((page) => - page === currentPage ? ( - - [ - {page} - ] - - ) : ( - - [ - - {page} - - ] - - ), - )} + {pageNumbers.map((page) => ( + + [ + + {page} + + ] + + ))} {currentPage < totalPages ? ( {' '} - -
- {t('stored_locally', { - location: window.electronApi?.isElectron ? 'this desktop app' : isAndroid ? 'this mobile app' : window.location.hostname, - interpolation: { escapeValue: false }, - })} + {' '} + {' '} + +
+ {t('account_auto_generated')} {t('stored_locally', { location: host, interpolation: { escapeValue: false } })}
- + diff --git a/src/hooks/use-popular-posts.ts b/src/hooks/use-popular-posts.ts index 534e2cf4..63a21902 100644 --- a/src/hooks/use-popular-posts.ts +++ b/src/hooks/use-popular-posts.ts @@ -2,77 +2,92 @@ import { useMemo, useRef } from 'react'; import { Comment, Subplebbit } from '@plebbit/plebbit-react-hooks'; import { getCommentMediaInfo, getHasThumbnail } from '../lib/utils/media-utils'; +const MAX_POSTS = 8; +const MAX_PER_SUB = 3; + /** - * Extracts popular posts from subplebbits. - * Uses memoization to avoid recomputing when only updatingState changes. + * Ranked by replyCount instead of a static threshold so the box + * adapts to both low- and high-activity periods. + * + * Grow-only commit: once a post enters the grid it never shifts or + * disappears — new posts fill remaining slots until the cap is reached. */ const usePopularPosts = (subplebbits: Subplebbit[]) => { - // Track the previous CID list to detect actual content changes vs transient state changes - const prevCidsRef = useRef(''); + const committedRef = useRef<{ posts: Comment[]; cids: Set }>({ + posts: [], + cids: new Set(), + }); + const prevInputKeyRef = useRef(''); + + // Reset committed when the board set changes (e.g. NSFW filter toggle) + const inputKey = subplebbits + .map((s) => s?.address) + .filter(Boolean) + .sort() + .join(','); + if (prevInputKeyRef.current !== inputKey) { + prevInputKeyRef.current = inputKey; + committedRef.current = { posts: [], cids: new Set() }; + } + + const candidates = useMemo(() => { + if (committedRef.current.posts.length >= MAX_POSTS) return []; - const { popularPosts, error } = useMemo(() => { try { - const uniqueLinks: Set = new Set(); + const uniqueLinks = new Set(); const allPosts: Comment[] = []; - // Base quota on boards that currently have loaded hot comments. - // Using total directory count can underfill the list when many boards are empty/unavailable. - const loadedSubplebbitsCount = subplebbits.filter((sub) => sub?.posts?.pages?.hot?.comments).length; - const postsPerSub = [0, 8, 4, 3, 2, 2, 2, 2, 1][Math.min(loadedSubplebbitsCount, 8)]; + for (const sub of subplebbits) { + if (!sub?.posts?.pages?.hot?.comments) continue; - subplebbits.forEach((subplebbit: any) => { - let subplebbitPosts: Comment[] = []; + const subPosts: Comment[] = []; + for (const post of Object.values(sub.posts.pages.hot.comments as Comment)) { + const { deleted, link, linkHeight, linkWidth, locked, pinned, removed, thumbnailUrl } = post; - if (subplebbit?.posts?.pages?.hot?.comments) { - const rawPosts = Object.values(subplebbit.posts.pages.hot.comments as Comment); - for (const post of rawPosts) { - const { deleted, link, linkHeight, linkWidth, locked, pinned, removed, replyCount, thumbnailUrl } = post; + try { + const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight); + const hasThumbnail = getHasThumbnail(commentMediaInfo, link); - try { - const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight); - const hasThumbnail = getHasThumbnail(commentMediaInfo, link); - - if (hasThumbnail && replyCount > 1 && !deleted && !removed && !locked && !pinned && !uniqueLinks.has(link)) { - subplebbitPosts.push(post); - uniqueLinks.add(link); - } - } catch (err) { - console.error('Error processing post:', err); + if (hasThumbnail && !deleted && !removed && !locked && !pinned && !uniqueLinks.has(link)) { + subPosts.push(post); + uniqueLinks.add(link); } + } catch { + // skip posts with malformed media URLs } - - subplebbitPosts.sort((a: any, b: any) => b.timestamp - a.timestamp); - const selectedPosts = subplebbitPosts.slice(0, postsPerSub); - allPosts.push(...selectedPosts); } + + subPosts.sort((a, b) => (b.replyCount ?? 0) - (a.replyCount ?? 0)); + allPosts.push(...subPosts.slice(0, MAX_PER_SUB)); + } + + // Primary: most replies first. Tiebreaker: newest first. + allPosts.sort((a, b) => { + const diff = (b.replyCount ?? 0) - (a.replyCount ?? 0); + return diff !== 0 ? diff : (b.timestamp ?? 0) - (a.timestamp ?? 0); }); - const sortedPosts = allPosts.sort((a: any, b: any) => b.timestamp - a.timestamp).slice(0, 8); - - return { popularPosts: sortedPosts, error: null }; + return allPosts; } catch (err) { console.error('Error in usePopularPosts:', err); - return { popularPosts: [], error: 'Failed to fetch popular posts' }; + return []; } }, [subplebbits]); - // Create stable reference: only update if the post content actually changes - // Build a key from relevant mutable fields, not just CIDs - const currentKey = popularPosts.map((p) => `${p.cid}:${p.replyCount}:${p.timestamp}:${p.locked}:${p.pinned}`).join(','); - const stablePostsRef = useRef(popularPosts); - const keyChanged = currentKey !== prevCidsRef.current; - - if (keyChanged) { - prevCidsRef.current = currentKey; - stablePostsRef.current = popularPosts; + // Grow-only: committed posts keep their position, new ones fill empty slots + const { posts, cids } = committedRef.current; + for (const post of candidates) { + if (posts.length >= MAX_POSTS) break; + if (post.cid && !cids.has(post.cid)) { + posts.push(post); + cids.add(post.cid); + } } - // Derive loading state from subplebbit states rather than post count - // A subplebbit is still loading if it has no posts pages yet and isn't in a terminal state const hasLoadedData = subplebbits.some((sub) => sub?.posts?.pages?.hot?.comments); const isLoading = subplebbits.length > 0 && !hasLoadedData; - return { popularPosts: stablePostsRef.current, isLoading, error }; + return { popularPosts: posts, isLoading, error: null as string | null }; }; export default usePopularPosts; diff --git a/src/index.css b/src/index.css index 97e46b19..0d53e141 100644 --- a/src/index.css +++ b/src/index.css @@ -82,7 +82,8 @@ hr { cursor: pointer; } - .highlight { + .highlight, + .scroll-highlight { background: var(--reply-highlight-background-color) !important; } @@ -104,7 +105,8 @@ hr { cursor: pointer; } - .highlight { + .highlight, + .scroll-highlight { background: var(--reply-highlight-background-color) !important; border: var(--reply-highlight-border) !important; border-left: var(--reply-highlight-border-left, revert) !important; diff --git a/src/themes.css b/src/themes.css index 713bd5cd..fbba8575 100644 --- a/src/themes.css +++ b/src/themes.css @@ -58,7 +58,7 @@ --hr-height: 0px; /* pagelist */ - --pagelist-background-color: #ead6ca; + --pagelist-background-color: #F0E0D6; --pagelist-item-background-color: #f0e0d6; --pagelist-item-border-color: #d9bfb7; --pagelist-item-text-color: #800; @@ -303,7 +303,7 @@ --footer-item-background-color-desktop: #eef2ff; /* pagelist */ - --pagelist-background-color: #c9cde8; + --pagelist-background-color: #D6DAF0; --pagelist-item-background-color: #d6daf0; --pagelist-item-border-color: #b7c5d9; --pagelist-item-text-color: #89a; diff --git a/src/views/account-data-editor/account-data-editor.tsx b/src/views/account-data-editor/account-data-editor.tsx index 8f61b806..b753b90d 100644 --- a/src/views/account-data-editor/account-data-editor.tsx +++ b/src/views/account-data-editor/account-data-editor.tsx @@ -8,8 +8,11 @@ import styles from './account-data-editor.module.css'; const DEFAULT_RETURN_TO = '/subs/settings#account-settings'; const loadAce = async () => { - const [aceModule] = await Promise.all([import('react-ace'), import('ace-builds/src-noconflict/mode-json'), import('ace-builds/src-noconflict/theme-monokai')]); - return aceModule.default; + const aceModule = await import('react-ace'); + await Promise.all([import('ace-builds/src-noconflict/mode-json'), import('ace-builds/src-noconflict/theme-monokai')]); + // Vite CJS interop can double-wrap the default export + const mod = aceModule.default; + return typeof mod === 'function' ? mod : (mod as unknown as { default: typeof mod }).default; }; const AccountDataEditor = () => { @@ -103,7 +106,7 @@ const AccountDataEditor = () => {
)} - post?.cid || `post-${index}`} - itemContent={(index, post) => } - useWindowScroll={true} - components={footerComponents} - endReached={effectiveInfiniteScroll && hasMore ? loadMore : undefined} - ref={virtuosoRef} - restoreStateFrom={lastVirtuosoState} - initialScrollTop={lastVirtuosoState?.scrollTop} - /> + {effectiveInfiniteScroll ? ( + post?.cid || `post-${index}`} + itemContent={(index, post) => } + useWindowScroll={true} + components={footerComponents} + endReached={hasMore ? loadMore : undefined} + ref={virtuosoRef} + restoreStateFrom={lastVirtuosoState} + initialScrollTop={lastVirtuosoState?.scrollTop} + /> + ) : ( + <> + {displayFeed.map((post, index) => ( + + ))} + + + )} ); diff --git a/src/views/catalog/catalog.tsx b/src/views/catalog/catalog.tsx index 9aacca7b..ca429838 100644 --- a/src/views/catalog/catalog.tsx +++ b/src/views/catalog/catalog.tsx @@ -12,7 +12,6 @@ import { useFeedStateString } from '../../hooks/use-state-string'; import useWindowWidth from '../../hooks/use-window-width'; import useCatalogStyleStore from '../../stores/use-catalog-style-store'; import useFeedResetStore from '../../stores/use-feed-reset-store'; -import useFeedViewSettingsStore from '../../stores/use-feed-view-settings-store'; import useSortingStore from '../../stores/use-sorting-store'; import useCatalogFiltersStore from '../../stores/use-catalog-filters-store'; import { getSubplebbitAddress, isDirectoryBoard, normalizeMultiboardFeedPath } from '../../lib/utils/route-utils'; @@ -204,9 +203,9 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, const isInSubscriptionsView = viewType ? viewType === 'subs' : false; const isInModView = viewType ? viewType === 'mod' : false; - const enableInfiniteScroll = useFeedViewSettingsStore((state) => state.enableInfiniteScroll); - const isForcedInfiniteScroll = isInAllView || isInSubscriptionsView || isInModView; - const effectiveInfiniteScroll = enableInfiniteScroll || isForcedInfiniteScroll; + const isMultiboard = isInAllView || isInSubscriptionsView || isInModView; + // Single-board catalogs always cap at maxGuiPages (no infinite scroll beyond the board's page limit) + const effectiveInfiniteScroll = isMultiboard; const directories = useDirectories(); const resolvedAddressFromUrl = useResolvedSubplebbitAddress(); @@ -241,7 +240,7 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, const postsPerPage = columnCount <= 2 ? 10 : columnCount === 3 ? 15 : columnCount === 4 ? 20 : 25; const community = useDirectoryByAddress(isInAllView || isInSubscriptionsView || isInModView ? undefined : subplebbitAddress); - const { guiPostsPerPage: boardPostsPerPage, maxGuiPages, paginationFeedPostsPerPage, infiniteFeedPostsPerPage } = useBoardFeedPageSize(community); + const { guiPostsPerPage: boardPostsPerPage, maxGuiPages, paginationFeedPostsPerPage } = useBoardFeedPageSize(community); // Canonical redirect for multiboard catalog paths with numeric page segment (e.g. /all/catalog/1w/5 -> /all/catalog/1w) useEffect(() => { @@ -269,35 +268,13 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, }, [subplebbitAddress]); const feedOptions = useMemo(() => { - const catalogPostsPerPage = - isInAllView || isInSubscriptionsView || isInModView - ? effectiveInfiniteScroll - ? 10 - : 100 - : effectiveInfiniteScroll - ? infiniteFeedPostsPerPage - : paginationFeedPostsPerPage; - return { subplebbitAddresses, sortType: feedSortType, - postsPerPage: catalogPostsPerPage, + postsPerPage: isMultiboard ? 10 : paginationFeedPostsPerPage, filter: createCombinedFilter(filterItems, searchText, subplebbitAddress || 'all', handleFilterMatch), }; - }, [ - subplebbitAddresses, - feedSortType, - isInAllView, - isInSubscriptionsView, - isInModView, - effectiveInfiniteScroll, - infiniteFeedPostsPerPage, - paginationFeedPostsPerPage, - filterItems, - searchText, - subplebbitAddress, - handleFilterMatch, - ]); + }, [subplebbitAddresses, feedSortType, isMultiboard, paginationFeedPostsPerPage, filterItems, searchText, subplebbitAddress, handleFilterMatch]); const { feed, hasMore, loadMore, reset } = useFeed(feedOptions); const { accountComments } = useAccountComments(); diff --git a/src/views/home/boards-list/boards-list.tsx b/src/views/home/boards-list/boards-list.tsx index 1972cad7..1410a8eb 100644 --- a/src/views/home/boards-list/boards-list.tsx +++ b/src/views/home/boards-list/boards-list.tsx @@ -1,5 +1,6 @@ import { Link, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; +import useAccountsStore from '@plebbit/plebbit-react-hooks/dist/stores/accounts'; import { useDirectoriesState, useDirectories, DirectoryCommunity } from '../../../hooks/use-directories'; import { getBoardPath } from '../../../lib/utils/route-utils'; import useDisclaimerModalStore from '../../../stores/use-disclaimer-modal-store'; @@ -63,6 +64,15 @@ const BoardsList = ({ multisub }: { multisub: DirectoryCommunity[] }) => { const { useCatalogLinks, boardFilter } = useBoardsFilterStore(); const directories = useDirectories(); + const accountSubplebbitAddresses = useAccountsStore( + (state) => { + const activeAccountId = state.activeAccountId; + const activeAccount = activeAccountId ? state.accounts[activeAccountId] : undefined; + return Object.keys(activeAccount?.subplebbits || {}); + }, + (prev, next) => prev.length === next.length && prev.every((val, idx) => val === next[idx]), + ); + const handleLinkClick = (e: React.MouseEvent, address: string) => { e.preventDefault(); const boardPath = getBoardPath(address, directories); @@ -595,6 +605,11 @@ const BoardsList = ({ multisub }: { multisub: DirectoryCommunity[] }) => {
  • Subscriptions
  • + {accountSubplebbitAddresses.length > 0 && ( +
  • + {t('boards_you_moderate_nav')} +
  • + )} )} diff --git a/src/views/home/box-modal/box-modal.tsx b/src/views/home/box-modal/box-modal.tsx index 609770d1..92ad4e10 100644 --- a/src/views/home/box-modal/box-modal.tsx +++ b/src/views/home/box-modal/box-modal.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import useHomeFiltersStore from '../../../stores/use-popular-threads-options-store'; import styles from '../home.module.css'; @@ -11,21 +11,37 @@ const BoxModal = () => { const { showWorksafeContentOnly, setShowWorksafeContentOnly, showNsfwContentOnly, setShowNsfwContentOnly } = useHomeFiltersStore(); - const handleClickOutside = useCallback( - (event: MouseEvent) => { + useEffect(() => { + if (!showFilterModal) return; + const handleClickOutside = (event: MouseEvent) => { if (modalRef.current && !modalRef.current.contains(event.target as Node) && buttonRef.current && !buttonRef.current.contains(event.target as Node)) { setShowFilterModal(false); } - }, - [modalRef, buttonRef, setShowFilterModal], - ); - - useEffect(() => { - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); }; - }, [handleClickOutside]); + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, [showFilterModal]); + + const selectFilter = (filter: 'worksafe' | 'nsfw' | 'all') => { + if (filter === 'worksafe') { + if (showNsfwContentOnly) setShowNsfwContentOnly(false); + setShowWorksafeContentOnly(!showWorksafeContentOnly); + } else if (filter === 'nsfw') { + if (showWorksafeContentOnly) setShowWorksafeContentOnly(false); + setShowNsfwContentOnly(!showNsfwContentOnly); + } else { + setShowWorksafeContentOnly(false); + setShowNsfwContentOnly(false); + } + setShowFilterModal(false); + }; + + const handleKey = (e: React.KeyboardEvent, filter: 'worksafe' | 'nsfw' | 'all') => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + selectFilter(filter); + } + }; return ( <> @@ -46,70 +62,29 @@ const BoxModal = () => { {showFilterModal && (
    { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - if (showNsfwContentOnly) { - setShowNsfwContentOnly(false); - } - setShowWorksafeContentOnly(!showWorksafeContentOnly); - setShowFilterModal(false); - } - }} - onClick={() => { - if (showNsfwContentOnly) { - setShowNsfwContentOnly(false); - } - setShowWorksafeContentOnly(!showWorksafeContentOnly); - setShowFilterModal(false); - }} + onKeyDown={(e) => handleKey(e, 'worksafe')} + onClick={() => selectFilter('worksafe')} > {t('show_worksafe_content_only')}
    { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - if (showWorksafeContentOnly) { - setShowWorksafeContentOnly(false); - } - setShowNsfwContentOnly(!showNsfwContentOnly); - setShowFilterModal(false); - } - }} - onClick={() => { - if (showWorksafeContentOnly) { - setShowWorksafeContentOnly(false); - } - setShowNsfwContentOnly(!showNsfwContentOnly); - setShowFilterModal(false); - }} + onKeyDown={(e) => handleKey(e, 'nsfw')} + onClick={() => selectFilter('nsfw')} > {t('show_nsfw_content_only')}
    { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - setShowWorksafeContentOnly(false); - setShowNsfwContentOnly(false); - setShowFilterModal(false); - } - }} - onClick={() => { - setShowWorksafeContentOnly(false); - setShowNsfwContentOnly(false); - setShowFilterModal(false); - }} + onKeyDown={(e) => handleKey(e, 'all')} + onClick={() => selectFilter('all')} > {t('show_all_content')}
    diff --git a/src/views/home/popular-threads-box/popular-threads-box.tsx b/src/views/home/popular-threads-box/popular-threads-box.tsx index 0e176a05..7cb215ff 100644 --- a/src/views/home/popular-threads-box/popular-threads-box.tsx +++ b/src/views/home/popular-threads-box/popular-threads-box.tsx @@ -15,7 +15,8 @@ import { removeMarkdown } from '../../../lib/utils/post-utils'; interface PopularThreadProps { post: Comment; - directories: DirectoryCommunity[]; + boardTitle: string; + boardPath: string; } export const ContentPreview = ({ content, maxLength = 99 }: { content: string; maxLength?: number }) => { @@ -25,23 +26,17 @@ export const ContentPreview = ({ content, maxLength = 99 }: { content: string; m return truncatedText; }; -// Memoize to prevent rerenders when parent rerenders due to updatingState const PopularThreadCard = memo( - ({ post, directories }: PopularThreadProps) => { - const { cid, content, link, linkHeight, linkWidth, subplebbitAddress, thumbnailUrl, title } = post || {}; + ({ post, boardTitle, boardPath }: PopularThreadProps) => { + const { cid, content, link, linkHeight, linkWidth, thumbnailUrl, title } = post || {}; const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight); - // Find the matching DirectoryCommunity entry and get its title - const directoriesEntry = directories.find((ms) => ms?.address === subplebbitAddress); - const boardTitle = directoriesEntry?.title?.replace(/^\/[^/]+\/\s*-\s*/, '') || ''; - const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, directories) : ''; - return (
    {boardTitle}
    - +
    @@ -56,35 +51,31 @@ const PopularThreadCard = memo(
    ); }, - // Custom equality: rerender if post.cid or directories entry title changes - (prevProps, nextProps) => { - if (prevProps.post?.cid !== nextProps.post?.cid) return false; - // Compare the relevant directories entry for this post's subplebbitAddress - const prevEntry = prevProps.directories.find((ms) => ms?.address === prevProps.post?.subplebbitAddress); - const nextEntry = nextProps.directories.find((ms) => ms?.address === nextProps.post?.subplebbitAddress); - return prevEntry?.title === nextEntry?.title; - }, + (prevProps, nextProps) => prevProps.post?.cid === nextProps.post?.cid && prevProps.boardTitle === nextProps.boardTitle, ); const PopularThreadsBox = ({ directories, subplebbits }: { directories: DirectoryCommunity[]; subplebbits: any }) => { const { t } = useTranslation(); const { showWorksafeContentOnly, showNsfwContentOnly } = usePopularThreadsOptionsStore(); + const directoryByAddress = useMemo(() => new Map(directories.map((d) => [d.address, d])), [directories]); + const filteredSubplebbits = useMemo(() => { if (showWorksafeContentOnly) { return subplebbits.filter((sub: Subplebbit) => { - const directoriesEntry = directories.find((ms) => ms?.address === sub?.address); - return directoriesEntry ? !directoriesEntry.nsfw : true; + const entry = directoryByAddress.get(sub?.address); + return entry ? !entry.nsfw : true; }); } if (showNsfwContentOnly) { return subplebbits.filter((sub: Subplebbit) => { - const directoriesEntry = directories.find((ms) => ms?.address === sub?.address); - return directoriesEntry ? directoriesEntry.nsfw : false; + const entry = directoryByAddress.get(sub?.address); + return entry ? entry.nsfw : false; }); } return subplebbits; - }, [subplebbits, showWorksafeContentOnly, showNsfwContentOnly, directories]); + }, [subplebbits, showWorksafeContentOnly, showNsfwContentOnly, directoryByAddress]); + const { popularPosts } = usePopularPosts(filteredSubplebbits); const isLoading = popularPosts.length === 0; @@ -98,7 +89,12 @@ const PopularThreadsBox = ({ directories, subplebbits }: { directories: Director {isLoading ? ( ) : ( - popularPosts.map((post: any) => ) + popularPosts.map((post: Comment) => { + const entry = directoryByAddress.get(post.subplebbitAddress); + const boardTitle = entry?.title?.replace(/^\/[^/]+\/\s*-\s*/, '') || ''; + const boardPath = post.subplebbitAddress ? getBoardPath(post.subplebbitAddress, directories) : ''; + return ; + }) )}
    diff --git a/yarn.lock b/yarn.lock index ebe83f19..ef506b9b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4581,6 +4581,78 @@ viem "2.45.0" zod "4.3.6" +"@plebbit/plebbit-js@https://github.com/plebbit/plebbit-js.git#fd7486d31ce71b29b6dc49c019278af8fb0b0d45": + version "0.0.11" + resolved "https://github.com/plebbit/plebbit-js.git#fd7486d31ce71b29b6dc49c019278af8fb0b0d45" + dependencies: + "@bonfida/spl-name-service" "3.0.16" + "@enhances/with-resolvers" "0.0.5" + "@helia/block-brokers" "5.0.13" + "@helia/delegated-routing-v1-http-api-client" "5.1.4" + "@helia/ipns" "9.1.5" + "@helia/unixfs" "6.0.4" + "@libp2p/crypto" "5.1.13" + "@libp2p/fetch" "4.0.9" + "@libp2p/gossipsub" "15.0.11" + "@libp2p/identify" "4.0.9" + "@libp2p/interfaces" "3.3.2" + "@libp2p/peer-id" "6.0.4" + "@libp2p/peer-id-factory" "4.2.4" + "@libp2p/websockets" "10.1.2" + "@mintpass/challenge" "1.1.11" + "@plebbit/plebbit-logger" "git+https://github.com/plebbit/plebbit-logger.git#355a96d7659ed820047980049dfa627d30d83a69" + "@plebbit/proper-lockfile" "git+https://github.com/plebbit/node-proper-lockfile.git#7fd6332117340c1d3d98dd0afee2d31cc06f72b8" + "@types/proper-lockfile" "4.1.2" + assert "2.1.0" + better-sqlite3 "12.4.1" + blockstore-core "6.1.1" + blockstore-fs "3.0.2" + blockstore-idb "3.0.1" + buffer "6.0.3" + captcha-canvas "3.3.4" + cbor "9.0.1" + debounce "1.2.1" + err-code "3.0.1" + ethers "6.16.0" + ext-name "5.0.0" + helia "6.0.14" + hpagent "1.2.0" + it-all "3.0.6" + jose "4.11.0" + js-sha256 "0.11.1" + js-sha512 "0.9.0" + kubo-rpc-client "6.0.2" + limiter-es6-compat "2.1.2" + localforage "1.10.0" + lodash.merge "4.6.2" + lru-cache "10.1.0" + open-graph-scraper "6.10.0" + p-limit "4.0.0" + p-timeout "6.1.2" + peer-id "0.16.0" + probe-image-size "7.2.3" + remeda "1.57.0" + retry "0.13.1" + rpc-websockets "9.3.1" + safe-stable-stringify "2.4.3" + sha1-uint8array "0.10.7" + skia-canvas "3.0.8" + tcp-port-used "1.0.2" + tiny-typed-emitter "2.1.0" + tinycache "1.1.2" + ts-custom-error "3.3.1" + typestub-ipfs-only-hash "4.0.0" + uuid "11.1.0" + viem "2.45.0" + zod "4.3.6" + +"@plebbit/plebbit-logger@git+https://github.com/plebbit/plebbit-logger.git#355a96d7659ed820047980049dfa627d30d83a69": + version "0.0.1" + uid "355a96d7659ed820047980049dfa627d30d83a69" + resolved "git+https://github.com/plebbit/plebbit-logger.git#355a96d7659ed820047980049dfa627d30d83a69" + dependencies: + debug "4.3.4" + "@plebbit/plebbit-logger@github:plebbit/plebbit-logger#355a96d7659ed820047980049dfa627d30d83a69": version "0.0.1" uid "355a96d7659ed820047980049dfa627d30d83a69" @@ -4610,6 +4682,30 @@ uuid "11.1.0" zustand "4.0.0" +"@plebbit/plebbit-react-hooks@https://github.com/plebbit/plebbit-react-hooks.git#a828bd598bcc392ed379136feaee0a7b1c8cd23f": + version "0.0.1" + resolved "https://github.com/plebbit/plebbit-react-hooks.git#a828bd598bcc392ed379136feaee0a7b1c8cd23f" + dependencies: + "@plebbit/plebbit-js" "https://github.com/plebbit/plebbit-js.git#fd7486d31ce71b29b6dc49c019278af8fb0b0d45" + "@plebbit/plebbit-logger" "https://github.com/plebbit/plebbit-logger.git" + assert "2.0.0" + ethers "5.6.9" + localforage "1.10.0" + lodash.isequal "4.5.0" + memoizee "0.4.15" + quick-lru "5.1.1" + uint8arrays "3.1.1" + uuid "11.1.0" + zustand "4.0.0" + +"@plebbit/proper-lockfile@git+https://github.com/plebbit/node-proper-lockfile.git#7fd6332117340c1d3d98dd0afee2d31cc06f72b8": + version "4.1.2" + resolved "git+https://github.com/plebbit/node-proper-lockfile.git#7fd6332117340c1d3d98dd0afee2d31cc06f72b8" + dependencies: + graceful-fs "^4.2.4" + retry "^0.12.0" + signal-exit "^3.0.2" + "@plebbit/proper-lockfile@github:plebbit/node-proper-lockfile#7fd6332117340c1d3d98dd0afee2d31cc06f72b8": version "4.1.2" resolved "https://codeload.github.com/plebbit/node-proper-lockfile/tar.gz/7fd6332117340c1d3d98dd0afee2d31cc06f72b8"