Merge pull request #376 from plebbit/development

Development
This commit is contained in:
Tom (plebeius.eth)
2024-05-31 16:53:39 +02:00
committed by GitHub
76 changed files with 1145 additions and 361 deletions
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "بيانات الحساب الكاملة", "full_account_data": "بيانات الحساب الكاملة",
"this_account": "هذا الحساب", "this_account": "هذا الحساب",
"delete": "حذف", "delete": "حذف",
"delete_account": "حذف الحساب" "delete_account": "حذف الحساب",
"file_type": "نوع الملف",
"version": "الإصدار",
"spoiler": "تحذير",
"interface": "واجهة",
"invalid_url": "رابط غير صالح",
"no_link_provided": "لم يتم توفير رابط",
"image_search": "البحث عن صورة",
"view_on": "عرض على",
"copy_link": "نسخ الرابط",
"hide_post": "إخفاء المنشور",
"hide_thread": "إخفاء الموضوع",
"unhide_post": "إظهار المنشور",
"unhide_thread": "إظهار الموضوع",
"search_image_on_google": "البحث عن صورة على جوجل",
"search_image_on_yandex": "البحث عن صورة على Yandex",
"search_image_on_saucenao": "البحث عن صورة على SauceNAO",
"view_on_client": "عرض على {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "সম্পূর্ণ অ্যাকাউন্ট ডেটা", "full_account_data": "সম্পূর্ণ অ্যাকাউন্ট ডেটা",
"this_account": "এই অ্যাকাউন্ট", "this_account": "এই অ্যাকাউন্ট",
"delete": "মুছে ফেলা", "delete": "মুছে ফেলা",
"delete_account": "অ্যাকাউন্ট মুছুন" "delete_account": "অ্যাকাউন্ট মুছুন",
"file_type": "ফাইলের ধরন",
"version": "সংস্করণ",
"spoiler": "স্পোয়েলার",
"interface": "ইন্টারফেস",
"invalid_url": "অবৈধ ইউআরএল",
"no_link_provided": "কোনো লিঙ্ক উপস্থিত নেই",
"image_search": "চিত্র অনুসন্ধান",
"view_on": "উপর দেখুন",
"copy_link": "লিংক অনুলিপি করুন",
"hide_post": "পোস্ট লুকাও",
"hide_thread": "থ্রেড লুকাও",
"unhide_post": "পোস্ট প্রদর্শন করুন",
"unhide_thread": "থ্রেড প্রদর্শন করুন",
"search_image_on_google": "গুগলে চিত্র অনুসন্ধান করুন",
"search_image_on_yandex": "Yandexে চিত্র অনুসন্ধান করুন",
"search_image_on_saucenao": "SauceNAO এ চিত্র অনুসন্ধান করুন",
"view_on_client": "{{client}} উপর দেখুন"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "plná účetní data", "full_account_data": "plná účetní data",
"this_account": "tento účet", "this_account": "tento účet",
"delete": "smazat", "delete": "smazat",
"delete_account": "smazat účet" "delete_account": "smazat účet",
"file_type": "Typ souboru",
"version": "Verze",
"spoiler": "spoiler",
"interface": "rozhraní",
"invalid_url": "Neplatná adresa URL",
"no_link_provided": "Není poskytnut žádný odkaz",
"image_search": "hledání obrázku",
"view_on": "zobrazit na",
"copy_link": "kopírovat odkaz",
"hide_post": "skrýt příspěvek",
"hide_thread": "skrýt vlákno",
"unhide_post": "zobrazit příspěvek",
"unhide_thread": "zobrazit vlákno",
"search_image_on_google": "hledat obrázek na Googlu",
"search_image_on_yandex": "hledat obrázek na Yandexu",
"search_image_on_saucenao": "hledat obrázek na SauceNAO",
"view_on_client": "Zobrazit na {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "fulde kontodata", "full_account_data": "fulde kontodata",
"this_account": "denne konto", "this_account": "denne konto",
"delete": "slet", "delete": "slet",
"delete_account": "slet konto" "delete_account": "slet konto",
"file_type": "Filtype",
"version": "Version",
"spoiler": "spoiler",
"interface": "interface",
"invalid_url": "Ugyldig webadresse",
"no_link_provided": "Ingen link angivet",
"image_search": "billedsøgning",
"view_on": "se på",
"copy_link": "kopier link",
"hide_post": "skjul post",
"hide_thread": "skjul tråd",
"unhide_post": "vis indlæg",
"unhide_thread": "vis tråd",
"search_image_on_google": "søg billede på Google",
"search_image_on_yandex": "søg billede på Yandex",
"search_image_on_saucenao": "søg billede på SauceNAO",
"view_on_client": "Se på {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "vollständige Kontodaten", "full_account_data": "vollständige Kontodaten",
"this_account": "dieses Konto", "this_account": "dieses Konto",
"delete": "löschen", "delete": "löschen",
"delete_account": "Konto löschen" "delete_account": "Konto löschen",
"file_type": "Dateityp",
"version": "Version",
"spoiler": "Spoiler",
"interface": "Schnittstelle",
"invalid_url": "Ungültige URL",
"no_link_provided": "Kein Link angegeben",
"image_search": "Bildersuche",
"view_on": "anzeigen auf",
"copy_link": "Link kopieren",
"hide_post": "Beitrag ausblenden",
"hide_thread": "Thread ausblenden",
"unhide_post": "Beitrag anzeigen",
"unhide_thread": "Thread anzeigen",
"search_image_on_google": "Bild auf Google suchen",
"search_image_on_yandex": "Bild auf Yandex suchen",
"search_image_on_saucenao": "Bild auf SauceNAO suchen",
"view_on_client": "Anzeigen auf {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "πλήρα δεδομένα λογαριασμού", "full_account_data": "πλήρα δεδομένα λογαριασμού",
"this_account": "αυτός ο λογαριασμός", "this_account": "αυτός ο λογαριασμός",
"delete": "διαγραφή", "delete": "διαγραφή",
"delete_account": "διαγραφή λογαριασμού" "delete_account": "διαγραφή λογαριασμού",
"file_type": "Τύπος αρχείου",
"version": "Έκδοση",
"spoiler": "spoiler",
"interface": "διεπαφή",
"invalid_url": "Μη έγκυρη διεύθυνση URL",
"no_link_provided": "Δεν παρέχεται σύνδεσμος",
"image_search": "αναζήτηση εικόνων",
"view_on": "προβολή σε",
"copy_link": "αντιγραφή συνδέσμου",
"hide_post": "απόκρυψη ανάρτησης",
"hide_thread": "απόκρυψη νήματος",
"unhide_post": "εμφάνιση ανάρτησης",
"unhide_thread": "εμφάνιση νήματος",
"search_image_on_google": "αναζήτηση εικόνας στο Google",
"search_image_on_yandex": "αναζήτηση εικόνας στο Yandex",
"search_image_on_saucenao": "αναζήτηση εικόνας στο SauceNAO",
"view_on_client": "Προβολή στο {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "full account data", "full_account_data": "full account data",
"this_account": "this account", "this_account": "this account",
"delete": "delete", "delete": "delete",
"delete_account": "delete account" "delete_account": "delete account",
"file_type": "File type",
"version": "Version",
"spoiler": "spoiler",
"interface": "interface",
"invalid_url": "Invalid URL",
"no_link_provided": "No link provided",
"image_search": "image search",
"view_on": "view on",
"copy_link": "Copy link",
"hide_post": "Hide post",
"hide_thread": "Hide thread",
"unhide_post": "Unhide post",
"unhide_thread": "Unhide thread",
"search_image_on_google": "Search image on Google",
"search_image_on_yandex": "Search image on Yandex",
"search_image_on_saucenao": "Search image on SauceNAO",
"view_on_client": "View on {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "datos completos de la cuenta", "full_account_data": "datos completos de la cuenta",
"this_account": "esta cuenta", "this_account": "esta cuenta",
"delete": "eliminar", "delete": "eliminar",
"delete_account": "eliminar cuenta" "delete_account": "eliminar cuenta",
"file_type": "Tipo de archivo",
"version": "Versión",
"spoiler": "spoiler",
"interface": "interfaz",
"invalid_url": "URL no válida",
"no_link_provided": "No se proporcionó ningún enlace",
"image_search": "búsqueda de imágenes",
"view_on": "ver en",
"copy_link": "Copiar enlace",
"hide_post": "Ocultar publicación",
"hide_thread": "Ocultar hilo",
"unhide_post": "Mostrar publicación",
"unhide_thread": "Mostrar hilo",
"search_image_on_google": "Buscar imagen en Google",
"search_image_on_yandex": "Buscar imagen en Yandex",
"search_image_on_saucenao": "Buscar imagen en SauceNAO",
"view_on_client": "Ver en {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "داده حساب کامل", "full_account_data": "داده حساب کامل",
"this_account": "این حساب", "this_account": "این حساب",
"delete": "حذف کنید", "delete": "حذف کنید",
"delete_account": "حذف حساب" "delete_account": "حذف حساب",
"file_type": "نوع فایل",
"version": "نسخه",
"spoiler": "اسپویلر",
"interface": "رابط",
"invalid_url": "آدرس اینترنتی نامعتبر است",
"no_link_provided": "لینکی ارائه نشده است",
"image_search": "جستجوی تصویر",
"view_on": "نمایش در",
"copy_link": "رونوشت لینک",
"hide_post": "پنهان کردن پست",
"hide_thread": "پنهان کردن موضوع",
"unhide_post": "نمایش پست",
"unhide_thread": "نمایش موضوع",
"search_image_on_google": "جستجوی تصویر در گوگل",
"search_image_on_yandex": "جستجوی تصویر در Yandex",
"search_image_on_saucenao": "جستجوی تصویر در SauceNAO",
"view_on_client": "مشاهده در {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "täydelliset tilitiedot", "full_account_data": "täydelliset tilitiedot",
"this_account": "tämä tili", "this_account": "tämä tili",
"delete": "poista", "delete": "poista",
"delete_account": "poista tili" "delete_account": "poista tili",
"file_type": "Tiedostotyyppi",
"version": "Versio",
"spoiler": "spoiler",
"interface": "liitäntä",
"invalid_url": "Virheellinen URL-osoite",
"no_link_provided": "Ei linkkiä annettu",
"image_search": "kuvahaku",
"view_on": "näytä",
"copy_link": "kopioi linkki",
"hide_post": "piilota viesti",
"hide_thread": "piilota keskustelu",
"unhide_post": "näytä viesti",
"unhide_thread": "näytä keskustelu",
"search_image_on_google": "etsi kuva Googlesta",
"search_image_on_yandex": "etsi kuva Yandexistä",
"search_image_on_saucenao": "etsi kuva SauceNAO: sta",
"view_on_client": "Näytä {{client}}: ssä"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "buong account data", "full_account_data": "buong account data",
"this_account": "ang account na ito", "this_account": "ang account na ito",
"delete": "burahin", "delete": "burahin",
"delete_account": "tanggalin ang account" "delete_account": "tanggalin ang account",
"file_type": "Uri ng file",
"version": "Bersyon",
"spoiler": "spoiler",
"interface": "interface",
"invalid_url": "Hindi wastong URL",
"no_link_provided": "Walang link na ibinigay",
"image_search": "paghahanap ng larawan",
"view_on": "tingnan sa",
"copy_link": "kopyahin ang link",
"hide_post": "itago ang post",
"hide_thread": "itago ang thread",
"unhide_post": "ipakita ang post",
"unhide_thread": "ipakita ang thread",
"search_image_on_google": "maghanap ng larawan sa Google",
"search_image_on_yandex": "maghanap ng larawan sa Yandex",
"search_image_on_saucenao": "maghanap ng larawan sa SauceNAO",
"view_on_client": "Tingnan sa {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "données complètes du compte", "full_account_data": "données complètes du compte",
"this_account": "ce compte", "this_account": "ce compte",
"delete": "supprimer", "delete": "supprimer",
"delete_account": "supprimer le compte" "delete_account": "supprimer le compte",
"file_type": "Type de fichier",
"version": "Version",
"spoiler": "spoiler",
"interface": "interface",
"invalid_url": "URL non valide",
"no_link_provided": "Aucun lien fourni",
"image_search": "recherche d'images",
"view_on": "voir sur",
"copy_link": "Copier le lien",
"hide_post": "Masquer la publication",
"hide_thread": "Masquer le fil",
"unhide_post": "Afficher la publication",
"unhide_thread": "Afficher le fil",
"search_image_on_google": "Rechercher une image sur Google",
"search_image_on_yandex": "Rechercher une image sur Yandex",
"search_image_on_saucenao": "Rechercher une image sur SauceNAO",
"view_on_client": "Voir sur {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "נתוני החשבון המלאים", "full_account_data": "נתוני החשבון המלאים",
"this_account": "החשבון הזה", "this_account": "החשבון הזה",
"delete": "מחק", "delete": "מחק",
"delete_account": "מחק חשבון" "delete_account": "מחק חשבון",
"file_type": "סוג הקובץ",
"version": "גרסה",
"spoiler": "ספוילר",
"interface": "ממשק",
"invalid_url": "כתובת אינטרנט לא תקינה",
"no_link_provided": "לא סופק קישור",
"image_search": "חיפוש תמונות",
"view_on": "צפה ב",
"copy_link": "העתק קישור",
"hide_post": "הסתר פוסט",
"hide_thread": "הסתר ת’רד",
"unhide_post": "הצג פוסט",
"unhide_thread": "הצג ת’רד",
"search_image_on_google": "חפש תמונה ב-Google",
"search_image_on_yandex": "חפש תמונה ב-Yandex",
"search_image_on_saucenao": "חפש תמונה ב- SauceNAO",
"view_on_client": "הצג ב {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "पूरे खाते का डेटा", "full_account_data": "पूरे खाते का डेटा",
"this_account": "इस खाता", "this_account": "इस खाता",
"delete": "हटाएँ", "delete": "हटाएँ",
"delete_account": "खाता हटाएं" "delete_account": "खाता हटाएं",
"file_type": "फ़ाइल का प्रकार",
"version": "संस्करण",
"spoiler": "स्पॉइलर",
"interface": "इंटरफेस",
"invalid_url": "अमान्य URL",
"no_link_provided": "कोई लिंक नहीं दिया गया है",
"image_search": "चित्र खोज",
"view_on": "पर देखें",
"copy_link": "लिंक कॉपी करें",
"hide_post": "पोस्ट छुपाएं",
"hide_thread": "धागा छुपाएं",
"unhide_post": "पोस्ट प्रदर्शित करें",
"unhide_thread": "धागा प्रदर्शित करें",
"search_image_on_google": "गूगल पर छवि खोजें",
"search_image_on_yandex": "Yandex पर छवि खोजें",
"search_image_on_saucenao": "SauceNAO पर छवि खोजें",
"view_on_client": "{{client}} पर देखें"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "teljes fiókadata", "full_account_data": "teljes fiókadata",
"this_account": "ez a fiók", "this_account": "ez a fiók",
"delete": "töröl", "delete": "töröl",
"delete_account": "fiók törlése" "delete_account": "fiók törlése",
"file_type": "Fájltípus",
"version": "Verzió",
"spoiler": "spoiler",
"interface": "felület",
"invalid_url": "Érvénytelen URL-cím",
"no_link_provided": "Nincs megadva link",
"image_search": "képkeresés",
"view_on": "nézet",
"copy_link": "link másolása",
"hide_post": "bejegyzés elrejtése",
"hide_thread": "szál elrejtése",
"unhide_post": "bejegyzés megjelenítése",
"unhide_thread": "szál megjelenítése",
"search_image_on_google": "keresés kép a Google-ben",
"search_image_on_yandex": "keresés kép a Yandex-ben",
"search_image_on_saucenao": "keresés kép a SauceNAO-n",
"view_on_client": "Megtekintés itt: {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "data akun lengkap", "full_account_data": "data akun lengkap",
"this_account": "akun ini", "this_account": "akun ini",
"delete": "hapus", "delete": "hapus",
"delete_account": "hapus akun" "delete_account": "hapus akun",
"file_type": "Tipe file",
"version": "Versi",
"spoiler": "spoiler",
"interface": "antarmuka",
"invalid_url": "URL tidak valid",
"no_link_provided": "Tidak ada tautan yang diberikan",
"image_search": "pencarian gambar",
"view_on": "lihat di",
"copy_link": "salin tautan",
"hide_post": "sembunyikan pos",
"hide_thread": "sembunyikan utas",
"unhide_post": "tampilkan pos",
"unhide_thread": "tampilkan utas",
"search_image_on_google": "cari gambar di Google",
"search_image_on_yandex": "cari gambar di Yandex",
"search_image_on_saucenao": "cari gambar di SauceNAO",
"view_on_client": "Lihat di {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "i dati completi dell'account", "full_account_data": "i dati completi dell'account",
"this_account": "questo account", "this_account": "questo account",
"delete": "elimina", "delete": "elimina",
"delete_account": "elimina account" "delete_account": "elimina account",
"file_type": "Tipo di file",
"version": "Versione",
"spoiler": "spoiler",
"interface": "interfaccia",
"invalid_url": "URL non valido",
"no_link_provided": "Nessun link fornito",
"image_search": "ricerca immagini",
"view_on": "Vedi su",
"copy_link": "Copia link",
"hide_post": "Nascondi post",
"hide_thread": "Nascondi thread",
"unhide_post": "Mostra post",
"unhide_thread": "Mostra thread",
"search_image_on_google": "Cerca immagine su Google",
"search_image_on_yandex": "Cerca immagine su Yandex",
"search_image_on_saucenao": "Cerca immagine su SauceNAO",
"view_on_client": "Vedi su {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "フルアカウントデータ", "full_account_data": "フルアカウントデータ",
"this_account": "このアカウント", "this_account": "このアカウント",
"delete": "削除", "delete": "削除",
"delete_account": "アカウントを削除" "delete_account": "アカウントを削除",
"file_type": "ファイルタイプ",
"version": "バージョン",
"spoiler": "ネタバレ",
"interface": "インターフェース",
"invalid_url": "無効なURL",
"no_link_provided": "リンクが指定されていません",
"image_search": "画像検索",
"view_on": "で表示",
"copy_link": "リンクをコピー",
"hide_post": "投稿を非表示",
"hide_thread": "スレッドを非表示",
"unhide_post": "投稿を表示",
"unhide_thread": "スレッドを表示",
"search_image_on_google": "Google で画像を検索",
"search_image_on_yandex": "Yandex で画像を検索",
"search_image_on_saucenao": "SauceNAO で画像を検索",
"view_on_client": "{{client}} で表示"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "전체 계정 데이터", "full_account_data": "전체 계정 데이터",
"this_account": "이 계정", "this_account": "이 계정",
"delete": "삭제", "delete": "삭제",
"delete_account": "계정 삭제" "delete_account": "계정 삭제",
"file_type": "파일 유형",
"version": "버전",
"spoiler": "스포일러",
"interface": "인터페이스",
"invalid_url": "잘못된 URL",
"no_link_provided": "링크가 제공되지 않았습니다",
"image_search": "이미지 검색",
"view_on": "보기",
"copy_link": "링크 복사",
"hide_post": "게시물 숨기기",
"hide_thread": "스레드 숨기기",
"unhide_post": "게시물 숨기기 해제",
"unhide_thread": "스레드 숨기기 해제",
"search_image_on_google": "Google 이미지 검색",
"search_image_on_yandex": "Yandex 이미지 검색",
"search_image_on_saucenao": "SauceNAO 이미지 검색",
"view_on_client": "{{client}}에서 보기"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "पूर्ण खाते डेटा", "full_account_data": "पूर्ण खाते डेटा",
"this_account": "हे खाते", "this_account": "हे खाते",
"delete": "काढा", "delete": "काढा",
"delete_account": "खाते हटवा" "delete_account": "खाते हटवा",
"file_type": "फाइल प्रकार",
"version": "आवृत्ती",
"spoiler": "स्पॉइलर",
"interface": "इंटरफेस",
"invalid_url": "अवैध URL",
"no_link_provided": "कोणतेही लिंक दिले नाहीत",
"image_search": "प्रतिमा शोध",
"view_on": "पाहा",
"copy_link": "लिंक कॉपी करा",
"hide_post": "पोस्ट लपवा",
"hide_thread": "थ्रेड लपवा",
"unhide_post": "पोस्ट दाखवा",
"unhide_thread": "थ्रेड दाखवा",
"search_image_on_google": "Google वर छायाचित्र शोधा",
"search_image_on_yandex": "Yandex वर छायाचित्र शोधा",
"search_image_on_saucenao": "SauceNAO वर छायाचित्र शोधा",
"view_on_client": "{{client}} वर पहा"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "volledige accountgegevens", "full_account_data": "volledige accountgegevens",
"this_account": "dit account", "this_account": "dit account",
"delete": "verwijderen", "delete": "verwijderen",
"delete_account": "account verwijderen" "delete_account": "account verwijderen",
"file_type": "Bestandstype",
"version": "Versie",
"spoiler": "spoiler",
"interface": "interface",
"invalid_url": "Ongeldige URL",
"no_link_provided": "Geen link opgegeven",
"image_search": "afbeeldingen zoeken",
"view_on": "bekijk op",
"copy_link": "link kopiëren",
"hide_post": "bericht verbergen",
"hide_thread": "thread verbergen",
"unhide_post": "bericht weergeven",
"unhide_thread": "thread weergeven",
"search_image_on_google": "zoek afbeelding op Google",
"search_image_on_yandex": "zoek afbeelding op Yandex",
"search_image_on_saucenao": "zoek afbeelding op SauceNAO",
"view_on_client": "Bekijk op {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "full kontodata", "full_account_data": "full kontodata",
"this_account": "denne kontoen", "this_account": "denne kontoen",
"delete": "slett", "delete": "slett",
"delete_account": "slett konto" "delete_account": "slett konto",
"file_type": "Filtype",
"version": "Versjon",
"spoiler": "spoiler",
"interface": "grensesnitt",
"invalid_url": "Ugyldig URL",
"no_link_provided": "Ingen lenke oppgitt",
"image_search": "bilde søk",
"view_on": "se på",
"copy_link": "kopier lenke",
"hide_post": "skjul innlegg",
"hide_thread": "skjul tråd",
"unhide_post": "vis innlegg",
"unhide_thread": "vis tråd",
"search_image_on_google": "søk etter bilde på Google",
"search_image_on_yandex": "søk etter bilde på Yandex",
"search_image_on_saucenao": "søk etter bilde på SauceNAO",
"view_on_client": "Vis på {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "pełne dane konta", "full_account_data": "pełne dane konta",
"this_account": "ten rachunek", "this_account": "ten rachunek",
"delete": "usuń", "delete": "usuń",
"delete_account": "usuń konto" "delete_account": "usuń konto",
"file_type": "Typ pliku",
"version": "Wersja",
"spoiler": "spoiler",
"interface": "interfejs",
"invalid_url": "Nieprawidłowy adres URL",
"no_link_provided": "Nie podano linku",
"image_search": "wyszukiwanie obrazów",
"view_on": "zobacz na",
"copy_link": "skopiuj link",
"hide_post": "ukryj post",
"hide_thread": "ukryj wątek",
"unhide_post": "pokaż post",
"unhide_thread": "pokaż wątek",
"search_image_on_google": "szukaj obrazu w Google",
"search_image_on_yandex": "szukaj obrazu w Yandex",
"search_image_on_saucenao": "szukaj obrazu w SauceNAO",
"view_on_client": "Zobacz na {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "dados completos da conta", "full_account_data": "dados completos da conta",
"this_account": "esta conta", "this_account": "esta conta",
"delete": "apagar", "delete": "apagar",
"delete_account": "excluir conta" "delete_account": "excluir conta",
"file_type": "Tipo de arquivo",
"version": "Versão",
"spoiler": "spoiler",
"interface": "interface",
"invalid_url": "URL inválida",
"no_link_provided": "Nenhum link fornecido",
"image_search": "pesquisa de imagens",
"view_on": "ver em",
"copy_link": "Copiar link",
"hide_post": "Ocultar postagem",
"hide_thread": "Ocultar tópico",
"unhide_post": "Mostrar postagem",
"unhide_thread": "Mostrar tópico",
"search_image_on_google": "Pesquisar imagem no Google",
"search_image_on_yandex": "Pesquisar imagem no Yandex",
"search_image_on_saucenao": "Pesquisar imagem no SauceNAO",
"view_on_client": "Ver no {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "date complete ale contului", "full_account_data": "date complete ale contului",
"this_account": "acest cont", "this_account": "acest cont",
"delete": "ștergeți", "delete": "ștergeți",
"delete_account": "șterge contul" "delete_account": "șterge contul",
"file_type": "Tip de fișier",
"version": "Versiune",
"spoiler": "spoiler",
"interface": "interfață",
"invalid_url": "URL invalid",
"no_link_provided": "Nu s-a furnizat niciun link",
"image_search": "căutare de imagini",
"view_on": "vezi pe",
"copy_link": "copiați linkul",
"hide_post": "ascundeți postarea",
"hide_thread": "ascundeți firul",
"unhide_post": "afișează postarea",
"unhide_thread": "afișează firul",
"search_image_on_google": "căutați imaginea pe Google",
"search_image_on_yandex": "căutați imaginea pe Yandex",
"search_image_on_saucenao": "căutați imaginea pe SauceNAO",
"view_on_client": "Vezi pe {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "полные данные аккаунта", "full_account_data": "полные данные аккаунта",
"this_account": "этот аккаунт", "this_account": "этот аккаунт",
"delete": "удалить", "delete": "удалить",
"delete_account": "удалить аккаунт" "delete_account": "удалить аккаунт",
"file_type": "Тип файла",
"version": "Версия",
"spoiler": "спойлер",
"interface": "интерфейс",
"invalid_url": "Недействительный URL",
"no_link_provided": "Ссылка не предоставлена",
"image_search": "поиск изображений",
"view_on": "просмотр на",
"copy_link": "копировать ссылку",
"hide_post": "скрыть пост",
"hide_thread": "скрыть ветку",
"unhide_post": "показать пост",
"unhide_thread": "показать ветку",
"search_image_on_google": "поиск изображения в Google",
"search_image_on_yandex": "поиск изображения в Yandex",
"search_image_on_saucenao": "поиск изображения на SauceNAO",
"view_on_client": "Посмотреть на {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "të dhënat e plotë të llogarisë", "full_account_data": "të dhënat e plotë të llogarisë",
"this_account": "kjo llogaria", "this_account": "kjo llogaria",
"delete": "fshij", "delete": "fshij",
"delete_account": "fshi llogarinë" "delete_account": "fshi llogarinë",
"file_type": "Lloji i skedarit",
"version": "Version",
"spoiler": "spoiler",
"interface": "ndërfaqe",
"invalid_url": "URL jo e vlefshëm",
"no_link_provided": "Nuk është dhënë asnjë lidhje",
"image_search": "kërkim imazhesh",
"view_on": "shiko në",
"copy_link": "kopjo linkun",
"hide_post": "fsheh postën",
"hide_thread": "fsheh fijen",
"unhide_post": "shfaq postën",
"unhide_thread": "shfaq fijen",
"search_image_on_google": "kërko imazhin në Google",
"search_image_on_yandex": "kërko imazhin në Yandex",
"search_image_on_saucenao": "kërko imazhin në SauceNAO",
"view_on_client": "Shiko në {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "fullständig kontodata", "full_account_data": "fullständig kontodata",
"this_account": "detta konto", "this_account": "detta konto",
"delete": "ta bort", "delete": "ta bort",
"delete_account": "ta bort konto" "delete_account": "ta bort konto",
"file_type": "Filtyp",
"version": "Version",
"spoiler": "spoiler",
"interface": "gränssnitt",
"invalid_url": "Ogiltig URL",
"no_link_provided": "Ingen länk angiven",
"image_search": "bildsökning",
"view_on": "se på",
"copy_link": "kopiera länk",
"hide_post": "dölj inlägg",
"hide_thread": "dölj tråd",
"unhide_post": "visa inlägg",
"unhide_thread": "visa tråd",
"search_image_on_google": "sök bild på Google",
"search_image_on_yandex": "sök bild på Yandex",
"search_image_on_saucenao": "sök bild på SauceNAO",
"view_on_client": "Visa på {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "పూర్తి ఖాతా డేటా", "full_account_data": "పూర్తి ఖాతా డేటా",
"this_account": "ఈ ఖాతా", "this_account": "ఈ ఖాతా",
"delete": "తొలగించండి", "delete": "తొలగించండి",
"delete_account": "ఖాతాను తొలగించు" "delete_account": "ఖాతాను తొలగించు",
"file_type": "ఫైల్ రకం",
"version": "వెర్షన్",
"spoiler": "స్పోయ్లర్",
"interface": "అంతరాఫ్సు",
"invalid_url": "చెల్లని URL",
"no_link_provided": "లింక్ ఇవ్వబడలేదు",
"image_search": "చిత్రాల శోధన",
"view_on": "వీక్షించు",
"copy_link": "లింక్ ను కాపీ చేయండి",
"hide_post": "పోస్ట్ దాచు",
"hide_thread": "థ్రెడ్ దాచు",
"unhide_post": "పోస్ట్ చూపించు",
"unhide_thread": "థ్రెడ్ చూపించు",
"search_image_on_google": "గూగుల్‌లో చిత్రాన్ని వెతుకోండి",
"search_image_on_yandex": "యాండెక్స్‌లో చిత్రాన్ని వెతుకోండి",
"search_image_on_saucenao": "SauceNAOలో చిత్రాన్ని వెతుకోండి",
"view_on_client": "{{client}} లో చూడండి"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "ข้อมูลบัญชีเต็มรูปแบบ", "full_account_data": "ข้อมูลบัญชีเต็มรูปแบบ",
"this_account": "บัญชีนี้", "this_account": "บัญชีนี้",
"delete": "ลบ", "delete": "ลบ",
"delete_account": "ลบบัญชี" "delete_account": "ลบบัญชี",
"file_type": "ประเภทไฟล์",
"version": "รุ่น",
"spoiler": "สปอยล์เลอร์",
"interface": "อินเทอร์เฟซ",
"invalid_url": "URL ไม่ถูกต้อง",
"no_link_provided": "ไม่มีการให้ลิงก์",
"image_search": "ค้นหารูปภาพ",
"view_on": "ดูที่",
"copy_link": "คัดลอกลิงก์",
"hide_post": "ซ่อนโพสต์",
"hide_thread": "ซ่อนเธรด",
"unhide_post": "แสดงโพสต์",
"unhide_thread": "แสดงเธรด",
"search_image_on_google": "ค้นหารูปภาพใน Google",
"search_image_on_yandex": "ค้นหารูปภาพใน Yandex",
"search_image_on_saucenao": "ค้นหารูปภาพใน SauceNAO",
"view_on_client": "ดูบน {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "tam hesap verisi", "full_account_data": "tam hesap verisi",
"this_account": "bu hesap", "this_account": "bu hesap",
"delete": "sil", "delete": "sil",
"delete_account": "hesabı sil" "delete_account": "hesabı sil",
"file_type": "Dosya türü",
"version": "Sürüm",
"spoiler": "spoiler",
"interface": "arayüz",
"invalid_url": "Geçersiz URL",
"no_link_provided": "Hiçbir bağlantı sağlanmadı",
"image_search": "resim arama",
"view_on": "üzerinde görüntüle",
"copy_link": "bağlantıyı kopyala",
"hide_post": "gönderiyi gizle",
"hide_thread": "konuyu gizle",
"unhide_post": "gönderiyi göster",
"unhide_thread": "konuyu göster",
"search_image_on_google": "Google'da resim ara",
"search_image_on_yandex": "Yandex'da resim ara",
"search_image_on_saucenao": "SauceNAO'da resim ara",
"view_on_client": "{{client}}'de görüntüle"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "повні дані аккаунта", "full_account_data": "повні дані аккаунта",
"this_account": "цей аккаунт", "this_account": "цей аккаунт",
"delete": "видалити", "delete": "видалити",
"delete_account": "видалити обліковий запис" "delete_account": "видалити обліковий запис",
"file_type": "Тип файлу",
"version": "Версія",
"spoiler": "спойлер",
"interface": "інтерфейс",
"invalid_url": "Недійсний URL",
"no_link_provided": "Не надано жодного посилання",
"image_search": "пошук зображень",
"view_on": "перегляд на",
"copy_link": "копіювати посилання",
"hide_post": "сховати допис",
"hide_thread": "сховати нитку",
"unhide_post": "показати допис",
"unhide_thread": "показати нитку",
"search_image_on_google": "пошук зображення в Google",
"search_image_on_yandex": "пошук зображення в Yandex",
"search_image_on_saucenao": "пошук зображення в SauceNAO",
"view_on_client": "Переглянути на {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "مکمل اکاؤنٹ ڈیٹا", "full_account_data": "مکمل اکاؤنٹ ڈیٹا",
"this_account": "یہ اکاؤنٹ", "this_account": "یہ اکاؤنٹ",
"delete": "حذف کریں", "delete": "حذف کریں",
"delete_account": "اکاؤنٹ حذف کریں" "delete_account": "اکاؤنٹ حذف کریں",
"file_type": "فائل کی قسم",
"version": "ورژن",
"spoiler": "سپوائلر",
"interface": "انٹرفیس",
"invalid_url": "غیر درست یو آر ایل",
"no_link_provided": "کوئی لنک فراہم نہیں کیا گیا",
"image_search": "تصویر کی تلاش",
"view_on": "پر دیکھیں",
"copy_link": "لنک کاپی کریں",
"hide_post": "پوسٹ چھپائیں",
"hide_thread": "دھاگہ چھپائیں",
"unhide_post": "پوسٹ دکھائیں",
"unhide_thread": "دھاگہ دکھائیں",
"search_image_on_google": "گوگل پر تصویر کا مطالعہ کریں",
"search_image_on_yandex": "یانڈیکس پر تصویر کا مطالعہ کریں",
"search_image_on_saucenao": "SauceNAO پر تصویر کا مطالعہ کریں",
"view_on_client": "{{client}} پر دیکھیں"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "dữ liệu tài khoản đầy đủ", "full_account_data": "dữ liệu tài khoản đầy đủ",
"this_account": "tài khoản này", "this_account": "tài khoản này",
"delete": "xóa", "delete": "xóa",
"delete_account": "xóa tài khoản" "delete_account": "xóa tài khoản",
"file_type": "Loại tệp",
"version": "Phiên bản",
"spoiler": "spoiler",
"interface": "giao diện",
"invalid_url": "URL không hợp lệ",
"no_link_provided": "Không có liên kết được cung cấp",
"image_search": "tìm kiếm hình ảnh",
"view_on": "xem trên",
"copy_link": "sao chép liên kết",
"hide_post": "Ẩn bài viết",
"hide_thread": "Ẩn chủ đề",
"unhide_post": "Hiện bài viết",
"unhide_thread": "Hiện chủ đề",
"search_image_on_google": "Tìm kiếm hình ảnh trên Google",
"search_image_on_yandex": "Tìm kiếm hình ảnh trên Yandex",
"search_image_on_saucenao": "Tìm kiếm hình ảnh trên SauceNAO",
"view_on_client": "Xem trên {{client}}"
} }
+18 -1
View File
@@ -114,5 +114,22 @@
"full_account_data": "完整帐户数据", "full_account_data": "完整帐户数据",
"this_account": "此帐户", "this_account": "此帐户",
"delete": "删除", "delete": "删除",
"delete_account": "删除账户" "delete_account": "删除账户",
"file_type": "文件类型",
"version": "版本",
"spoiler": "spoiler",
"interface": "界面",
"invalid_url": "无效的网址",
"no_link_provided": "未提供链接",
"image_search": "图像搜索",
"view_on": "查看",
"copy_link": "复制链接",
"hide_post": "隐藏帖子",
"hide_thread": "隐藏主题",
"unhide_post": "显示帖子",
"unhide_thread": "显示主题",
"search_image_on_google": "在Google上搜索图像",
"search_image_on_yandex": "在Yandex上搜索图像",
"search_image_on_saucenao": "在SauceNAO上搜索图像",
"view_on_client": "在{{client}}上查看"
} }
+15 -5
View File
@@ -1,6 +1,7 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { Outlet, Route, Routes, useLocation, useParams } from 'react-router-dom'; import { Outlet, Route, Routes, useLocation, useParams } from 'react-router-dom';
import { isHomeView } from './lib/utils/view-utils'; import { isHomeView } from './lib/utils/view-utils';
import useIsMobile from './hooks/use-is-mobile';
import useTheme from './hooks/use-theme'; import useTheme from './hooks/use-theme';
import styles from './app.module.css'; import styles from './app.module.css';
import Board from './views/board'; import Board from './views/board';
@@ -9,16 +10,17 @@ import Home from './views/home';
import NotFound from './views/not-found'; import NotFound from './views/not-found';
import PendingPost from './views/pending-post'; import PendingPost from './views/pending-post';
import PostPage from './views/post-page'; import PostPage from './views/post-page';
import BoardBanner from './components/board-banner';
import { DesktopBoardButtons, MobileBoardButtons } from './components/board-buttons'; import { DesktopBoardButtons, MobileBoardButtons } from './components/board-buttons';
import BoardNav from './components/board-nav'; import BoardHeader from './components/board-header';
import ChallengeModal from './components/challenge-modal'; import ChallengeModal from './components/challenge-modal';
import SubplebbitStats from './components/subplebbit-stats';
import PostForm from './components/post-form'; import PostForm from './components/post-form';
import SubplebbitStats from './components/subplebbit-stats';
import TopBar from './components/topbar';
const BoardLayout = () => { const BoardLayout = () => {
const { accountCommentIndex, commentCid, subplebbitAddress } = useParams(); const { accountCommentIndex, commentCid, subplebbitAddress } = useParams();
const location = useLocation(); const location = useLocation();
const isMobile = useIsMobile();
const isValidAccountCommentIndex = !accountCommentIndex || (!isNaN(parseInt(accountCommentIndex)) && parseInt(accountCommentIndex) >= 0); const isValidAccountCommentIndex = !accountCommentIndex || (!isNaN(parseInt(accountCommentIndex)) && parseInt(accountCommentIndex) >= 0);
const isValidCommentCid = !commentCid || /^Qm[a-zA-Z0-9]{44}$/.test(commentCid); const isValidCommentCid = !commentCid || /^Qm[a-zA-Z0-9]{44}$/.test(commentCid);
@@ -33,12 +35,20 @@ const BoardLayout = () => {
return ( return (
<div className={styles.boardLayout}> <div className={styles.boardLayout}>
<BoardNav /> <TopBar />
<BoardBanner /> <BoardHeader />
{isMobile ? (
<>
<PostForm key={key} />
<MobileBoardButtons /> <MobileBoardButtons />
</>
) : (
<>
<PostForm key={key} /> <PostForm key={key} />
{subplebbitAddress && isValidSubplebbitAddress && <SubplebbitStats />} {subplebbitAddress && isValidSubplebbitAddress && <SubplebbitStats />}
<DesktopBoardButtons /> <DesktopBoardButtons />
</>
)}
<Outlet /> <Outlet />
</div> </div>
); );
-1
View File
@@ -1 +0,0 @@
export { default } from './board-banner';
@@ -1,6 +1,6 @@
.mobileBoardButtons { .mobileBoardButtons {
text-align: center; text-align: center;
margin-top: 9px; margin: 10px 0;
} }
.mobileBoardButtons button { .mobileBoardButtons button {
@@ -12,9 +12,13 @@
all: unset; all: unset;
} }
.mobilePostPageButtonsSecondRow { .showBlockedButton {
padding-top: 5px; color: var(--button-desktop-text-color);
padding-bottom: 20px; }
.showBlockedButton:hover {
color: var(--button-desktop-text-color-hover);
cursor: pointer;
} }
@media (max-width: 640px) { @media (max-width: 640px) {
+45 -25
View File
@@ -1,12 +1,13 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link, useLocation, useParams } from 'react-router-dom'; import { Link, useLocation, useParams } from 'react-router-dom';
import { useAccountComment, useSubscribe } from '@plebbit/plebbit-react-hooks'; import { useAccountComment, useSubscribe } from '@plebbit/plebbit-react-hooks';
import styles from './board-buttons.module.css';
import { isAllView, isCatalogView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { isAllView, isCatalogView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
import useFeedResetStore from '../../stores/use-feed-reset-store';
import styles from './board-buttons.module.css';
interface BoardButtonsProps { interface BoardButtonsProps {
isInAllView?: boolean; isInAllView?: boolean;
address: string | undefined; address?: string | undefined;
isInCatalogView?: boolean; isInCatalogView?: boolean;
isInSubscriptionsView?: boolean; isInSubscriptionsView?: boolean;
} }
@@ -16,16 +17,11 @@ const OptionsButton = () => {
return <button className='button'>{t('options')}</button>; return <button className='button'>{t('options')}</button>;
}; };
const CatalogButton = ({ address, isInAllView, isInCatalogView }: BoardButtonsProps) => { const CatalogButton = ({ address, isInAllView, isInSubscriptionsView }: BoardButtonsProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<button className='button'> <button className='button'>
{isInCatalogView ? ( <Link to={isInAllView ? `/p/all/catalog` : isInSubscriptionsView ? `/p/subscriptions/catalog` : `/p/${address}/catalog`}>{t('catalog')}</Link>
<Link to={isInAllView ? '/p/all' : `/p/${address}`}>{t('return')}</Link>
) : (
<Link to={isInAllView ? `/p/all/catalog` : `/p/${address}/catalog`}>{t('catalog')}</Link>
)}
</button> </button>
); );
}; };
@@ -50,11 +46,12 @@ const ReturnButton = ({ address }: BoardButtonsProps) => {
); );
}; };
const BottomButton = () => { const RefreshButton = () => {
const { t } = useTranslation(); const reset = useFeedResetStore((state) => state.reset);
return ( return (
<button className='button' onClick={() => window.scrollTo(0, document.body.scrollHeight)}> <button className='button' onClick={() => reset && reset()}>
{t('bottom')} Refresh
</button> </button>
); );
}; };
@@ -74,20 +71,22 @@ export const MobileBoardButtons = () => {
return ( return (
<div className={styles.mobileBoardButtons}> <div className={styles.mobileBoardButtons}>
{isInPostView || isInPendingPostPage ? ( {isInPostView || isInPendingPostPage ? (
<div className={styles.mobilePostPageButtons}> <>
<ReturnButton address={subplebbitAddress} /> <ReturnButton address={subplebbitAddress} />
<CatalogButton address={subplebbitAddress} isInAllView={isInAllView} /> <CatalogButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} />
<BottomButton />
<div className={styles.mobilePostPageButtonsSecondRow}>
<OptionsButton /> <OptionsButton />
<SubscribeButton address={subplebbitAddress} /> <SubscribeButton address={subplebbitAddress} />
</div> </>
</div>
) : ( ) : (
<> <>
{isInCatalogView ? (
<ReturnButton address={subplebbitAddress} />
) : (
<CatalogButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} />
)}
<OptionsButton /> <OptionsButton />
<CatalogButton address={subplebbitAddress} isInAllView={isInAllView} isInCatalogView={isInCatalogView} isInSubscriptionsView={isInSubscriptionsView} />
<SubscribeButton address={subplebbitAddress} /> <SubscribeButton address={subplebbitAddress} />
<RefreshButton />
</> </>
)} )}
</div> </div>
@@ -110,19 +109,40 @@ export const DesktopBoardButtons = () => {
<hr /> <hr />
{isInPostView || isInPendingPostPage ? ( {isInPostView || isInPendingPostPage ? (
<> <>
[<ReturnButton address={subplebbitAddress} />] [<CatalogButton address={subplebbitAddress} />] [<BottomButton />] [ [
<OptionsButton />] <ReturnButton address={subplebbitAddress} />
] [
<CatalogButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} />
] [
<OptionsButton />]{' '}
<span className={styles.subscribeButton}>
[<SubscribeButton address={subplebbitAddress} />]
</span>
</>
) : isInCatalogView ? (
<>
[
<ReturnButton address={subplebbitAddress} />
] [
<OptionsButton />
] [
<RefreshButton />]{' '}
<span className={styles.subscribeButton}> <span className={styles.subscribeButton}>
[<SubscribeButton address={subplebbitAddress} />] [<SubscribeButton address={subplebbitAddress} />]
</span> </span>
</> </>
) : ( ) : (
<> <>
[<OptionsButton />] [ [
<CatalogButton address={subplebbitAddress} isInAllView={isInAllView} isInCatalogView={isInCatalogView} isInSubscriptionsView={isInSubscriptionsView} />] <CatalogButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} />
] [
<OptionsButton />
] [
<RefreshButton />]{' '}
{!(isInAllView || isInSubscriptionsView) && ( {!(isInAllView || isInSubscriptionsView) && (
<span className={styles.subscribeButton}> <span className={styles.subscribeButton}>
[<SubscribeButton address={subplebbitAddress} />] [
<SubscribeButton address={subplebbitAddress} />]
</span> </span>
)} )}
</> </>
@@ -2,7 +2,7 @@ import { useState } from 'react';
import { useLocation, useParams } from 'react-router-dom'; import { useLocation, useParams } from 'react-router-dom';
import { useAccountComment, useSubplebbit } from '@plebbit/plebbit-react-hooks'; import { useAccountComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
import styles from './board-banner.module.css'; import styles from './board-header.module.css';
import { useMultisubMetadata } from '../../hooks/use-default-subplebbits'; import { useMultisubMetadata } from '../../hooks/use-default-subplebbits';
const totalBanners = 57; const totalBanners = 57;
@@ -16,7 +16,7 @@ const ImageBanner = () => {
return <img src={imagePath} alt='' />; return <img src={imagePath} alt='' />;
}; };
const BoardBanner = () => { const BoardHeader = () => {
const location = useLocation(); const location = useLocation();
const params = useParams(); const params = useParams();
@@ -46,4 +46,4 @@ const BoardBanner = () => {
); );
}; };
export default BoardBanner; export default BoardHeader;
+1
View File
@@ -0,0 +1 @@
export { default } from './board-header';
-1
View File
@@ -1 +0,0 @@
export { default } from './board-nav';
@@ -54,6 +54,7 @@ export const CatalogPostMedia = ({ commentMediaInfo, isOutOfFeed, linkWidth, lin
thumbnailComponent = thumbnail ? ( thumbnailComponent = thumbnail ? (
<img src={thumbnail} alt='' onLoad={handleLoad} onError={handleError} style={loadingStyle} /> <img src={thumbnail} alt='' onLoad={handleLoad} onError={handleError} style={loadingStyle} />
) : ( ) : (
// show first frame of the video, as a workaround for Safari not loading thumbnails
<video src={`${url}#t=0.001`} onLoad={handleLoad} onError={handleError} style={loadingStyle} /> <video src={`${url}#t=0.001`} onLoad={handleLoad} onError={handleError} style={loadingStyle} />
); );
} else if (type === 'webpage' && !hasError) { } else if (type === 'webpage' && !hasError) {
@@ -3,8 +3,8 @@ import Draggable from 'react-draggable';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Challenge as ChallengeType } from '@plebbit/plebbit-react-hooks'; import { Challenge as ChallengeType } from '@plebbit/plebbit-react-hooks';
import { getPublicationType } from '../../lib/utils/challenge-utils'; import { getPublicationType } from '../../lib/utils/challenge-utils';
import useChallenges from '../../hooks/use-challenges';
import useIsMobile from '../../hooks/use-is-mobile'; import useIsMobile from '../../hooks/use-is-mobile';
import useChallengesStore from '../../stores/use-challenges-store';
import styles from './challenge-modal.module.css'; import styles from './challenge-modal.module.css';
import _ from 'lodash'; import _ from 'lodash';
@@ -125,7 +125,7 @@ const Challenge = ({ challenge, closeModal }: ChallengeProps) => {
}; };
const ChallengeModal = () => { const ChallengeModal = () => {
const { challenges, removeChallenge } = useChallenges(); const { challenges, removeChallenge } = useChallengesStore();
const isOpen = !!challenges.length; const isOpen = !!challenges.length;
const closeModal = () => removeChallenge(); const closeModal = () => removeChallenge();
@@ -4,7 +4,7 @@
height: var(--height); height: var(--height);
max-width: 250px; max-width: 250px;
max-height: 250px; max-height: 250px;
margin: 3px 20px 5px 0; margin: 3px 20px 5px 20px;
display: inline-flex; display: inline-flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
+11 -9
View File
@@ -9,16 +9,16 @@ import Embed, { canEmbed } from '../embed';
interface MediaProps { interface MediaProps {
commentMediaInfo?: CommentMediaInfo; commentMediaInfo?: CommentMediaInfo;
isDeleted?: boolean;
isOutOfFeed?: boolean; // virtuoso wrapper unneeded isOutOfFeed?: boolean; // virtuoso wrapper unneeded
isReply: boolean; isReply: boolean;
linkHeight?: number; linkHeight?: number;
linkWidth?: number; linkWidth?: number;
showThumbnail?: boolean; showThumbnail?: boolean;
setShowThumbnail: (showThumbnail: boolean) => void; setShowThumbnail: (showThumbnail: boolean) => void;
toggleExpanded?: () => void;
} }
const Thumbnail = ({ commentMediaInfo, isOutOfFeed, isReply, linkHeight, linkWidth, setShowThumbnail }: MediaProps) => { const Thumbnail = ({ commentMediaInfo, isDeleted, isOutOfFeed, isReply, linkHeight, linkWidth, setShowThumbnail }: MediaProps) => {
const { patternThumbnailUrl, thumbnail, type, url } = commentMediaInfo || {}; const { patternThumbnailUrl, thumbnail, type, url } = commentMediaInfo || {};
const [hasError, setHasError] = useState(false); const [hasError, setHasError] = useState(false);
const handleError = () => setHasError(true); const handleError = () => setHasError(true);
@@ -49,7 +49,12 @@ const Thumbnail = ({ commentMediaInfo, isOutOfFeed, isReply, linkHeight, linkWid
if (type === 'image') { if (type === 'image') {
thumbnailComponent = <img src={url} alt='' onError={handleError} onClick={() => setShowThumbnail(false)} />; thumbnailComponent = <img src={url} alt='' onError={handleError} onClick={() => setShowThumbnail(false)} />;
} else if (type === 'video') { } else if (type === 'video') {
thumbnailComponent = thumbnail ? <img src={thumbnail} alt='' /> : <video src={`${url}#t=0.001`} onClick={() => setShowThumbnail(false)} />; thumbnailComponent = thumbnail ? (
<img src={thumbnail} alt='' />
) : (
// show first frame of the video, as a workaround for Safari not loading thumbnails
<video src={`${url}#t=0.001`} onClick={() => setShowThumbnail(false)} />
);
} else if (type === 'webpage') { } else if (type === 'webpage') {
thumbnailComponent = <img src={thumbnail} alt='' onClick={() => setShowThumbnail(false)} />; thumbnailComponent = <img src={thumbnail} alt='' onClick={() => setShowThumbnail(false)} />;
} else if (type === 'iframe') { } else if (type === 'iframe') {
@@ -60,16 +65,12 @@ const Thumbnail = ({ commentMediaInfo, isOutOfFeed, isReply, linkHeight, linkWid
thumbnailComponent = <audio src={url} controls />; thumbnailComponent = <audio src={url} controls />;
} }
if (hasError) {
thumbnailComponent = <img className={styles.fileDeleted} src='/assets/filedeleted-res.gif' alt='File deleted' />;
}
const thumbnailSmallPadding = isMobile ? styles.thumbnailMobile : styles.thumbnailReplyDesktop; const thumbnailSmallPadding = isMobile ? styles.thumbnailMobile : styles.thumbnailReplyDesktop;
const thumbnailDimensions = { '--width': displayWidth, '--height': displayHeight } as React.CSSProperties; const thumbnailDimensions = { '--width': displayWidth, '--height': displayHeight } as React.CSSProperties;
const linkWithoutThumbnail = url && new URL(url); const linkWithoutThumbnail = url && new URL(url);
return hasError ? ( return hasError || isDeleted ? (
<img className={styles.fileDeleted} src='/assets/filedeleted-res.gif' alt='File deleted' /> <img className={styles.fileDeleted} src='/assets/filedeleted-res.gif' alt='File deleted' />
) : isOutOfFeed ? ( ) : isOutOfFeed ? (
<span className={styles.subplebbitAvatar}>{thumbnailComponent}</span> <span className={styles.subplebbitAvatar}>{thumbnailComponent}</span>
@@ -132,7 +133,7 @@ const Media = ({ commentMediaInfo, isReply, setShowThumbnail }: MediaProps) => {
); );
}; };
const CommentMedia = ({ commentMediaInfo, isOutOfFeed, isReply, linkHeight, linkWidth, showThumbnail, setShowThumbnail }: MediaProps) => { const CommentMedia = ({ commentMediaInfo, isDeleted, isOutOfFeed, isReply, linkHeight, linkWidth, showThumbnail, setShowThumbnail }: MediaProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const { type, url } = commentMediaInfo || {}; const { type, url } = commentMediaInfo || {};
@@ -143,6 +144,7 @@ const CommentMedia = ({ commentMediaInfo, isOutOfFeed, isReply, linkHeight, link
{url && ( {url && (
<Thumbnail <Thumbnail
commentMediaInfo={commentMediaInfo} commentMediaInfo={commentMediaInfo}
isDeleted={isDeleted}
isOutOfFeed={isOutOfFeed} isOutOfFeed={isOutOfFeed}
isReply={isReply} isReply={isReply}
linkHeight={linkHeight} linkHeight={linkHeight}
+1 -1
View File
@@ -1 +1 @@
export { default } from './post-form'; export { LinkTypePreviewer, default } from './post-form';
+38 -10
View File
@@ -15,12 +15,7 @@
.postFormMobile { .postFormMobile {
text-align: center; text-align: center;
padding-top: 9px; margin-top: -2px;
padding-bottom: 0.5em;
}
.postFormMobile .showFormButton {
margin-bottom: 5px;
} }
.closed { .closed {
@@ -87,16 +82,49 @@
.linkType { .linkType {
font-weight: normal; font-weight: normal;
position: absolute; }
white-space: nowrap;
bottom: 3px; .linkType td {
padding-left: 3px; height: 20px;
padding-left: 2px;
}
.spoilerButton input[type="checkbox"] {
margin: 0 3px;
vertical-align: middle;
transform: translateY(-2px);
}
.spoilerButton td {
padding-top: 2px;
height: 20px;
padding-left: 2px;
}
.spoilerButton {
font-weight: normal;
} }
@media (max-width: 640px) { @media (max-width: 640px) {
.postFormDesktop { .postFormDesktop {
display: none; display: none;
} }
.postFormTable {
padding-top: 5px;
}
.postFormTable td input {
height: 20px;
}
.postFormTable td textarea {
height: 80px;
}
.postFormTable button {
padding: 4px 8px;
}
} }
@media (min-width: 640px) { @media (min-width: 640px) {
+26 -13
View File
@@ -7,9 +7,9 @@ import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-util
import { getLinkMediaInfo } from '../../lib/utils/media-utils'; import { getLinkMediaInfo } from '../../lib/utils/media-utils';
import { isValidURL } from '../../lib/utils/url-utils'; import { isValidURL } from '../../lib/utils/url-utils';
import { isAllView, isDescriptionView, isPostPageView, isRulesView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { isAllView, isDescriptionView, isPostPageView, isRulesView, isSubscriptionsView } from '../../lib/utils/view-utils';
import challengesStore from '../../hooks/use-challenges';
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits'; import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
import useReply from '../../hooks/use-reply'; import useReply from '../../hooks/use-reply';
import useChallengesStore from '../../stores/use-challenges-store';
import styles from './post-form.module.css'; import styles from './post-form.module.css';
import _ from 'lodash'; import _ from 'lodash';
@@ -18,26 +18,29 @@ type SubmitState = {
title: string | undefined; title: string | undefined;
content: string | undefined; content: string | undefined;
link: string | undefined; link: string | undefined;
spoiler: boolean | undefined;
publishCommentOptions: PublishCommentOptions; publishCommentOptions: PublishCommentOptions;
setSubmitStore: (data: Partial<SubmitState>) => void; setSubmitStore: (data: Partial<SubmitState>) => void;
resetSubmitStore: () => void; resetSubmitStore: () => void;
}; };
const { addChallenge } = challengesStore.getState(); const { addChallenge } = useChallengesStore.getState();
const useSubmitStore = create<SubmitState>((set) => ({ const useSubmitStore = create<SubmitState>((set) => ({
subplebbitAddress: undefined, subplebbitAddress: undefined,
title: undefined, title: undefined,
content: undefined, content: undefined,
link: undefined, link: undefined,
spoiler: undefined,
publishCommentOptions: {}, publishCommentOptions: {},
setSubmitStore: ({ subplebbitAddress, title, content, link }) => setSubmitStore: ({ subplebbitAddress, title, content, link, spoiler }) =>
set((state) => { set((state) => {
const nextState = { ...state }; const nextState = { ...state };
if (subplebbitAddress !== undefined) nextState.subplebbitAddress = subplebbitAddress; if (subplebbitAddress !== undefined) nextState.subplebbitAddress = subplebbitAddress;
if (title !== undefined) nextState.title = title || undefined; if (title !== undefined) nextState.title = title || undefined;
if (content !== undefined) nextState.content = content || undefined; if (content !== undefined) nextState.content = content || undefined;
if (link !== undefined) nextState.link = link || undefined; if (link !== undefined) nextState.link = link || undefined;
if (spoiler !== undefined) nextState.spoiler = spoiler || undefined;
nextState.publishCommentOptions = { nextState.publishCommentOptions = {
...nextState, ...nextState,
@@ -51,12 +54,13 @@ const useSubmitStore = create<SubmitState>((set) => ({
}; };
return nextState; return nextState;
}), }),
resetSubmitStore: () => set({ subplebbitAddress: undefined, title: undefined, content: undefined, link: undefined, publishCommentOptions: {} }), resetSubmitStore: () => set({ subplebbitAddress: undefined, title: undefined, content: undefined, link: undefined, spoiler: undefined, publishCommentOptions: {} }),
})); }));
const LinkTypePreviewer = ({ link }: { link: string }) => { export const LinkTypePreviewer = ({ link }: { link: string }) => {
const { t } = useTranslation();
const mediaInfo = getLinkMediaInfo(link); const mediaInfo = getLinkMediaInfo(link);
return isValidURL(link) ? mediaInfo?.type : 'Invalid URL'; return isValidURL(link) ? mediaInfo?.type : t('invalid_url');
}; };
const PostFormTable = ({ closeForm }: { closeForm: () => void }) => { const PostFormTable = ({ closeForm }: { closeForm: () => void }) => {
@@ -209,13 +213,21 @@ const PostFormTable = ({ closeForm }: { closeForm: () => void }) => {
isInPostView ? setContent.link(e.target.value) : setSubmitStore({ link: e.target.value }); isInPostView ? setContent.link(e.target.value) : setSubmitStore({ link: e.target.value });
}} }}
/> />
<span className={styles.linkType}> </td>
{url && ( </tr>
<> <tr className={styles.linkType}>
(<LinkTypePreviewer link={url} />) <td>{t('file_type')}</td>
</> <td>{url ? <LinkTypePreviewer link={url} /> : t('no_link_provided')}</td>
)} </tr>
</span> <tr className={styles.spoilerButton}>
<td>{t('options')}</td>
<td>
[
<label>
<input type='checkbox' onChange={(e) => (isInPostView ? setContent.spoiler(e.target.checked) : setSubmitStore({ spoiler: e.target.checked }))} />
{t('spoiler')}?
</label>
]
</td> </td>
</tr> </tr>
{(isInAllView || isInSubscriptionsView) && ( {(isInAllView || isInSubscriptionsView) && (
@@ -293,6 +305,7 @@ 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)} />} {showForm && <PostFormTable closeForm={() => setShowForm(false)} />}
<hr />
</> </>
)} )}
</div> </div>
@@ -2,7 +2,7 @@ import { useState } from 'react';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { useLocation, useParams } from 'react-router-dom'; import { useLocation, useParams } from 'react-router-dom';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { useAccount } from '@plebbit/plebbit-react-hooks'; import { useAccount, useBlock, 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 '../post.module.css'; import styles from '../post.module.css';
import { getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail } from '../../../lib/utils/media-utils'; import { getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail } from '../../../lib/utils/media-utils';
@@ -20,7 +20,7 @@ import PostMenuDesktop from './post-menu-desktop/';
import { PostProps } from '../post'; import { PostProps } from '../post';
import _ from 'lodash'; import _ from 'lodash';
const PostInfo = ({ openReplyModal, post, roles }: PostProps) => { const PostInfo = ({ openReplyModal, post, roles, isHidden }: PostProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { author, cid, locked, pinned, parentCid, postCid, shortCid, state, subplebbitAddress, timestamp, title } = post || {}; const { author, cid, locked, pinned, parentCid, postCid, shortCid, state, subplebbitAddress, timestamp, title } = post || {};
const { address, displayName, shortAddress } = author || {}; const { address, displayName, shortAddress } = author || {};
@@ -44,9 +44,11 @@ const PostInfo = ({ openReplyModal, post, roles }: PostProps) => {
return ( return (
<div className={styles.postInfo}> <div className={styles.postInfo}>
{!isHidden && (
<span className={styles.checkbox}> <span className={styles.checkbox}>
<input type='checkbox' /> <input type='checkbox' />
</span> </span>
)}
{title && <span className={styles.subject}>{displayTitle} </span>} {title && <span className={styles.subject}>{displayTitle} </span>}
<span className={styles.nameBlock}> <span className={styles.nameBlock}>
<span className={`${styles.name} ${(isDescription || isRules || authorRole) && styles.capcodeMod}`}> <span className={`${styles.name} ${(isDescription || isRules || authorRole) && styles.capcodeMod}`}>
@@ -90,7 +92,7 @@ const PostInfo = ({ openReplyModal, post, roles }: PostProps) => {
<img src='/assets/icons/closed.gif' alt='' className={styles.closedIcon} title={t('closed')} /> <img src='/assets/icons/closed.gif' alt='' className={styles.closedIcon} title={t('closed')} />
</span> </span>
)} )}
{!isInPostView && !isReply && ( {!isInPostView && !isReply && !isHidden && (
<span className={styles.replyButton}> <span className={styles.replyButton}>
[ [
<Link <Link
@@ -109,7 +111,7 @@ const PostInfo = ({ openReplyModal, post, roles }: PostProps) => {
const PostMedia = ({ post }: PostProps) => { const PostMedia = ({ post }: PostProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { link, linkHeight, linkWidth, parentCid } = post || {}; const { deleted, link, linkHeight, linkWidth, parentCid, removed } = post || {};
const { isDescription, isRules } = post || {}; // custom properties, not from api const { isDescription, isRules } = post || {}; // custom properties, not from api
const commentMediaInfo = getCommentMediaInfo(post); const commentMediaInfo = getCommentMediaInfo(post);
const { type, url } = commentMediaInfo || {}; const { type, url } = commentMediaInfo || {};
@@ -149,22 +151,25 @@ const PostMedia = ({ post }: PostProps) => {
)} )}
</div> </div>
{(hasThumbnail || (!hasThumbnail && !showThumbnail)) && ( {(hasThumbnail || (!hasThumbnail && !showThumbnail)) && (
<div className={styles.fileThumbnail}>
<CommentMedia <CommentMedia
commentMediaInfo={commentMediaInfo} commentMediaInfo={commentMediaInfo}
isOutOfFeed={isDescription || isRules} // virtuoso wrapper unneeded isOutOfFeed={isDescription || isRules} // virtuoso wrapper unneeded
isDeleted={removed || deleted}
isReply={isReply} isReply={isReply}
linkHeight={linkHeight} linkHeight={linkHeight}
linkWidth={linkWidth} linkWidth={linkWidth}
showThumbnail={showThumbnail} showThumbnail={showThumbnail}
setShowThumbnail={setShowThumbnail} setShowThumbnail={setShowThumbnail}
/> />
</div>
)} )}
</div> </div>
); );
}; };
const PostMessage = ({ post }: PostProps) => { const PostMessage = ({ post }: PostProps) => {
const { cid, content, parentCid, postCid, state, subplebbitAddress } = post || {}; const { cid, content, deleted, parentCid, postCid, reason, removed, state, subplebbitAddress } = post || {};
const params = useParams(); const params = useParams();
const location = useLocation(); const location = useLocation();
@@ -191,7 +196,20 @@ const PostMessage = ({ post }: PostProps) => {
<br /> <br />
</> </>
)} )}
{removed ? (
<span className={styles.removedContent}>(THIS POST WAS REMOVED)</span>
) : deleted ? (
<span className={styles.removedContent}>User deleted this post.</span>
) : (
<Markdown content={displayContent} /> <Markdown content={displayContent} />
)}
{(removed || deleted) && reason && (
<span>
<br />
<br />
reason: {reason}
</span>
)}
{!isReply && content.length > 1000 && !isInPostView && ( {!isReply && content.length > 1000 && !isInPostView && (
<span className={styles.abbr}> <span className={styles.abbr}>
<br /> <br />
@@ -209,13 +227,21 @@ const PostMessage = ({ post }: PostProps) => {
}; };
const PostDesktop = ({ openReplyModal, post, roles, showAllReplies }: PostProps) => { const PostDesktop = ({ openReplyModal, post, roles, showAllReplies }: PostProps) => {
// handle pending mod or author edit
const { editedComment } = useEditedComment({ comment: post });
if (editedComment) {
post = editedComment;
}
const { cid, content, link, pinned, replyCount, subplebbitAddress } = post || {}; const { cid, content, link, pinned, replyCount, subplebbitAddress } = post || {};
const { isDescription, isRules } = post || {}; // custom properties, not from api const { isDescription, isRules } = post || {}; // custom properties, not from api
const params = useParams(); const params = useParams();
const location = useLocation(); const location = useLocation();
const isInPendingPostView = isPendingPostView(location.pathname, params); const isInPendingPostView = isPendingPostView(location.pathname, params);
const isInPostView = isPostPageView(location.pathname, params); const isInPostPageView = isPostPageView(location.pathname, params);
const { blocked, unblock, block } = useBlock({ cid });
const isHidden = blocked && !isInPostPageView;
const replies = useReplies(post); const replies = useReplies(post);
const visiblelinksCount = useCountLinksInReplies(post, 5); const visiblelinksCount = useCountLinksInReplies(post, 5);
@@ -228,16 +254,17 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies }: PostProps)
<div className={styles.hrWrapper}> <div className={styles.hrWrapper}>
<hr /> <hr />
</div> </div>
{!isInPostView && ( <div className={isHidden ? styles.postDesktopBlocked : ''}>
{!isInPostPageView && !isDescription && !isRules && (
<span className={styles.hideButtonWrapper}> <span className={styles.hideButtonWrapper}>
<span className={`${styles.hideButton} ${styles.hideThread}`} /> <span className={`${styles.hideButton} ${blocked ? styles.unhideThread : styles.hideThread}`} onClick={blocked ? unblock : block} />
</span> </span>
)} )}
{link && isValidURL(link) && <PostMedia post={post} />} {link && !isHidden && isValidURL(link) && <PostMedia post={post} />}
<PostInfo openReplyModal={openReplyModal} post={post} roles={roles} /> <PostInfo isHidden={isHidden} openReplyModal={openReplyModal} post={post} roles={roles} />
{!content && <div className={styles.spacer} />} {!isHidden && !content && <div className={styles.spacer} />}
{content && <PostMessage post={post} />} {!isHidden && content && <PostMessage post={post} />}
{!isDescription && !isRules && !isInPendingPostView && (replies.length > 5 || (pinned && replies.length > 0)) && !isInPostView && ( {!isHidden && !isDescription && !isRules && !isInPendingPostView && (replies.length > 5 || (pinned && replies.length > 0)) && !isInPostPageView && (
<span className={styles.summary}> <span className={styles.summary}>
<span className={styles.expandButtonWrapper}> <span className={styles.expandButtonWrapper}>
<span className={styles.expandButton} /> <span className={styles.expandButton} />
@@ -254,7 +281,8 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies }: PostProps)
)} )}
</span> </span>
)} )}
{!(pinned && !isInPostView) && {!isHidden &&
!(pinned && !isInPostPageView) &&
!isInPendingPostView && !isInPendingPostView &&
!isDescription && !isDescription &&
!isRules && !isRules &&
@@ -272,6 +300,7 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies }: PostProps)
</div> </div>
))} ))}
</div> </div>
</div>
); );
}; };
@@ -1,12 +1,13 @@
import { useState } from 'react'; import { useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { useLocation } from 'react-router-dom'; import { useLocation, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { autoUpdate, flip, FloatingFocusManager, offset, shift, useClick, useDismiss, useFloating, useId, useInteractions, useRole } from '@floating-ui/react'; import { autoUpdate, flip, FloatingFocusManager, offset, shift, useClick, useDismiss, useFloating, useId, useInteractions, useRole } from '@floating-ui/react';
import { Comment } from '@plebbit/plebbit-react-hooks'; import { Comment, useBlock } from '@plebbit/plebbit-react-hooks';
import styles from './post-menu-desktop.module.css'; import styles from './post-menu-desktop.module.css';
import { getCommentMediaInfo } from '../../../../lib/utils/media-utils'; import { getCommentMediaInfo } from '../../../../lib/utils/media-utils';
import { copyShareLinkToClipboard, isValidURL } from '../../../../lib/utils/url-utils'; import { copyShareLinkToClipboard, isValidURL } from '../../../../lib/utils/url-utils';
import { isCatalogView } from '../../../../lib/utils/view-utils'; import { isCatalogView, isPostPageView } from '../../../../lib/utils/view-utils';
interface PostMenuDesktopProps { interface PostMenuDesktopProps {
cid: string; cid: string;
@@ -17,6 +18,7 @@ interface PostMenuDesktopProps {
} }
const CopyLinkButton = ({ cid, subplebbitAddress, onClose }: PostMenuDesktopProps) => { const CopyLinkButton = ({ cid, subplebbitAddress, onClose }: PostMenuDesktopProps) => {
const { t } = useTranslation();
return ( return (
<div <div
onClick={() => { onClick={() => {
@@ -24,12 +26,13 @@ const CopyLinkButton = ({ cid, subplebbitAddress, onClose }: PostMenuDesktopProp
onClose(); onClose();
}} }}
> >
<div className={styles.postMenuItem}>Copy link</div> <div className={styles.postMenuItem}>{t('copy_link')}</div>
</div> </div>
); );
}; };
const ImageSearchButton = ({ url, onClose }: { url: string; onClose: () => void }) => { const ImageSearchButton = ({ url, onClose }: { url: string; onClose: () => void }) => {
const { t } = useTranslation();
const [isImageSearchMenuOpen, setIsImageSearchMenuOpen] = useState(false); const [isImageSearchMenuOpen, setIsImageSearchMenuOpen] = useState(false);
const { refs, floatingStyles } = useFloating({ const { refs, floatingStyles } = useFloating({
@@ -45,7 +48,7 @@ const ImageSearchButton = ({ url, onClose }: { url: string; onClose: () => void
ref={refs.setReference} ref={refs.setReference}
onClick={onClose} onClick={onClose}
> >
Image search » {t('image_search')} »
{isImageSearchMenuOpen && ( {isImageSearchMenuOpen && (
<div ref={refs.setFloating} style={floatingStyles} className={styles.dropdownMenu}> <div ref={refs.setFloating} style={floatingStyles} className={styles.dropdownMenu}>
<a href={`https://lens.google.com/uploadbyurl?url=${url}`} target='_blank' rel='noreferrer'> <a href={`https://lens.google.com/uploadbyurl?url=${url}`} target='_blank' rel='noreferrer'>
@@ -64,6 +67,7 @@ const ImageSearchButton = ({ url, onClose }: { url: string; onClose: () => void
}; };
const ViewOnButton = ({ cid, isDescription, isRules, subplebbitAddress, onClose }: PostMenuDesktopProps) => { const ViewOnButton = ({ cid, isDescription, isRules, subplebbitAddress, onClose }: PostMenuDesktopProps) => {
const { t } = useTranslation();
const [isClientRedirectMenuOpen, setIsClientRedirectMenuOpen] = useState(false); const [isClientRedirectMenuOpen, setIsClientRedirectMenuOpen] = useState(false);
const viewOnOtherClientLink = `p/${subplebbitAddress}${isDescription || isRules ? '' : `/c/${cid}`}`; const viewOnOtherClientLink = `p/${subplebbitAddress}${isDescription || isRules ? '' : `/c/${cid}`}`;
@@ -80,7 +84,7 @@ const ViewOnButton = ({ cid, isDescription, isRules, subplebbitAddress, onClose
ref={refs.setReference} ref={refs.setReference}
onClick={onClose} onClick={onClose}
> >
View on » {t('view_on')} »
{isClientRedirectMenuOpen && ( {isClientRedirectMenuOpen && (
<div ref={refs.setFloating} style={floatingStyles} className={styles.dropdownMenu}> <div ref={refs.setFloating} style={floatingStyles} className={styles.dropdownMenu}>
<a href={`https://seedit.app/#/${viewOnOtherClientLink}`} target='_blank' rel='noreferrer'> <a href={`https://seedit.app/#/${viewOnOtherClientLink}`} target='_blank' rel='noreferrer'>
@@ -96,12 +100,18 @@ const ViewOnButton = ({ cid, isDescription, isRules, subplebbitAddress, onClose
}; };
const PostMenuDesktop = ({ post }: { post: Comment }) => { const PostMenuDesktop = ({ post }: { post: Comment }) => {
const { t } = useTranslation();
const { cid, isDescription, isRules, link, postCid, subplebbitAddress } = post || {}; const { cid, isDescription, isRules, link, postCid, subplebbitAddress } = post || {};
const commentMediaInfo = getCommentMediaInfo(post); const commentMediaInfo = getCommentMediaInfo(post);
const { thumbnail, type, url } = commentMediaInfo || {}; const { thumbnail, type, url } = commentMediaInfo || {};
const [menuBtnRotated, setMenuBtnRotated] = useState(false); const [menuBtnRotated, setMenuBtnRotated] = useState(false);
const isInCatalogView = isCatalogView(useLocation().pathname); const { blocked, unblock, block } = useBlock({ cid });
const location = useLocation();
const params = useParams();
const isInCatalogView = isCatalogView(location.pathname);
const isInPostPageView = isPostPageView(location.pathname, params);
const { refs, floatingStyles, context } = useFloating({ const { refs, floatingStyles, context } = useFloating({
placement: 'bottom-start', placement: 'bottom-start',
@@ -135,6 +145,17 @@ const PostMenuDesktop = ({ post }: { post: Comment }) => {
<FloatingFocusManager context={context} modal={false}> <FloatingFocusManager context={context} modal={false}>
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}> <div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} onClose={handleClose} />} {cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} onClose={handleClose} />}
{!isInPostPageView && !isDescription && !isRules && (
<div
className={styles.postMenuItem}
onClick={() => {
blocked ? unblock() : block();
handleClose();
}}
>
{blocked ? (postCid === cid ? t('unhide_thread') : t('unhide_post')) : postCid === cid ? t('hide_thread') : t('hide_post')}
</div>
)}
{link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && <ImageSearchButton url={url} onClose={handleClose} />} {link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && <ImageSearchButton url={url} onClose={handleClose} />}
<ViewOnButton cid={cid} isDescription={isDescription} isRules={isRules} subplebbitAddress={subplebbitAddress} onClose={handleClose} /> <ViewOnButton cid={cid} isDescription={isDescription} isRules={isRules} subplebbitAddress={subplebbitAddress} onClose={handleClose} />
</div> </div>
@@ -27,6 +27,7 @@
.postMenu a { .postMenu a {
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
outline: none;
} }
.postMenuItem { .postMenuItem {
@@ -1,6 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { autoUpdate, flip, FloatingFocusManager, offset, shift, useClick, useDismiss, useFloating, useId, useInteractions, useRole } from '@floating-ui/react'; import { autoUpdate, flip, FloatingFocusManager, offset, shift, useClick, useDismiss, useFloating, useId, useInteractions, useRole } from '@floating-ui/react';
import { useTranslation } from 'react-i18next';
import { Comment } from '@plebbit/plebbit-react-hooks'; import { Comment } from '@plebbit/plebbit-react-hooks';
import styles from './post-menu-mobile.module.css'; import styles from './post-menu-mobile.module.css';
import { getCommentMediaInfo } from '../../../../lib/utils/media-utils'; import { getCommentMediaInfo } from '../../../../lib/utils/media-utils';
@@ -15,6 +16,7 @@ interface PostMenuMobileProps {
} }
const CopyLinkButton = ({ cid, subplebbitAddress, onClose }: PostMenuMobileProps) => { const CopyLinkButton = ({ cid, subplebbitAddress, onClose }: PostMenuMobileProps) => {
const { t } = useTranslation();
return ( return (
<div <div
onClick={() => { onClick={() => {
@@ -22,37 +24,38 @@ const CopyLinkButton = ({ cid, subplebbitAddress, onClose }: PostMenuMobileProps
onClose(); onClose();
}} }}
> >
<div className={styles.postMenuItem}>Copy link</div> <div className={styles.postMenuItem}>{t('copy_link')}</div>
</div> </div>
); );
}; };
const ImageSearchButtons = ({ url, onClose }: { url: string; onClose: () => void }) => { const ImageSearchButtons = ({ url, onClose }: { url: string; onClose: () => void }) => {
const { t } = useTranslation();
return ( return (
<div onClick={onClose}> <div onClick={onClose}>
<a href={`https://lens.google.com/uploadbyurl?url=${url}`} target='_blank' rel='noreferrer'> <a href={`https://lens.google.com/uploadbyurl?url=${url}`} target='_blank' rel='noreferrer'>
<div className={styles.postMenuItem}>Search image on Google</div> <div className={styles.postMenuItem}>{t('search_image_on_google')}</div>
</a> </a>
<a href={`https://www.yandex.com/images/search?img_url=${url}&rpt=imageview`} target='_blank' rel='noreferrer'> <a href={`https://www.yandex.com/images/search?img_url=${url}&rpt=imageview`} target='_blank' rel='noreferrer'>
<div className={styles.postMenuItem}>Search image on Yandex</div> <div className={styles.postMenuItem}>{t('search_image_on_yandex')}</div>
</a> </a>
<a href={`https://saucenao.com/search.php?url=${url}`} target='_blank' rel='noreferrer'> <a href={`https://saucenao.com/search.php?url=${url}`} target='_blank' rel='noreferrer'>
<div className={styles.postMenuItem}>Search image on SauceNAO</div> <div className={styles.postMenuItem}>{t('search_image_on_saucenao')}</div>
</a> </a>
</div> </div>
); );
}; };
const ViewOnButtons = ({ cid, isDescription, isRules, subplebbitAddress, onClose }: PostMenuMobileProps) => { const ViewOnButtons = ({ cid, isDescription, isRules, subplebbitAddress, onClose }: PostMenuMobileProps) => {
const { t } = useTranslation();
const viewOnOtherClientLink = `p/${subplebbitAddress}${isDescription || isRules ? '' : `/c/${cid}`}`; const viewOnOtherClientLink = `p/${subplebbitAddress}${isDescription || isRules ? '' : `/c/${cid}`}`;
return ( return (
<div onClick={onClose}> <div onClick={onClose}>
<a href={`https://seedit.app/#/${viewOnOtherClientLink}`} target='_blank' rel='noreferrer'> <a href={`https://seedit.app/#/${viewOnOtherClientLink}`} target='_blank' rel='noreferrer'>
<div className={styles.postMenuItem}>View on Seedit</div> <div className={styles.postMenuItem}>{t('view_on_client', { client: 'Seedit' })}</div>
</a> </a>
<a href={`https://plebones.netlify.app/#/${viewOnOtherClientLink}`} target='_blank' rel='noreferrer'> <a href={`https://plebones.netlify.app/#/${viewOnOtherClientLink}`} target='_blank' rel='noreferrer'>
<div className={styles.postMenuItem}>View on Plebones</div> <div className={styles.postMenuItem}>{t('view_on_client', { client: 'Plebones' })}</div>
</a> </a>
</div> </div>
); );
@@ -1,7 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { Link, useLocation, useParams } from 'react-router-dom'; import { Link, useLocation, useParams } from 'react-router-dom';
import { useAccount } from '@plebbit/plebbit-react-hooks'; import { useAccount, 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 '../post.module.css'; import styles from '../post.module.css';
import { getCommentMediaInfo, getHasThumbnail } from '../../../lib/utils/media-utils'; import { getCommentMediaInfo, getHasThumbnail } from '../../../lib/utils/media-utils';
@@ -19,7 +19,7 @@ import _ from 'lodash';
const PostInfoAndMedia = ({ openReplyModal, post, roles }: PostProps) => { const PostInfoAndMedia = ({ openReplyModal, post, roles }: PostProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { author, cid, link, linkHeight, linkWidth, locked, parentCid, pinned, shortCid, state, subplebbitAddress, timestamp, title } = post || {}; const { author, cid, deleted, link, linkHeight, linkWidth, locked, parentCid, pinned, removed, shortCid, state, subplebbitAddress, timestamp, title } = post || {};
const { isDescription, isRules } = post || {}; // custom properties, not from api const { isDescription, isRules } = post || {}; // custom properties, not from api
const { address, displayName, shortAddress } = author || {}; const { address, displayName, shortAddress } = author || {};
@@ -98,6 +98,7 @@ const PostInfoAndMedia = ({ openReplyModal, post, roles }: PostProps) => {
<CommentMedia <CommentMedia
commentMediaInfo={commentMediaInfo} commentMediaInfo={commentMediaInfo}
isOutOfFeed={isDescription || isRules} // virtuoso wrapper unneeded isOutOfFeed={isDescription || isRules} // virtuoso wrapper unneeded
isDeleted={removed || deleted}
isReply={isReply} isReply={isReply}
linkHeight={linkHeight} linkHeight={linkHeight}
linkWidth={linkWidth} linkWidth={linkWidth}
@@ -110,7 +111,7 @@ const PostInfoAndMedia = ({ openReplyModal, post, roles }: PostProps) => {
}; };
const PostMessageMobile = ({ post }: PostProps) => { const PostMessageMobile = ({ post }: PostProps) => {
const { cid, content, parentCid, postCid, state, subplebbitAddress } = post || {}; const { cid, content, deleted, parentCid, postCid, reason, removed, state, subplebbitAddress } = post || {};
const params = useParams(); const params = useParams();
const location = useLocation(); const location = useLocation();
@@ -138,7 +139,20 @@ const PostMessageMobile = ({ post }: PostProps) => {
<br /> <br />
</> </>
)} )}
{removed ? (
<span className={styles.removedContent}>(THIS POST WAS REMOVED)</span>
) : deleted ? (
<span className={styles.removedContent}>User deleted this post.</span>
) : (
<Markdown content={displayContent} /> <Markdown content={displayContent} />
)}
{(removed || deleted) && reason && (
<span>
<br />
<br />
reason: {reason}
</span>
)}
{!isReply && content.length > 1000 && !isInPostView && ( {!isReply && content.length > 1000 && !isInPostView && (
<span className={styles.abbr}> <span className={styles.abbr}>
<br /> <br />
@@ -157,6 +171,11 @@ const PostMessageMobile = ({ post }: PostProps) => {
}; };
const PostMobile = ({ openReplyModal, post, roles, showAllReplies }: PostProps) => { const PostMobile = ({ openReplyModal, post, roles, showAllReplies }: PostProps) => {
// handle pending mod or author edit
const { editedComment } = useEditedComment({ comment: post });
if (editedComment) {
post = editedComment;
}
const { t } = useTranslation(); const { t } = useTranslation();
const { cid, content, pinned, replyCount, subplebbitAddress } = post || {}; const { cid, content, pinned, replyCount, subplebbitAddress } = post || {};
const { isDescription, isRules } = post || {}; // custom properties, not from api const { isDescription, isRules } = post || {}; // custom properties, not from api
+15 -3
View File
@@ -11,6 +11,8 @@
} }
.hideButtonWrapper { .hideButtonWrapper {
height: 15px;
width: 18px;
padding-right: 5px; padding-right: 5px;
float: left; float: left;
} }
@@ -26,6 +28,10 @@
transform: translateY(-1px); transform: translateY(-1px);
} }
.postDesktopBlocked {
opacity: 0.5;
}
/* clearfix to contain float elements */ /* clearfix to contain float elements */
.postDesktop::after { .postDesktop::after {
content: ""; content: "";
@@ -52,11 +58,11 @@
} }
.postDesktop .postInfo { .postDesktop .postInfo {
padding-top: 3px;
width: 100%; width: 100%;
} }
.postDesktop .checkbox, .replyDesktop .checkbox { .postDesktop .checkbox, .replyDesktop .checkbox {
display: inline-block;
padding: 3px 7px 3px 4px; padding: 3px 7px 3px 4px;
filter: var(--filter80); filter: var(--filter80);
} }
@@ -272,6 +278,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);
word-break: break-all;
} }
.postMobile .postLink { .postMobile .postLink {
@@ -307,7 +314,7 @@
} }
.replyDesktop { .replyDesktop {
padding-bottom: 4px; padding-top: 4px;
} }
.replyDesktop .sideArrows { .replyDesktop .sideArrows {
@@ -322,7 +329,7 @@
border-left: none; border-left: none;
border-top: none; border-top: none;
display: table; display: table;
padding: 2px; padding: 0 2px 2px 2px;
} }
.replyDesktop .postInfo { .replyDesktop .postInfo {
@@ -387,6 +394,11 @@
cursor: pointer; cursor: pointer;
} }
.removedContent {
font-weight: 700;
color: red;
}
@media (max-width: 640px) { @media (max-width: 640px) {
.postDesktop { .postDesktop {
display: none; display: none;
+1
View File
@@ -6,6 +6,7 @@ import PostMobile from './post-mobile';
export interface PostProps { export interface PostProps {
index?: number; index?: number;
isHidden?: boolean;
post?: any; post?: any;
reply?: any; reply?: any;
roles?: Role[]; roles?: Role[];
@@ -71,6 +71,16 @@
cursor: pointer; cursor: pointer;
} }
.footer input[type="checkbox"] {
margin: 0 3px;
vertical-align: text-top;
}
.spoilerButton {
display: inline-block;
padding-top: 2px;
}
.parentCid { .parentCid {
color: var(--post-form-field-input-color); color: var(--post-form-field-input-color);
position: absolute; position: absolute;
+26 -2
View File
@@ -1,4 +1,4 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef, useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Draggable from 'react-draggable'; import Draggable from 'react-draggable';
@@ -8,6 +8,7 @@ import { isValidURL } from '../../lib/utils/url-utils';
import useReply from '../../hooks/use-reply'; import useReply from '../../hooks/use-reply';
import useIsMobile from '../../hooks/use-is-mobile'; import useIsMobile from '../../hooks/use-is-mobile';
import styles from './reply-modal.module.css'; import styles from './reply-modal.module.css';
import { LinkTypePreviewer } from '../post-form';
import _ from 'lodash'; import _ from 'lodash';
interface ReplyModalProps { interface ReplyModalProps {
@@ -24,6 +25,8 @@ const ReplyModal = ({ closeModal, parentCid, scrollY }: ReplyModalProps) => {
const account = useAccount(); const account = useAccount();
const { displayName } = account?.author || {}; const { displayName } = account?.author || {};
const [url, setUrl] = useState('');
const textRef = useRef<HTMLTextAreaElement>(null); const textRef = useRef<HTMLTextAreaElement>(null);
const urlRef = useRef<HTMLInputElement>(null); const urlRef = useRef<HTMLInputElement>(null);
@@ -95,7 +98,15 @@ const ReplyModal = ({ closeModal, parentCid, scrollY }: ReplyModalProps) => {
/> />
</div> </div>
<div className={styles.link}> <div className={styles.link}>
<input type='text' ref={urlRef} placeholder={_.capitalize(t('link'))} onChange={(e) => setContent.link(e.target.value)} /> <input
type='text'
ref={urlRef}
placeholder={_.capitalize(t('link'))}
onChange={(e) => {
setUrl(e.target.value);
setContent.link(e.target.value);
}}
/>
</div> </div>
<div className={styles.content}> <div className={styles.content}>
<span className={styles.parentCid} ref={parentCidRef}> <span className={styles.parentCid} ref={parentCidRef}>
@@ -114,6 +125,19 @@ const ReplyModal = ({ closeModal, parentCid, scrollY }: ReplyModalProps) => {
/> />
</div> </div>
<div className={styles.footer}> <div className={styles.footer}>
{url && (
<>
{t('file_type')}: <LinkTypePreviewer link={url} />{' '}
</>
)}
<span className={styles.spoilerButton}>
[
<label>
<input type='checkbox' onChange={(e) => setContent.spoiler(e.target.checked)} />
{t('spoiler')}?
</label>
]
</span>
<button onClick={onPublishReply}>{t('reply')}</button> <button onClick={onPublishReply}>{t('reply')}</button>
</div> </div>
</div> </div>
@@ -107,7 +107,7 @@ const InterfaceSettings = () => {
return ( return (
<div className={styles.interfaceSettings}> <div className={styles.interfaceSettings}>
<div className={styles.version}> <div className={styles.version}>
Version:{' '} {t('version')}:{' '}
<a href={`https://github.com/plebbit/plebchan/releases/tag/v${packageJson.version}`} target='_blank' rel='noopener noreferrer'> <a href={`https://github.com/plebbit/plebchan/releases/tag/v${packageJson.version}`} target='_blank' rel='noopener noreferrer'>
{packageJson.version} {packageJson.version}
</a> </a>
@@ -44,7 +44,7 @@ const SettingsModal = () => {
<div className={`${styles.setting} ${styles.category}`}> <div className={`${styles.setting} ${styles.category}`}>
<label onClick={() => setShowInterfaceSettings(!showInterfaceSettings)}> <label onClick={() => setShowInterfaceSettings(!showInterfaceSettings)}>
<span className={showInterfaceSettings ? styles.hideButton : styles.showButton} /> <span className={showInterfaceSettings ? styles.hideButton : styles.showButton} />
Interface {t('interface')}
</label> </label>
</div> </div>
{showInterfaceSettings && <InterfaceSettings />} {showInterfaceSettings && <InterfaceSettings />}
+1
View File
@@ -0,0 +1 @@
export { default } from './topbar';
@@ -1,17 +1,17 @@
.boardNavDesktop { .boardNavDesktop {
font-size: var(--boardnav-font-size); font-size: var(--topbar-font-size);
color: var(--boardnav-separator-color); color: var(--topbar-separator-color);
} }
.boardNavDesktop a { .boardNavDesktop a {
color: var(--boardnav-desktop-link-text-color); color: var(--topbar-desktop-link-text-color);
text-decoration: var(--boardnav-desktop-link-text-decoration); text-decoration: var(--topbar-desktop-link-text-decoration);
} }
.boardNavDesktop a:hover { .boardNavDesktop a:hover {
color: var(--boardnav-desktop-link-text-color-hover); color: var(--topbar-desktop-link-text-color-hover);
text-decoration: var(--boardnav-desktop-link-text-decoration-hover); text-decoration: var(--topbar-desktop-link-text-decoration-hover);
} }
.navTopRight { .navTopRight {
@@ -21,31 +21,17 @@
.boardNavMobile { .boardNavMobile {
padding: 2px 4px; padding: 2px 4px;
background-color: var(--boardnav-mobile-background-color); background-color: var(--topbar-mobile-background-color);
border-bottom: var(--boardnav-mobile-border-bottom); border-bottom: var(--topbar-mobile-border-bottom);
font-size: var(--boardnav-mobile-font-size); font-size: var(--topbar-mobile-font-size);
position: fixed; position: fixed;
width: 100%; width: 100%;
z-index: 3; z-index: 3;
animation-name: stickyMenuAnimation; transition: top 0.3s ease-in-out;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: 1;
animation-fill-mode: both;
animation-play-state: paused;
align-items: center; align-items: center;
justify-content: space-around; justify-content: space-around;
} }
@keyframes stickyMenuAnimation {
0% {
top: 0px;
}
100% {
top: -50px;
}
}
.boardNavMobile .pageJump a { .boardNavMobile .pageJump a {
color: var(--button-text-color-mobile); color: var(--button-text-color-mobile);
text-decoration: var(--button-text-decoration-mobile); text-decoration: var(--button-text-decoration-mobile);
@@ -57,7 +43,7 @@
} }
.boardNavMobile select { .boardNavMobile select {
font-size: var(--boardnav-mobile-font-size); font-size: var(--topbar-mobile-font-size);
border: var(--select-border, revert); border: var(--select-border, revert);
background-color: var(--select-background-color, revert); background-color: var(--select-background-color, revert);
color: var(--select-color, revert); color: var(--select-color, revert);
@@ -1,11 +1,13 @@
import { useEffect, useRef, useState } from 'react';
import { useAccountComment } from '@plebbit/plebbit-react-hooks'; import { useAccountComment } from '@plebbit/plebbit-react-hooks';
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'; import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { isAllView, isCatalogView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { isAllView, isCatalogView, isSubscriptionsView } from '../../lib/utils/view-utils';
import styles from './board-nav.module.css'; import styles from './topbar.module.css';
import useDefaultSubplebbits, { categorizeSubplebbits, useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits'; import useDefaultSubplebbits, { categorizeSubplebbits, useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
import { debounce } from 'lodash';
const BoardNavDesktop = () => { const TopBarDesktop = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const location = useLocation(); const location = useLocation();
const isInCatalogView = isCatalogView(location.pathname); const isInCatalogView = isCatalogView(location.pathname);
@@ -30,14 +32,14 @@ const BoardNavDesktop = () => {
{renderSubplebbits(interestsSubs)}] [{renderSubplebbits(randomSubs)}] [{renderSubplebbits(internationalSubs)}] {renderSubplebbits(interestsSubs)}] [{renderSubplebbits(randomSubs)}] [{renderSubplebbits(internationalSubs)}]
</span> </span>
<span className={styles.navTopRight}> <span className={styles.navTopRight}>
[<Link to={!location.pathname.endsWith('settings') ? location.pathname + '/settings' : location.pathname}>{t('settings')}</Link>] [<Link to='/'>{t('home')}</Link> [<Link to={!location.pathname.endsWith('settings') ? location.pathname.replace(/\/$/, '') + '/settings' : location.pathname}>{t('settings')}</Link>] [
] <Link to='/'>{t('home')}</Link>]
</span> </span>
</div> </div>
); );
}; };
const BoardNavMobile = ({ subplebbitAddress }: { subplebbitAddress: string }) => { const TopBarMobile = ({ subplebbitAddress }: { subplebbitAddress: string }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const subplebbitAddresses = useDefaultSubplebbitAddresses(); const subplebbitAddresses = useDefaultSubplebbitAddresses();
@@ -67,77 +69,49 @@ const BoardNavMobile = ({ subplebbitAddress }: { subplebbitAddress: string }) =>
</select> </select>
); );
// navbar animation on scroll
const [visible, setVisible] = useState(true);
const prevScrollPosRef = useRef(0);
useEffect(() => {
const debouncedHandleScroll = debounce(() => {
const currentScrollPos = window.scrollY;
const prevScrollPos = prevScrollPosRef.current;
setVisible(prevScrollPos > currentScrollPos || currentScrollPos < 10);
prevScrollPosRef.current = currentScrollPos;
}, 50);
window.addEventListener('scroll', debouncedHandleScroll);
return () => window.removeEventListener('scroll', debouncedHandleScroll);
}, []);
return ( return (
<div className={styles.boardNavMobile} id='sticky-menu'> <div className={styles.boardNavMobile} id='sticky-menu' style={{ top: visible ? 0 : '-23px' }}>
<div className={styles.boardSelect}> <div className={styles.boardSelect}>
<strong>{t('board')}</strong> <strong>{t('board')}</strong>
{boardSelect} {boardSelect}
</div> </div>
<div className={styles.pageJump}> <div className={styles.pageJump}>
<Link to={useLocation().pathname + '/settings'}>{t('settings')}</Link> <Link to={useLocation().pathname.replace(/\/$/, '') + '/settings'}>{t('settings')}</Link>
<Link to='/'>{t('home')}</Link> <Link to='/'>{t('home')}</Link>
</div> </div>
</div> </div>
); );
}; };
// sticky menu animation const TopBar = () => {
// will trigger more than once with hot reloading during development
if (!window.STICKY_MENU_SCROLL_LISTENER) {
window.STICKY_MENU_SCROLL_LISTENER = true;
const scrollRange = 50; // the animation css px range in stickyMenuAnimation, must also edit css animation 100%: {top}
let currentScrollInRange = 0,
previousScroll = 0;
window.addEventListener('scroll', () => {
// find difference between current and last scroll position
const currentScroll = window.scrollY;
const scrollDifference = currentScroll - previousScroll;
previousScroll = currentScroll;
// find new current scroll in range
const previousScrollInRange = currentScrollInRange;
currentScrollInRange += scrollDifference;
if (currentScrollInRange > scrollRange) {
currentScrollInRange = 0;
} else if (currentScrollInRange < 0) {
currentScrollInRange = 0;
}
// fix mobile overflow scroll bug
if (currentScroll <= 0) {
currentScrollInRange = 0;
}
// no changes
if (currentScrollInRange === previousScrollInRange) {
return;
}
// Get the menu element
const menuElement = document.getElementById('sticky-menu');
if (!menuElement) {
return;
}
// control progress of the animation using negative animation-delay (0 to -1s)
const animationPercent = currentScrollInRange / scrollRange;
menuElement.style.animationDelay = animationPercent * -1 + 's';
});
}
const BoardNav = () => {
const params = useParams(); const params = useParams();
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;
return ( return (
<> <>
<BoardNavDesktop /> <TopBarDesktop />
<BoardNavMobile subplebbitAddress={subplebbitAddress} /> <TopBarMobile subplebbitAddress={subplebbitAddress} />
</> </>
); );
}; };
export default BoardNav; export default TopBar;
+1 -1
View File
@@ -1,8 +1,8 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { ChallengeVerification, Comment, PublishCommentOptions, usePublishComment } from '@plebbit/plebbit-react-hooks'; import { ChallengeVerification, Comment, PublishCommentOptions, usePublishComment } from '@plebbit/plebbit-react-hooks';
import { create } from 'zustand'; import { create } from 'zustand';
import useChallengesStore from './use-challenges';
import { alertChallengeVerificationFailed } from '../lib/utils/challenge-utils'; import { alertChallengeVerificationFailed } from '../lib/utils/challenge-utils';
import useChallengesStore from '../stores/use-challenges-store';
type SetReplyStoreData = { type SetReplyStoreData = {
subplebbitAddress: string; subplebbitAddress: string;
+8
View File
@@ -8,6 +8,12 @@ export const isAllView = (pathname: string): boolean => {
return pathname.startsWith('/p/all'); return pathname.startsWith('/p/all');
}; };
export const isBoardView = (pathname: string, params: ParamsType): boolean => {
// some subs might use emojis in their address, so we need to decode the pathname
const decodedPathname = decodeURIComponent(pathname);
return params.subplebbitAddress ? decodedPathname.startsWith(`/p/${params.subplebbitAddress}`) : false;
};
export const isCatalogView = (pathname: string): boolean => { export const isCatalogView = (pathname: string): boolean => {
return pathname.endsWith('/catalog') || pathname.endsWith('/catalog/settings'); return pathname.endsWith('/catalog') || pathname.endsWith('/catalog/settings');
}; };
@@ -44,6 +50,8 @@ export const isSubscriptionsView = (pathname: string): boolean => {
export const isNotFoundView = (pathname: string, params: ParamsType): boolean => { export const isNotFoundView = (pathname: string, params: ParamsType): boolean => {
return ( return (
!isAllView(pathname) &&
!isBoardView(pathname, params) &&
!isCatalogView(pathname) && !isCatalogView(pathname) &&
!isDescriptionView(pathname, params) && !isDescriptionView(pathname, params) &&
!isHomeView(pathname) && !isHomeView(pathname) &&
+13
View File
@@ -0,0 +1,13 @@
import { create } from 'zustand';
interface FeedResetState {
reset: (() => void) | null;
setResetFunction: (resetFunction: () => void) => void;
}
const useFeedResetStore = create<FeedResetState>((set) => ({
reset: null,
setResetFunction: (resetFunction) => set({ reset: resetFunction }),
}));
export default useFeedResetStore;
+60 -60
View File
@@ -6,16 +6,16 @@
--board-address-font-size: x-small; --board-address-font-size: x-small;
/* board nav */ /* board nav */
--boardnav-font-size: 9pt; --topbar-font-size: 9pt;
--boardnav-separator-color: #b86; --topbar-separator-color: #b86;
--boardnav-desktop-link-text-color: #800; --topbar-desktop-link-text-color: #800;
--boardnav-desktop-link-text-color-hover: #e00; --topbar-desktop-link-text-color-hover: #e00;
--boardnav-desktop-link-text-decoration: none; --topbar-desktop-link-text-decoration: none;
--boardnav-desktop-link-text-decoration-hover: underline; --topbar-desktop-link-text-decoration-hover: underline;
--boardnav-mobile-background-color: #f0e0d6; --topbar-mobile-background-color: #f0e0d6;
--boardnav-mobile-border-bottom: 2px solid #d9c5b7; --topbar-mobile-border-bottom: 2px solid #d9c5b7;
--boardnav-mobile-font-size: 10px; --topbar-mobile-font-size: 10px;
--boardnav-mobile-button-text-color: navy; --topbar-mobile-button-text-color: navy;
/* body */ /* body */
--body-background-base-color: #ffe; --body-background-base-color: #ffe;
@@ -192,16 +192,16 @@
--board-address-text-color: #af0a0f; --board-address-text-color: #af0a0f;
/* board nav */ /* board nav */
--boardnav-font-size: 9pt; --topbar-font-size: 9pt;
--boardnav-separator-color: #89a; --topbar-separator-color: #89a;
--boardnav-desktop-link-text-color: #34345c; --topbar-desktop-link-text-color: #34345c;
--boardnav-desktop-link-text-color-hover: #d00; --topbar-desktop-link-text-color-hover: #d00;
--boardnav-desktop-link-text-decoration: none; --topbar-desktop-link-text-decoration: none;
--boardnav-desktop-link-text-decoration-hover: none; --topbar-desktop-link-text-decoration-hover: none;
--boardnav-mobile-background-color: #d6daf0; --topbar-mobile-background-color: #d6daf0;
--boardnav-mobile-border-bottom: 2px solid #b7c5d9; --topbar-mobile-border-bottom: 2px solid #b7c5d9;
--boardnav-mobile-font-size: 10px; --topbar-mobile-font-size: 10px;
--boardnav-mobile-button-text-color: #34345c; --topbar-mobile-button-text-color: #34345c;
/* body */ /* body */
--body-background-base-color: #eef2ff; --body-background-base-color: #eef2ff;
@@ -375,16 +375,16 @@
--body-font-size: 16px; --body-font-size: 16px;
/* board nav */ /* board nav */
--boardnav-font-size: 11pt; --topbar-font-size: 11pt;
--boardnav-separator-color: none; --topbar-separator-color: none;
--boardnav-desktop-link-text-color: #00e; --topbar-desktop-link-text-color: #00e;
--boardnav-desktop-link-text-color-hover: red; --topbar-desktop-link-text-color-hover: red;
--boardnav-desktop-link-text-decoration: underline; --topbar-desktop-link-text-decoration: underline;
--boardnav-desktop-link-text-decoration-hover: underline; --topbar-desktop-link-text-decoration-hover: underline;
--boardnav-mobile-background-color: #f0e0d6; --topbar-mobile-background-color: #f0e0d6;
--boardnav-mobile-border-bottom: 2px solid #d9c5b7; --topbar-mobile-border-bottom: 2px solid #d9c5b7;
--boardnav-mobile-font-size: 10pt; --topbar-mobile-font-size: 9pt;
--boardnav-mobile-button-text-color: #00f; --topbar-mobile-button-text-color: #00f;
/* board title */ /* board title */
--board-title-font-size: 32px; --board-title-font-size: 32px;
@@ -526,16 +526,16 @@
--board-address-text-color: #af0a0f; --board-address-text-color: #af0a0f;
/* board nav */ /* board nav */
--boardnav-font-size: 11pt; --topbar-font-size: 11pt;
--boardnav-separator-color: none; --topbar-separator-color: none;
--boardnav-desktop-link-text-color: #34345c; --topbar-desktop-link-text-color: #34345c;
--boardnav-desktop-link-text-color-hover: #d00; --topbar-desktop-link-text-color-hover: #d00;
--boardnav-desktop-link-text-decoration: underline; --topbar-desktop-link-text-decoration: underline;
--boardnav-desktop-link-text-decoration-hover: underline; --topbar-desktop-link-text-decoration-hover: underline;
--boardnav-mobile-background-color: #d6daf0; --topbar-mobile-background-color: #d6daf0;
--boardnav-mobile-border-bottom: 2px solid #b7c5d9; --topbar-mobile-border-bottom: 2px solid #b7c5d9;
--boardnav-mobile-font-size: 10pt; --topbar-mobile-font-size: 9pt;
--boardnav-mobile-button-text-color: #34345c; --topbar-mobile-button-text-color: #34345c;
/* body */ /* body */
--body-background-base-color: #eef2ff; --body-background-base-color: #eef2ff;
@@ -689,16 +689,16 @@
--body-font-size: 13px; --body-font-size: 13px;
/* board nav */ /* board nav */
--boardnav-font-size: 9pt; --topbar-font-size: 9pt;
--boardnav-separator-color: #c5c8c6; --topbar-separator-color: #c5c8c6;
--boardnav-desktop-link-text-color: #81a2be; --topbar-desktop-link-text-color: #81a2be;
--boardnav-desktop-link-text-color-hover: #5f89ac; --topbar-desktop-link-text-color-hover: #5f89ac;
--boardnav-desktop-link-text-decoration: none; --topbar-desktop-link-text-decoration: none;
--boardnav-desktop-link-text-decoration-hover: none; --topbar-desktop-link-text-decoration-hover: none;
--boardnav-mobile-background-color: #1d1f21; --topbar-mobile-background-color: #1d1f21;
--boardnav-mobile-border-bottom: 2px solid #282a2e; --topbar-mobile-border-bottom: 2px solid #282a2e;
--boardnav-mobile-font-size: 10px; --topbar-mobile-font-size: 10px;
--boardnav-mobile-button-text-color: #81a2be; --topbar-mobile-button-text-color: #81a2be;
/* board title */ /* board title */
--board-title-font-size: 28px; --board-title-font-size: 28px;
@@ -875,16 +875,16 @@
--body-font-size: 13px; --body-font-size: 13px;
/* board nav */ /* board nav */
--boardnav-font-size: 9pt; --topbar-font-size: 9pt;
--boardnav-separator-color: #333; --topbar-separator-color: #333;
--boardnav-desktop-link-text-color: #f60; --topbar-desktop-link-text-color: #f60;
--boardnav-desktop-link-text-color-hover: #f30; --topbar-desktop-link-text-color-hover: #f30;
--boardnav-desktop-link-text-decoration: none; --topbar-desktop-link-text-decoration: none;
--boardnav-desktop-link-text-decoration-hover: none; --topbar-desktop-link-text-decoration-hover: none;
--boardnav-mobile-background-color: #ddd; --topbar-mobile-background-color: #ddd;
--boardnav-mobile-border-bottom: 2px solid #ccc; --topbar-mobile-border-bottom: 2px solid #ccc;
--boardnav-mobile-font-size: 10px; --topbar-mobile-font-size: 10px;
--boardnav-mobile-button-text-color: #f60; --topbar-mobile-button-text-color: #f60;
/* board title */ /* board title */
--board-title-font-size: 28px; --board-title-font-size: 28px;
+7 -1
View File
@@ -8,6 +8,7 @@ import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits'; import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
import useFeedStateString from '../../hooks/use-feed-state-string'; import useFeedStateString from '../../hooks/use-feed-state-string';
import useReplyModal from '../../hooks/use-reply-modal'; import useReplyModal from '../../hooks/use-reply-modal';
import useFeedResetStore from '../../stores/use-feed-reset-store';
import LoadingEllipsis from '../../components/loading-ellipsis'; import LoadingEllipsis from '../../components/loading-ellipsis';
import Post from '../../components/post'; import Post from '../../components/post';
import ReplyModal from '../../components/reply-modal'; import ReplyModal from '../../components/reply-modal';
@@ -40,7 +41,12 @@ const Board = () => {
}, [isInAllView, isInSubscriptionsView, subplebbitAddress, defaultSubplebbitAddresses, subscriptions]); }, [isInAllView, isInSubscriptionsView, subplebbitAddress, defaultSubplebbitAddresses, subscriptions]);
const sortType = 'active'; const sortType = 'active';
const { feed, hasMore, loadMore } = useFeed({ subplebbitAddresses, sortType }); const { feed, hasMore, loadMore, reset } = useFeed({ subplebbitAddresses, sortType });
const setResetFunction = useFeedResetStore((state) => state.setResetFunction);
useEffect(() => {
setResetFunction(reset);
}, [reset, setResetFunction]);
const subplebbit = useSubplebbit({ subplebbitAddress }); const subplebbit = useSubplebbit({ subplebbitAddress });
const { createdAt, description, rules, shortAddress, state, suggested } = subplebbit || {}; const { createdAt, description, rules, shortAddress, state, suggested } = subplebbit || {};
+9 -5
View File
@@ -8,6 +8,7 @@ import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbi
import useFeedStateString from '../../hooks/use-feed-state-string'; import useFeedStateString from '../../hooks/use-feed-state-string';
import { useMultisubMetadata } from '../../hooks/use-default-subplebbits'; import { useMultisubMetadata } from '../../hooks/use-default-subplebbits';
import useWindowWidth from '../../hooks/use-window-width'; import useWindowWidth from '../../hooks/use-window-width';
import useFeedResetStore from '../../stores/use-feed-reset-store';
import CatalogRow from '../../components/catalog-row'; import CatalogRow from '../../components/catalog-row';
import LoadingEllipsis from '../../components/loading-ellipsis'; import LoadingEllipsis from '../../components/loading-ellipsis';
import SettingsModal from '../../components/settings-modal'; import SettingsModal from '../../components/settings-modal';
@@ -61,7 +62,6 @@ const useFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolean, subp
return _feed; return _feed;
}, [feed, description, rules, address, isFeedLoaded, createdAt, title, shortAddress, avatarUrl, t, isInAllView, multisub]); }, [feed, description, rules, address, isFeedLoaded, createdAt, title, shortAddress, avatarUrl, t, isInAllView, multisub]);
// Memoize rows calculation, ensuring it updates on changes to the modified feed or column count
const rows = useMemo(() => { const rows = useMemo(() => {
const rows = []; const rows = [];
for (let i = 0; i < feedWithDescriptionAndRules.length; i += columnCount) { for (let i = 0; i < feedWithDescriptionAndRules.length; i += columnCount) {
@@ -102,7 +102,12 @@ const Catalog = () => {
// eslint-disable-next-line // eslint-disable-next-line
const postsPerPage = useMemo(() => (columnCount <= 2 ? 10 : columnCount === 3 ? 15 : columnCount === 4 ? 20 : 25), []); const postsPerPage = useMemo(() => (columnCount <= 2 ? 10 : columnCount === 3 ? 15 : columnCount === 4 ? 20 : 25), []);
const { feed, hasMore, loadMore } = useFeed({ subplebbitAddresses, sortType: 'active', postsPerPage }); const { feed, hasMore, loadMore, reset } = useFeed({ subplebbitAddresses, sortType: 'active', postsPerPage });
const setResetFunction = useFeedResetStore((state) => state.setResetFunction);
useEffect(() => {
setResetFunction(reset);
}, [reset, setResetFunction]);
const subplebbit = useSubplebbit({ subplebbitAddress }); const subplebbit = useSubplebbit({ subplebbitAddress });
const { shortAddress, state, title } = subplebbit || {}; const { shortAddress, state, title } = subplebbit || {};
@@ -130,10 +135,9 @@ const Catalog = () => {
return <div className={styles.footer}>{footerContent}</div>; return <div className={styles.footer}>{footerContent}</div>;
}; };
const isFeedloaded = feed.length > 0 || state === 'failed'; const isFeedLoaded = feed.length > 0 || state === 'failed';
// split feed into rows const rows = useFeedRows(columnCount, feed, isFeedLoaded, subplebbit);
const rows = useFeedRows(columnCount, feed, isFeedloaded, subplebbit);
// save the last Virtuoso state to restore it when navigating back // save the last Virtuoso state to restore it when navigating back
const virtuosoRef = useRef<VirtuosoHandle | null>(null); const virtuosoRef = useRef<VirtuosoHandle | null>(null);
+2 -3
View File
@@ -119,7 +119,6 @@
} }
.list a { .list a {
display: inline-block;
color: var(--homepage-box-link-text-color); color: var(--homepage-box-link-text-color);
text-decoration: var(--homepage-box-link-text-decoration); text-decoration: var(--homepage-box-link-text-decoration);
} }
@@ -140,7 +139,7 @@
background-image: url('/public/assets/icons/offline.png'); background-image: url('/public/assets/icons/offline.png');
display: inline-block; display: inline-block;
vertical-align: text-top; vertical-align: text-top;
margin-left: 2px; margin-right: 2px;
width: 13px; width: 13px;
height: 13px; height: 13px;
background-repeat: no-repeat; background-repeat: no-repeat;
@@ -161,7 +160,7 @@
float: left; float: left;
width: 168px; width: 168px;
margin-right: 15px; margin-right: 15px;
overflow: hidden; word-break: break-word;
} }
.boardsBoxContent h3 { .boardsBoxContent h3 {
+10 -9
View File
@@ -3,6 +3,7 @@ import styles from './home.module.css';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { Comment, Subplebbit, useAccount, useAccountSubplebbits, useSubplebbits } from '@plebbit/plebbit-react-hooks'; import { Comment, Subplebbit, useAccount, useAccountSubplebbits, useSubplebbits } from '@plebbit/plebbit-react-hooks';
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
import packageJson from '../../../package.json'; import packageJson from '../../../package.json';
import useDefaultSubplebbits, { useMultisubMetadata, useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits'; import useDefaultSubplebbits, { useMultisubMetadata, useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
import usePopularPosts from '../../hooks/use-popular-posts'; import usePopularPosts from '../../hooks/use-popular-posts';
@@ -73,15 +74,15 @@ const InfoBox = () => {
const Board = ({ isOffline, subplebbit }: { isOffline: boolean; subplebbit: Subplebbit }) => { const Board = ({ isOffline, subplebbit }: { isOffline: boolean; subplebbit: Subplebbit }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { address, title, tags } = subplebbit; const { address, title, tags } = subplebbit || {};
const nsfwTags = ['adult', 'gore']; const nsfwTags = ['adult', 'gore'];
const nsfwTag = tags.find((tag: string) => nsfwTags.includes(tag)); const nsfwTag = tags.find((tag: string) => nsfwTags.includes(tag));
return ( return (
<div className={styles.subplebbit} key={address}> <div className={styles.subplebbit} key={address}>
{isOffline && <span className={styles.offlineIcon} />}
<Link to={`/p/${address}`}>{title || address}</Link> <Link to={`/p/${address}`}>{title || address}</Link>
{nsfwTag && <span className={styles.nsfw}> ({t(nsfwTag)})</span>} {nsfwTag && <span className={styles.nsfw}> ({t(nsfwTag)})</span>}
{isOffline && <span className={styles.offlineIcon} />}
</div> </div>
); );
}; };
@@ -93,13 +94,13 @@ const Boards = ({ multisub, subplebbits }: { multisub: Subplebbit[]; subplebbits
const { accountSubplebbits } = useAccountSubplebbits(); const { accountSubplebbits } = useAccountSubplebbits();
const accountSubplebbitAddresses = Object.keys(accountSubplebbits); const accountSubplebbitAddresses = Object.keys(accountSubplebbits);
const plebbitSubs = multisub.filter((sub) => sub.tags.includes('plebbit')); const plebbitSubs = multisub.filter((sub) => sub.tags?.includes('plebbit'));
const interestsSubs = multisub.filter( const interestsSubs = multisub.filter(
(sub) => sub.tags.includes('topic') && !sub.tags.includes('plebbit') && !sub.tags.includes('country') && !sub.tags.includes('international'), (sub) => sub.tags?.includes('topic') && !sub.tags?.includes('plebbit') && !sub.tags?.includes('country') && !sub.tags?.includes('international'),
); );
const randomSubs = multisub.filter((sub) => sub.tags.includes('random') && !sub.tags.includes('plebbit')); const randomSubs = multisub.filter((sub) => sub.tags?.includes('random') && !sub.tags?.includes('plebbit'));
const internationalSubs = multisub.filter((sub) => sub.tags.includes('international') || sub.tags.includes('country')); const internationalSubs = multisub.filter((sub) => sub.tags?.includes('international') || sub.tags?.includes('country'));
const projectsSubs = multisub.filter((sub) => sub.tags.includes('project') && !sub.tags.includes('plebbit') && !sub.tags.includes('topic')); const projectsSubs = multisub.filter((sub) => sub.tags?.includes('project') && !sub.tags?.includes('plebbit') && !sub.tags?.includes('topic'));
const isSubOffline = (address: string) => { const isSubOffline = (address: string) => {
const subplebbit = subplebbits && subplebbits.find((sub: Subplebbit) => sub?.address === address); const subplebbit = subplebbits && subplebbits.find((sub: Subplebbit) => sub?.address === address);
@@ -166,7 +167,7 @@ const Boards = ({ multisub, subplebbits }: { multisub: Subplebbit[]; subplebbits
{subscriptions.length > 0 {subscriptions.length > 0
? subscriptions.map((address: string, index: number) => ( ? subscriptions.map((address: string, index: number) => (
<div className={styles.subplebbit} key={index}> <div className={styles.subplebbit} key={index}>
<Link to={`/p/${address}`}>{address}</Link> <Link to={`/p/${address}`}>p/{address && Plebbit.getShortAddress(address)}</Link>
</div> </div>
)) ))
: t('not_subscribed')} : t('not_subscribed')}
@@ -176,7 +177,7 @@ const Boards = ({ multisub, subplebbits }: { multisub: Subplebbit[]; subplebbits
{accountSubplebbitAddresses.length > 0 {accountSubplebbitAddresses.length > 0
? accountSubplebbitAddresses.map((address: string, index: number) => ( ? accountSubplebbitAddresses.map((address: string, index: number) => (
<div className={styles.subplebbit} key={index}> <div className={styles.subplebbit} key={index}>
<Link to={`/p/${address}`}>p/{address}</Link> <Link to={`/p/${address}`}>p/{address && Plebbit.getShortAddress(address)}</Link>
</div> </div>
)) ))
: t('not_moderating')} : t('not_moderating')}