Merge pull request #660 from plebbit/development

Development
This commit is contained in:
Tom (plebeius.eth)
2025-02-23 16:42:16 +01:00
committed by GitHub
56 changed files with 610 additions and 508 deletions
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "تاريخ الإنشاء",
"sort_by": "ترتيب حسب",
"time_filter": "تصفية الوقت",
"subplebbit_offline_info": "قد يكون الرد على الرسائل غير متاح حالياً وقد تفشل النشر.",
"posts_last_synced_info": "آخر تزامن الرسائل {{time}}، قد يكون الرد على الرسائل غير متاح حالياً وقد تفشل النشر.",
"subplebbit_offline_info": "قد يكون المنتدى غير متصل وقد تفشل النشر",
"posts_last_synced_info": "آخر المشاركات تم مزامنتها {{time}} ، قد يكون المنتدى غير متصل وقد تفشل النشر.",
"newer_than": "أحدث من",
"no_threads": "لا توجد مواضيع",
"unblock": "إلغاء الحظر",
@@ -282,5 +282,7 @@
"node_stats": "إحصائيات العقدة",
"loading_default_boards": "جار تحميل قائمة اللوحات الافتراضية",
"loading_subscriptions": "جارٍ تحميل الاشتراكات",
"create_board": "إنشاء لوحة"
"create_board": "إنشاء لوحة",
"enter_board_address": "أدخل عنوان أي لوحة",
"board_not_reachable": "اللوحة غير قابلة للوصول"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "তৈরি তারিখ",
"sort_by": "দ্বারা বাছাই করুন",
"time_filter": "সময় ফিল্টার",
"subplebbit_offline_info": "সাবপ্লেবিট অফলাইন তে পারে এবং প্রকাশনা ব্যর্থ হতে পারে",
"posts_last_synced_info": "পোস্টগুলি সর্বশেষ সিঙ্ক হয়েছে {{time}}, সাবপ্লেবিট অফলাইনতে পারে এবং প্রকাশনা ব্যর্থ হতে পারে।",
"subplebbit_offline_info": "বোর্ডটি অফলাইন থাকতে পারে এবং প্রকাশনা ব্যর্থ হতে পারে",
"posts_last_synced_info": "সর্বশেষ পোস্টগুলি {{time}} সময় সিঙ্ক করা হয়েছে, বোর্ডটি অফলাইনে থাকতে পারে এবং প্রকাশনা ব্যর্থ হতে পারে।",
"newer_than": "নতুন পরে",
"no_threads": "কোন থ্রেড নেই",
"unblock": "অনবন্ধ করুন",
@@ -282,5 +282,7 @@
"node_stats": "নোড পরিসংখ্যান",
"loading_default_boards": "ডিফল্ট বোর্ড তালিকা লোড হচ্ছে",
"loading_subscriptions": "সাবস্ক্রিপশন লোড হচ্ছে",
"create_board": "বোর্ড তৈরি করুন"
"create_board": "বোর্ড তৈরি করুন",
"enter_board_address": "যেকোনো বোর্ডের ঠিকানা লিখুন",
"board_not_reachable": "বোর্ডটি অ্যাক্সেসযোগ্য নয়"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Datum vytvoření",
"sort_by": "Řadit podle",
"time_filter": "Filtr času",
"subplebbit_offline_info": "Subplebbit může být offline a zveřejnění může selhat.",
"posts_last_synced_info": "Poslední synchronizace příspěvků {{time}}, subplebbit může být offline a zveřejňování může selhat.",
"subplebbit_offline_info": "Deska může být offline a publikování může selhat",
"posts_last_synced_info": "Poslední příspěvky synchronizovány {{time}}, deska může být offline a publikování může selhat.",
"newer_than": "novější než",
"no_threads": "Žádná témata",
"unblock": "Odblokovat",
@@ -282,5 +282,7 @@
"node_stats": "Statistiky uzlu",
"loading_default_boards": "Načítání seznamu výchozích tabulí",
"loading_subscriptions": "Načítání odběrů",
"create_board": "Vytvořit desku"
"create_board": "Vytvořit desku",
"enter_board_address": "Zadejte adresu libovolné desky",
"board_not_reachable": "Deska není dostupná"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Oprettelsesdato",
"sort_by": "Sorter efter",
"time_filter": "Tidsfilter",
"subplebbit_offline_info": "Subplebbiten kan være offline, og offentliggørelse kan mislykkes.",
"posts_last_synced_info": "Seneste synkroniserede indlæg {{time}}, subplebbiten kan være offline, og offentliggørelse kan mislykkes.",
"subplebbit_offline_info": "Boardet kan være offline, og offentliggørelse kan mislykkes",
"posts_last_synced_info": "Sidste indlæg synkroniseret {{time}}, tavlen kan være offline og offentliggørelse kan fejle.",
"newer_than": "nyere end",
"no_threads": "Ingen tråde",
"unblock": "Fjern blokering",
@@ -282,5 +282,7 @@
"node_stats": "Node-statistik",
"loading_default_boards": "Indlæser standard tavleliste",
"loading_subscriptions": "Indlæser abonnementer",
"create_board": "Opret tavle"
"create_board": "Opret tavle",
"enter_board_address": "Indtast enhver tavleadresse",
"board_not_reachable": "Boardet er ikke tilgængeligt"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Erstellungsdatum",
"sort_by": "Sortieren nach",
"time_filter": "Zeitfilter",
"subplebbit_offline_info": "Der Subplebbit könnte offline sein und das Veröffentlichen könnte fehlschlagen.",
"posts_last_synced_info": "Beiträge zuletzt synchronisiert {{time}}, der Subplebbit könnte offline sein und das Veröffentlichen könnte fehlschlagen.",
"subplebbit_offline_info": "Das Board könnte offline sein und die Veröffentlichung könnte fehlschlagen",
"posts_last_synced_info": "Letzte Beiträge synchronisiert {{time}}, das Board könnte offline sein und das Veröffentlichen könnte fehlschlagen.",
"newer_than": "neuer als",
"no_threads": "Keine Threads",
"unblock": "Entsperren",
@@ -282,5 +282,7 @@
"node_stats": "Knotenstatistiken",
"loading_default_boards": "Laden der Standard-Boards-Liste",
"loading_subscriptions": "Lade Abonnements",
"create_board": "Board erstellen"
"create_board": "Board erstellen",
"enter_board_address": "Geben Sie eine beliebige Board-Adresse ein",
"board_not_reachable": "Board ist nicht erreichbar"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Ημερομηνία δημιουργίας",
"sort_by": "Ταξινόμηση κατά",
"time_filter": "Φίλτρο χρόνου",
"subplebbit_offline_info": "Το subplebbit ενδέχεται να είναι εκτός σύνδεσης και η δημοσίευση μπορεί να αποτύχει.",
"posts_last_synced_info": "Τελευταία συγχρονισμένες αναρτήσεις {{time}}, το subplebbit ενδέχεται να είναι εκτός σύνδεσης και η δημοσίευση μπορεί να αποτύχει.",
"subplebbit_offline_info": "Η φόρουμ μπορεί να είναι εκτός σύνδεσης και η δημοσίευση ενδέχεται να αποτύχει",
"posts_last_synced_info": "Τελευταία δημοσιεύματα συγχρονίστηκαν {{time}}, το φόρουμ μπορεί να είναι εκτός σύνδεσης και η δημοσίευση μπορεί να αποτύχει.",
"newer_than": "πιο πρόσφατο από",
"no_threads": "Δεν υπάρχουν θέματα",
"unblock": "Ξεκλείδωμα",
@@ -282,5 +282,7 @@
"node_stats": "Στατιστικά κόμβου",
"loading_default_boards": "Φόρτωση της λίστας προεπιλεγμένων πάνελ",
"loading_subscriptions": "Φορτώνει συνδρομές",
"create_board": "Δημιουργία πίνακα"
"create_board": "Δημιουργία πίνακα",
"enter_board_address": "Εισάγετε οποιαδήποτε διεύθυνση πίνακα",
"board_not_reachable": "Ο πίνακας δεν είναι προσβάσιμος"
}
+7 -5
View File
@@ -164,8 +164,8 @@
"creation_date": "Creation date",
"sort_by": "Sort by",
"time_filter": "Time Filter",
"subplebbit_offline_info": "The subplebbit might be offline and publishing might fail.",
"posts_last_synced_info": "Posts last synced {{time}}, the subplebbit might be offline and publishing might fail.",
"subplebbit_offline_info": "The board might be offline and publishing might fail",
"posts_last_synced_info": "Posts last synced {{time}}, the board might be offline and publishing might fail.",
"newer_than": "newer than",
"no_threads": "No threads",
"unblock": "Unblock",
@@ -237,8 +237,8 @@
"no_board_selected_warning": "Please select a board before posting.",
"invalid_url_alert": "The provided link is not a valid URL.",
"empty_comment_alert": "Cannot post empty comment.",
"more_threads_last_week": "{{count}} threads since {{currentTimeFilterName}} — [<1>Show more threads from last week</1>]",
"more_threads_last_month": "{{count}} threads since {{currentTimeFilterName}} — [<1>Show more threads from last month</1>]",
"more_threads_last_week": "{{count}} threads since {{currentTimeFilterName}} — [<1>Show More Threads from Last Week</1>]",
"more_threads_last_month": "{{count}} threads since {{currentTimeFilterName}} — [<1>Show More Threads from Last Month</1>]",
"newer_threads_available": "Newer threads available — [<1>Refresh</1>]",
"stored_locally": "Stored locally ({{location}}), not synced across devices",
"choose_one": "Choose one:",
@@ -282,5 +282,7 @@
"node_stats": "Node stats",
"loading_default_boards": "Loading default boards list",
"loading_subscriptions": "Loading subscriptions",
"create_board": "Create Board"
"create_board": "Create Board",
"enter_board_address": "Enter any board address",
"board_not_reachable": "Board is not reachable"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Fecha de creación",
"sort_by": "Ordenar por",
"time_filter": "Filtro de tiempo",
"subplebbit_offline_info": "El subplebbit podría estar fuera de línea y la publicación podría fallar.",
"posts_last_synced_info": "Últimas publicaciones sincronizadas {{time}}, el subplebbit podría estar fuera de línea y la publicación podría fallar.",
"subplebbit_offline_info": "El tablero podría estar fuera de línea y la publicación podría fallar",
"posts_last_synced_info": "Últimas publicaciones sincronizadas {{time}}, el tablero podría estar fuera de línea y la publicación podría fallar.",
"newer_than": "más reciente que",
"no_threads": "No hay hilos",
"unblock": "Desbloquear",
@@ -282,5 +282,7 @@
"node_stats": "Estadísticas del nodo",
"loading_default_boards": "Cargando lista de tableros predeterminados",
"loading_subscriptions": "Cargando suscripciones",
"create_board": "Crear tablero"
"create_board": "Crear tablero",
"enter_board_address": "Ingrese cualquier dirección de tablero",
"board_not_reachable": "El tablero no es accesible"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "تاریخ ایجاد",
"sort_by": "مرتب‌سازی بر اساس",
"time_filter": "فیلتر زمانی",
"subplebbit_offline_info": "زیرپلبیت ممکن است آفلاین باشد و انتشار ممکن است شکست خورده باشد.",
"posts_last_synced_info": "آخرین پست‌ها همگام‌سازی شده‌اند {{time}}، زیرپلبیت ممکن است آفلاین باشد و انتشار ممکن است شکست خورده باشد.",
"subplebbit_offline_info": "تابلو ممکن است آفلاین باشد و انتشار ممکن است شکست بخورد",
"posts_last_synced_info": "آخرین پست‌ها همگام‌سازی شد {{time}} ، ممکن است انجمن آفلاین باشد و انتشار ممکن است شکست بخورد.",
"newer_than": "جدیدتر از",
"no_threads": "هیچ موضوعی وجود ندارد",
"unblock": "رفع مسدودیت",
@@ -282,5 +282,7 @@
"node_stats": "آمار گره",
"loading_default_boards": "در حال بارگذاری فهرست تخته‌های پیش‌فرض",
"loading_subscriptions": "در حال بارگذاری اشترا‌ک‌ها",
"create_board": "ایجاد تابلو"
"create_board": "ایجاد تابلو",
"enter_board_address": "آدرس هر تابلو را وارد کنید",
"board_not_reachable": "تابلو قابل دسترسی نیست"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Luomispäivämäärä",
"sort_by": "Järjestä",
"time_filter": "Aikasuodatin",
"subplebbit_offline_info": "Subplebbit voi olla offline-tilassa, ja julkaisu voi epäonnistua.",
"posts_last_synced_info": "Viimeksi synkronoidut viestit {{time}}, subplebbit voi olla offline-tilassa, ja julkaisu voi epäonnistua.",
"subplebbit_offline_info": "Taulu voi olla offline-tilassa ja julkaiseminen voi epäonnistua",
"posts_last_synced_info": "Viimeisimmät julkaisut synkronoitu {{time}}, taulu saattaa olla offline-tilassa ja julkaiseminen saattaa epäonnistua.",
"newer_than": "uudempi kuin",
"no_threads": "Ei ketjuja",
"unblock": "Poista estäminen",
@@ -282,5 +282,7 @@
"node_stats": "Solmun tilastot",
"loading_default_boards": "Ladataan oletuslautalistaa",
"loading_subscriptions": "Ladataan tilauksia",
"create_board": "Luo taulu"
"create_board": "Luo taulu",
"enter_board_address": "Syötä mikä tahansa taulun osoite",
"board_not_reachable": "Taulua ei ole käytettävissä"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Petsa ng Paglikha",
"sort_by": "Ayusin ayon sa",
"time_filter": "Time Filter",
"subplebbit_offline_info": "Maaaring offline ang subplebbit at maaaring mabigo ang paglalathala.",
"posts_last_synced_info": "Huling naka-sync na mga post {{time}}, maaaring offline ang subplebbit at maaaring mabigo ang paglalathala.",
"subplebbit_offline_info": "Maaaring offline ang board at maaaring mabigo ang pag-publish",
"posts_last_synced_info": "Huling na-sync na mga post {{time}}, maaaring offline ang board at maaaring mabigo ang pag-publish.",
"newer_than": "mas bago kaysa",
"no_threads": "Walang mga thread",
"unblock": "I-unblock",
@@ -282,5 +282,7 @@
"node_stats": "Istatistika ng node",
"loading_default_boards": "Naglo-load ng default na listahan ng mga board",
"loading_subscriptions": "Naglo-load ng mga subscription",
"create_board": "Gumawa ng board"
"create_board": "Gumawa ng board",
"enter_board_address": "Ipasok ang anumang address ng board",
"board_not_reachable": "Hindi maa-access ang board"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Date de création",
"sort_by": "Trier par",
"time_filter": "Filtre temporel",
"subplebbit_offline_info": "Le subplebbit pourrait être hors ligne et la publication pourrait échouer.",
"posts_last_synced_info": "Dernières publications synchronisées {{time}}, le subplebbit pourrait être hors ligne et la publication pourrait échouer.",
"subplebbit_offline_info": "Le forum pourrait être hors ligne et la publication pourrait échouer",
"posts_last_synced_info": "Derniers messages synchronisés {{time}}, le forum pourrait être hors ligne et la publication pourrait échouer.",
"newer_than": "plus récent que",
"no_threads": "Pas de fils",
"unblock": "Débloquer",
@@ -282,5 +282,7 @@
"node_stats": "Statistiques du nœud",
"loading_default_boards": "Chargement de la liste des panneaux par défaut",
"loading_subscriptions": "Chargement des abonnements",
"create_board": "Créer un tableau"
"create_board": "Créer un tableau",
"enter_board_address": "Entrez n'importe quelle adresse du forum",
"board_not_reachable": "Le tableau n'est pas accessible"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "תאריך יצירה",
"sort_by": "מיין לפי",
"time_filter": "סינון זמן",
"subplebbit_offline_info": סאבפלביט עשוי להיות לא מקוון והפרסום עשוי להכשל.",
"posts_last_synced_info": "הפוסטים סונכרנו לאחרונה {{time}}, הסאבפלביט עשוי להיות לא מקוון והפרסום עשוי להכשל.",
"subplebbit_offline_info": פורום עשוי להיות לא זמין וייתכן שפרסום ייכשל",
"posts_last_synced_info": "הפוסטים סונכרנו לאחרונה {{time}}, ייתכן שהפורום לא זמין ופרסום עשוי להיכשל.",
"newer_than": "חדש יותר מ",
"no_threads": "אין נושאים",
"unblock": "בטל חסימה",
@@ -282,5 +282,7 @@
"node_stats": "סטטיסטיקות צומת",
"loading_default_boards": "טוען את רשימת הלוחות ברירת המחדל",
"loading_subscriptions": "טוען מנויים",
"create_board": "צור לוח"
"create_board": "צור לוח",
"enter_board_address": "הזן כתובת לוח כלשהי",
"board_not_reachable": "הלוח לא נגיש"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "निर्माण तिथि",
"sort_by": "क्रमबद्ध करें द्वारा",
"time_filter": "समय फ़िल्टर",
"subplebbit_offline_info": "सबप्लेबिट ऑफलाइन हो सकता है और प्रकाशन विफल हो सकता है",
"posts_last_synced_info": "आखिरी बार सिंक की गई पोस्ट {{time}}, सबप्लेबिट ऑफलाइन हो सकता है और प्रकाशन विफल हो सकता है।",
"subplebbit_offline_info": "बोर्ड ऑफलाइन हो सकता है और प्रकाशन विफल हो सकता है",
"posts_last_synced_info": "पोस्ट्स अंतिम बार सिंक किए गए {{time}}, बोर्ड ऑफलाइन हो सकता है और प्रकाशन विफल हो सकता है।",
"newer_than": "से नई",
"no_threads": "कोई थ्रेड नहीं है",
"unblock": "अनब्लॉक",
@@ -282,5 +282,7 @@
"node_stats": "नोड आँकड़े",
"loading_default_boards": "डिफ़ॉल्ट बोर्ड सूची लोड हो रही है",
"loading_subscriptions": "सब्सक्रिप्शन लोड हो रहे हैं",
"create_board": "बोर्ड बनाएं"
"create_board": "बोर्ड बनाएं",
"enter_board_address": "किसी भी बोर्ड का पता दर्ज करें",
"board_not_reachable": "बोर्ड पहुँच योग्य नहीं है"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Létrehozás dátuma",
"sort_by": "Rendezés",
"time_filter": "Idő szűrő",
"subplebbit_offline_info": "A subplebbit offline lehet, és a közzététel sikertelen lehet.",
"posts_last_synced_info": "Legutóbb szinkronizált bejegyzések {{time}}, a subplebbit offline lehet, és a közzététel sikertelen lehet.",
"subplebbit_offline_info": "A tábla offline lehet, és a közzététel sikertelen lehet",
"posts_last_synced_info": "Legutóbbi bejegyzések szinkronizálva {{time}}, a tábla offline lehet, és a közzététel sikertelen lehet.",
"newer_than": "újabb, mint",
"no_threads": "Nincsenek témák",
"unblock": "Blokkolás feloldása",
@@ -282,5 +282,7 @@
"node_stats": "Csomópont statisztikák",
"loading_default_boards": "Alapértelmezett táblák listájának betöltése",
"loading_subscriptions": "Előfizetések betöltése",
"create_board": "Hozzon létre táblát"
"create_board": "Hozzon létre táblát",
"enter_board_address": "Adjon meg bármilyen táblázat címet",
"board_not_reachable": "A tábla nem elérhető"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Tanggal pembuatan",
"sort_by": "Urutkan berdasarkan",
"time_filter": "Filter Waktu",
"subplebbit_offline_info": "Subplebbit mungkin offline dan penerbitan mungkin gagal.",
"posts_last_synced_info": "Posting terakhir disinkronkan {{time}}, subplebbit mungkin offline dan penerbitan mungkin gagal.",
"subplebbit_offline_info": "Papan bisa jadi offline dan publikasi bisa gagal",
"posts_last_synced_info": "Posting terakhir disinkronkan {{time}}, papan mungkin offline dan publikasi bisa gagal.",
"newer_than": "baru daripada",
"no_threads": "Tidak ada thread",
"unblock": "Buka blokir",
@@ -282,5 +282,7 @@
"node_stats": "Statistik node",
"loading_default_boards": "Memuat daftar papan default",
"loading_subscriptions": "Memuat langganan",
"create_board": "Buat papan"
"create_board": "Buat papan",
"enter_board_address": "Masukkan alamat papan apa saja",
"board_not_reachable": "Papan tidak dapat dijangkau"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Data di creazione",
"sort_by": "Ordina per",
"time_filter": "Filtro temporale",
"subplebbit_offline_info": "Il subplebbit potrebbe essere offline e la pubblicazione potrebbe fallire.",
"posts_last_synced_info": "Ultimi post sincronizzati {{time}}, il subplebbit potrebbe essere offline e la pubblicazione potrebbe fallire.",
"subplebbit_offline_info": "La board potrebbe essere offline e la pubblicazione potrebbe fallire",
"posts_last_synced_info": "Ultimi post sincronizzati {{time}}, la board potrebbe essere offline e la pubblicazione potrebbe fallire.",
"newer_than": "più recente di",
"no_threads": "Nessun thread",
"unblock": "Sblocca",
@@ -282,5 +282,7 @@
"node_stats": "Statistiche del nodo",
"loading_default_boards": "Caricamento della lista delle board predefinite",
"loading_subscriptions": "Caricamento abbonamenti",
"create_board": "Crea Board"
"create_board": "Crea Board",
"enter_board_address": "Inserisci indirizzo di qualsiasi board",
"board_not_reachable": "Board non raggiungibile"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "作成日",
"sort_by": "並べ替え",
"time_filter": "時間フィルター",
"subplebbit_offline_info": "サブプレビットがオフラインになっている可能性があり、公開が失敗することがあります",
"posts_last_synced_info": "最後に同期された投稿 {{time}}, サブプレビットがオフラインになっている可能性があり、公開失敗することがあります。",
"subplebbit_offline_info": "ボードはオフラインの可能性があり、公開が失敗する可能性があります",
"posts_last_synced_info": "最後に同期された投稿 {{time}}、ボードはオフラインの可能性があり、公開失敗する可能性があります。",
"newer_than": "より新しい",
"no_threads": "スレッドはありません",
"unblock": "ブロック解除",
@@ -282,5 +282,7 @@
"node_stats": "ノード統計",
"loading_default_boards": "デフォルトのボードリストを読み込み中",
"loading_subscriptions": "購読を読み込み中",
"create_board": "ボードを作成"
"create_board": "ボードを作成",
"enter_board_address": "任意のボードアドレスを入力してください",
"board_not_reachable": "ボードにアクセスできません"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "작성 날짜",
"sort_by": "정렬 기준",
"time_filter": "시간 필터",
"subplebbit_offline_info": "서브플레빗이 오프라인 일 수 있으며 게시 실패할 수 있습니다.",
"posts_last_synced_info": "최근 동기화된 게시물 {{time}}, 서브플레빗이 오프라인 일 수 있으며 게시 실패할 수 있습니다.",
"subplebbit_offline_info": "보드가 오프라인일 수 있으며 게시물이 실패할 수 있습니다",
"posts_last_synced_info": "마지막으로 동기화된 게시물 {{time}}, 게시판이 오프라인일 수 있으며 게시물이 실패할 수 있습니다.",
"newer_than": "이전보다 최신",
"no_threads": "스레드가 없습니다",
"unblock": "차단 해제",
@@ -282,5 +282,7 @@
"node_stats": "노드 통계",
"loading_default_boards": "기본 보드 목록을 로드 중",
"loading_subscriptions": "구독 로딩 중",
"create_board": "보드 만들기"
"create_board": "보드 만들기",
"enter_board_address": "어떤 보드 주소든 입력하세요",
"board_not_reachable": "보드를 찾을 수 없습니다"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "निर्माण तारीख",
"sort_by": "क्रमवार करा",
"time_filter": "वेळ फिल्टर",
"subplebbit_offline_info": "सबप्लेबिट मोबाइ असून प्रकाशन अयशस्वी होऊ शकतो.",
"posts_last_synced_info": "शेवटच्या बारीस सिन्क केलेले पोस्ट {{time}}, सबप्लेबिट मोबाइ असू प्रकाशन अयशस्वी होऊ शकत.",
"subplebbit_offline_info": "बोर्ड ऑफलाइ असू शकतो आणि प्रकाशित करणे अयशस्वी होऊ शकत",
"posts_last_synced_info": "शेवटचे पोस्ट सिंक्रनाइज केले {{time}}, बोर्ड ऑफलाइ असू शकतो आणि प्रकाशन अयशस्वी होऊ शकत.",
"newer_than": "नवीनतम आहे",
"no_threads": "कोणतेही थ्रेड्स नाहीत",
"unblock": "ब्लॉक सोडा",
@@ -282,5 +282,7 @@
"node_stats": "नोड आकडेवारी",
"loading_default_boards": "डीफॉल्ट बोर्ड यादी लोड करत आहे",
"loading_subscriptions": "सदस्यता लोड होत आहे",
"create_board": "बोर्ड तयार करा"
"create_board": "बोर्ड तयार करा",
"enter_board_address": "कोणत्याही बोर्ड पत्त्याची नोंद करा",
"board_not_reachable": "बोर्ड उपलब्ध नाही"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Creatiedatum",
"sort_by": "Sorteer op",
"time_filter": "Tijdsfilter",
"subplebbit_offline_info": "De subplebbit kan offline zijn en publiceren kan mislukken.",
"posts_last_synced_info": "Laatste keer gesynchroniseerde berichten {{time}}, de subplebbit kan offline zijn en publiceren kan mislukken.",
"subplebbit_offline_info": "Het bord kan offline zijn en publicatie kan mislukken",
"posts_last_synced_info": "Laatste berichten gesynchroniseerd {{time}}, het bord is mogelijk offline en publiceren kan mislukken.",
"newer_than": "nieuwer dan",
"no_threads": "Geen threads",
"unblock": "Deblokkeren",
@@ -282,5 +282,7 @@
"node_stats": "Node-statistieken",
"loading_default_boards": "Laden van de standaard boardlijst",
"loading_subscriptions": "Abonnementen laden",
"create_board": "Maak bord"
"create_board": "Maak bord",
"enter_board_address": "Voer een willekeurig bordadres in",
"board_not_reachable": "Bord is niet bereikbaar"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Opprettelsesdato",
"sort_by": "Sorter etter",
"time_filter": "Tidsfilter",
"subplebbit_offline_info": "Subplebbiten kan være offline, og publisering kan mislykkes.",
"posts_last_synced_info": "Siste synkroniserte innlegg {{time}}, subplebbiten kan være offline, og publisering kan mislykkes.",
"subplebbit_offline_info": "Brettet kan være offline, og publisering kan mislykkes",
"posts_last_synced_info": "Siste innlegg synkronisert {{time}}, tavlen kan være offline og publisering kan feile.",
"newer_than": "nyere enn",
"no_threads": "Ingen tråder",
"unblock": "Fjern blokkering",
@@ -282,5 +282,7 @@
"node_stats": "Node-statistikk",
"loading_default_boards": "Laster standard tavleliste",
"loading_subscriptions": "Laster abonnementer",
"create_board": "Lag tavle"
"create_board": "Lag tavle",
"enter_board_address": "Skriv inn hvilken som helst brettadresse",
"board_not_reachable": "Brettet er ikke tilgjengelig"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Data utworzenia",
"sort_by": "Sortuj według",
"time_filter": "Filtr czasu",
"subplebbit_offline_info": "Subplebbit może być offline, a publikacja może się nie powieść.",
"posts_last_synced_info": "Ostatnio zsynchronizowane posty {{time}}, subplebbit może być offline, a publikacja może się nie powieść.",
"subplebbit_offline_info": "Tablica może być offline, a publikacja może nie udać się",
"posts_last_synced_info": "Ostatnie posty zsynchronizowane {{time}}, tablica może być offline, a publikacja może nie powieść się.",
"newer_than": "nowsze niż",
"no_threads": "Brak wątków",
"unblock": "Odblokuj",
@@ -282,5 +282,7 @@
"node_stats": "Statystyki węzła",
"loading_default_boards": "Ładowanie domyślnej listy tablic",
"loading_subscriptions": "Ładowanie subskrypcji",
"create_board": "Utwórz tablicę"
"create_board": "Utwórz tablicę",
"enter_board_address": "Wprowadź dowolny adres tablicy",
"board_not_reachable": "Tablica jest niedostępna"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Data de criação",
"sort_by": "Ordenar por",
"time_filter": "Filtro de tempo",
"subplebbit_offline_info": "O subplebbit pode estar offline e a publicação pode falhar.",
"posts_last_synced_info": "Posts sincronizados pela última vez {{time}}, o subplebbit pode estar offline e a publicação pode falhar.",
"subplebbit_offline_info": "O quadro pode estar offline e a publicação pode falhar",
"posts_last_synced_info": "Últimas postagens sincronizadas {{time}}, o quadro pode estar offline e a publicação pode falhar.",
"newer_than": "mais recente que",
"no_threads": "Sem tópicos",
"unblock": "Desbloquear",
@@ -282,5 +282,7 @@
"node_stats": "Estatísticas do nó",
"loading_default_boards": "Carregando lista de quadros padrão",
"loading_subscriptions": "Carregando assinaturas",
"create_board": "Criar quadro"
"create_board": "Criar quadro",
"enter_board_address": "Digite qualquer endereço de quadro",
"board_not_reachable": "O quadro não é acessível"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Data de creare",
"sort_by": "Sortează după",
"time_filter": "Filtru de timp",
"subplebbit_offline_info": "Subplebbitul ar putea fi offline și publicarea ar putea eșua.",
"posts_last_synced_info": "Ultimele postări sincronizate {{time}}, subplebbitul ar putea fi offline și publicarea ar putea eșua.",
"subplebbit_offline_info": "Tabloul ar putea fi offline și publicarea ar putea eșua",
"posts_last_synced_info": "Ultimele postări sincronizate {{time}}, tabla poate fi offline și publicarea poate eșua.",
"newer_than": "mai nou decât",
"no_threads": "Nu sunt subiecte",
"unblock": "Deblocare",
@@ -282,5 +282,7 @@
"node_stats": "Statistici nod",
"loading_default_boards": "Se încarcă lista de panouri implicite",
"loading_subscriptions": "Încărcarea abonamentelor",
"create_board": "Creați tablou"
"create_board": "Creați tablou",
"enter_board_address": "Introduceți orice adresă de tablă",
"board_not_reachable": "Tabloul nu este accesibil"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Дата создания",
"sort_by": "Сортировать по",
"time_filter": "Фильтр времени",
"subplebbit_offline_info": "Сабплеббит может быть офлайн, и публикация может не удалиться.",
"posts_last_synced_info": "Последние синхронизированные сообщения {{time}}, сабплеббит может быть офлайн, и публикация может не удалиться.",
"subplebbit_offline_info": "Доска может быть офлайн, и публикация может не удаться",
"posts_last_synced_info": "Последние сообщения синхронизированы {{time}}, доска может быть оффлайн, и публикация может не удаться.",
"newer_than": "новее чем",
"no_threads": "Нет тем",
"unblock": "Разблокировать",
@@ -282,5 +282,7 @@
"node_stats": "Статистика узла",
"loading_default_boards": "Загрузка списка стандартных досок",
"loading_subscriptions": "Загрузка подписок",
"create_board": "Создать доску"
"create_board": "Создать доску",
"enter_board_address": "Введите любой адрес доски",
"board_not_reachable": "Доска недоступна"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Data e krijimit",
"sort_by": "Rendit sipas",
"time_filter": "Filtri i kohës",
"subplebbit_offline_info": "Subplebbiti mund të jetë jashtë linje dhe publikimi mund të dështojë.",
"posts_last_synced_info": "Postimet e fundit të sinkronizuara {{time}}, subplebbiti mund të jetë jashtë linje dhe publikimi mund të dështojë.",
"subplebbit_offline_info": "Tablli mund të jetë offline dhe publikimi mund të dështojë",
"posts_last_synced_info": "Postimet e fundit janë sinkronizuar {{time}}, bordi mund të jetë jashtë linje dhe botimi mund të dështojë.",
"newer_than": "më i ri se",
"no_threads": "Nuk ka tema",
"unblock": "Zhblloko",
@@ -282,5 +282,7 @@
"node_stats": "Statistikat e nyjës",
"loading_default_boards": "Po ngarkohen listat e bordit të parazgjedhur",
"loading_subscriptions": "Po ngarkohen abonimet",
"create_board": "Krijo bord"
"create_board": "Krijo bord",
"enter_board_address": "Shkruani çdo adresë bordi",
"board_not_reachable": "Tabela nuk është e arritshme"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Skapelsedatum",
"sort_by": "Sortera efter",
"time_filter": "Tidsfilter",
"subplebbit_offline_info": "Subplebbit kan vara offline och publicering kan misslyckas.",
"posts_last_synced_info": "Inlägg senast synkroniserade {{time}}, subplebbit kan vara offline och publicering kan misslyckas.",
"subplebbit_offline_info": "Brettet kan vara offline och publicering kan misslyckas",
"posts_last_synced_info": "Senaste inlägg synkroniserades {{time}}, tavlan kan vara offline och publiceringen kan misslyckas.",
"newer_than": "nyare än",
"no_threads": "Inga trådar",
"unblock": "Avblockera",
@@ -282,5 +282,7 @@
"node_stats": "Node-statistik",
"loading_default_boards": "Laddar standardlistan för tavlor",
"loading_subscriptions": "Laddar prenumerationer",
"create_board": "Skapa tavla"
"create_board": "Skapa tavla",
"enter_board_address": "Ange vilken som helst brädets adress",
"board_not_reachable": "Tavlan är inte tillgänglig"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "సృష్టి తేదీ",
"sort_by": "గట్టిగా",
"time_filter": "సమయ ఫిల్టర్",
"subplebbit_offline_info": "సబ్ప్లెబిట్ ఆఫ్‌లైన్ ఉండవచ్చు మరియు ప్రచురణ విఫలమైనా ఉంటుంది.",
"posts_last_synced_info": "ఇటీవలే సింక్ చేసిన పోస్ట్లు {{time}}, సబ్‌ప్లెబిట్ ఆఫ్‌లైన్ ఉండవచ్చు, మరియు ప్రకటన విఫలమవుతుంది.",
"subplebbit_offline_info": "బోర్డు ఆఫ్‌లైన్‌లో ఉండవచ్చు మరియు ప్రచురణ విఫలమవ్వవచ్చు",
"posts_last_synced_info": "తాజా పోస్టులు సింక్ చేసినవి {{time}}, బోర్డు ఆఫ్‌లైన్‌లో ఉండవచ్చు మరియు ప్రచురణ విఫలమవచ్చు.",
"newer_than": "కనునుగా",
"no_threads": "ఏ థ్రెడ్‌లు లేవు",
"unblock": "బ్లాక్‌ను తీసుకో",
@@ -282,5 +282,7 @@
"node_stats": "నోడ్ గణాంకాలు",
"loading_default_boards": "డిఫాల్ట్ బోర్డుల జాబితా లోడవుతోంది",
"loading_subscriptions": "సబ్‌స్క్రిప్షన్‌లు లోడ్ అవుతున్నాయి",
"create_board": "బోర్డ్ రూపొందించండి"
"create_board": "బోర్డ్ రూపొందించండి",
"enter_board_address": "ఏదైనా బోర్డ్ అడ్రస్ నమోదు చేయండి",
"board_not_reachable": "బోర్డు అందుబాటులో లేదు"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "วันที่สร้าง",
"sort_by": "เรียงลำดับตาม",
"time_filter": "ตัวกรองเวลา",
"subplebbit_offline_info": "เซ็บเพล็บบิทอาจออฟไลน์และการเผยแพร่อาจล้มเหลว",
"posts_last_synced_info": "โพสต์ล่าสุดที่ซิงค์ {{time}}, ซับเพลบบิทอาจออฟไลน์และการเผยแพร่อาจล้มเหลว",
"subplebbit_offline_info": "กระดานอาจออฟไลน์และการเผยแพร่อาจล้มเหลว",
"posts_last_synced_info": "โพสต์ล่าสุดซิงค์ {{time}} แล้ว บอร์ดอาจออฟไลน์และการเผยแพร่อาจล้มเหลว",
"newer_than": "ใหม่กว่า",
"no_threads": "ไม่มีกระทู้",
"unblock": "ยกเลิกการบล็อก",
@@ -282,5 +282,7 @@
"node_stats": "สถิติโหนด",
"loading_default_boards": "กำลังโหลดรายการกระดานเริ่มต้น",
"loading_subscriptions": "กำลังโหลดการสมัครสมาชิก",
"create_board": "สร้างกระดาน"
"create_board": "สร้างกระดาน",
"enter_board_address": "ป้อนที่อยู่บอร์ดใดก็ได้",
"board_not_reachable": "ไม่สามารถเข้าถึงบอร์ดได้"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Oluşturulma tarihi",
"sort_by": "Sırala",
"time_filter": "Zaman Filtresi",
"subplebbit_offline_info": "Subplebbit çevrimdışı olabilir ve yayınlama başarısız olabilir.",
"posts_last_synced_info": "Son senkronize edilen gönderiler {{time}}, subplebbit çevrimdışı olabilir ve yayınlama başarısız olabilir.",
"subplebbit_offline_info": "Tahta çevrimdışı olabilir ve yayınlama başarısız olabilir",
"posts_last_synced_info": "Son gönderiler senkronize edildi {{time}}, board çevrimdışı olabilir ve yayınlama başarısız olabilir.",
"newer_than": "daha yeni",
"no_threads": "Hiç gönderi yok",
"unblock": "Engeli kaldır",
@@ -282,5 +282,7 @@
"node_stats": "Düğüm istatistikleri",
"loading_default_boards": "Varsayılan panolar listesi yükleniyor",
"loading_subscriptions": "Abonelikler yükleniyor",
"create_board": "Tahta oluştur"
"create_board": "Tahta oluştur",
"enter_board_address": "Herhangi bir tahta adresini girin",
"board_not_reachable": "Board erişilemez"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Дата створення",
"sort_by": "Сортувати за",
"time_filter": "Фільтр за часом",
"subplebbit_offline_info": "Сабплебіт може бути офлайн, а публікація може не вдастися.",
"posts_last_synced_info": "Останні синхронізовані повідомлення {{time}}, сабплебіт може бути офлайн, а публікація може не вдастися.",
"subplebbit_offline_info": "Дошка може бути офлайн, а публікація може не вдатися",
"posts_last_synced_info": "Останні пости синхронізовано {{time}}, дошка може бути офлайн, і публікація може не вдатися.",
"newer_than": "новіше ніж",
"no_threads": "Немає тем",
"unblock": "Розблокувати",
@@ -282,5 +282,7 @@
"node_stats": "Статистика вузла",
"loading_default_boards": "Завантаження списку стандартних дошок",
"loading_subscriptions": "Завантаження підписок",
"create_board": "Створити дошку"
"create_board": "Створити дошку",
"enter_board_address": "Введіть будь-яку адресу дошки",
"board_not_reachable": "Дошка недоступна"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "تخلیق کی تاریخ",
"sort_by": "کے ذریعہ سارٹ کریں",
"time_filter": "وقت فلٹر",
"subplebbit_offline_info": "سب پلیبٹ آف لائن ہوسکتا ہے اور شائع ہونے میں ناکام ہوسکتا ہے۔",
"posts_last_synced_info": "آخری بار پوسٹ سنک کی گئی {{time}}, سب پلیبٹ آف لائن ہوسکتا ہے اور شائع ہونے میں ناکام ہوسکتا ہے۔",
"subplebbit_offline_info": "بورڈ آف لائن ہوسکتا ہے اور اشاعت ناکام ہوسکتی ہے",
"posts_last_synced_info": "آخری پوسٹس ہم آہنگ کی گئیں {{time}} ، بورڈ آف لائن ہو سکتا ہے اور اشاعت ناکام ہو سکتی ہے۔",
"newer_than": "نئی سے",
"no_threads": "کوئی تھریڈز نہیں ہیں",
"unblock": "بلاک ہٹائیں",
@@ -282,5 +282,7 @@
"node_stats": "نوڈ کے اعدادوشمار",
"loading_default_boards": "ڈिफالٹ بورڈ کی فہرست لوڈ ہو رہی ہے",
"loading_subscriptions": "سبسکرپشن لوڈ ہو رہی ہے",
"create_board": "بورڈ بنائیں"
"create_board": "بورڈ بنائیں",
"enter_board_address": "کسی بھی بورڈ کا پتہ درج کریں",
"board_not_reachable": "بورڈ قابل رسائی نہیں ہے"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "Ngày tạo",
"sort_by": "Sắp xếp theo",
"time_filter": "Bộ lọc thời gian",
"subplebbit_offline_info": "Subplebbit có thể ngoại tuyến và việc xuất bản có thể thất bại.",
"posts_last_synced_info": "Bài viết được đồng bộ lần cuối {{time}}, subplebbit có thể ngoại tuyến và việc xuất bản có thể thất bại.",
"subplebbit_offline_info": "Bảng có thể ngoại tuyến và việc xuất bản có thể thất bại",
"posts_last_synced_info": "Các bài viết cuối cùng đã đồng bộ {{time}}, bảng có thể ngoại tuyến và việc xuất bản có thể thất bại.",
"newer_than": "mới hơn",
"no_threads": "Không có chủ đề",
"unblock": "Bỏ chặn",
@@ -282,5 +282,7 @@
"node_stats": "Thống kê nút",
"loading_default_boards": "Đang tải danh sách bảng mặc định",
"loading_subscriptions": "Đang tải đăng ký",
"create_board": "Tạo bảng"
"create_board": "Tạo bảng",
"enter_board_address": "Nhập bất kỳ địa chỉ bảng nào",
"board_not_reachable": "Bảng không thể truy cập"
}
+5 -3
View File
@@ -164,8 +164,8 @@
"creation_date": "创造日期",
"sort_by": "排序方式",
"time_filter": "时间过滤器",
"subplebbit_offline_info": "子维基可能离线,发布可能失败",
"posts_last_synced_info": "最后同步的帖子 {{time}}, 子维基可能离线,发布可能失败。",
"subplebbit_offline_info": "该板可能离线,发布可能失败",
"posts_last_synced_info": "最后同步的帖子 {{time}},板块可能离线,发布可能失败。",
"newer_than": "新于",
"no_threads": "没有帖子",
"unblock": "解除封禁",
@@ -282,5 +282,7 @@
"node_stats": "节点统计",
"loading_default_boards": "正在加载默认板块列表",
"loading_subscriptions": "加载订阅",
"create_board": "创建板块"
"create_board": "创建板块",
"enter_board_address": "输入任何板块地址",
"board_not_reachable": "无法访问板块"
}
@@ -28,14 +28,28 @@
width: 90%;
}
.offlineIconWrapper {
display: inline-flex;
align-items: center;
margin-left: 10px;
line-height: 1;
}
.offlineIcon {
display: inline-block;
margin-left: 10px;
width: 16px;
height: 16px;
background-repeat: no-repeat;
background-size: contain;
image-rendering: pixelated;
width: 16px;
height: 16px;
background-repeat: no-repeat;
background-size: contain;
image-rendering: pixelated;
display: inline-block;
}
.redOfflineIcon {
background-image: var(--offline-icon);
}
.yellowOfflineIcon {
background-image: var(--offline-loading-icon);
}
@media (max-width: 640px) {
+6 -1
View File
@@ -7,6 +7,7 @@ import { useMultisubMetadata } from '../../hooks/use-default-subplebbits';
import useIsMobile from '../../hooks/use-is-mobile';
import useIsSubplebbitOffline from '../../hooks/use-is-subplebbit-offline';
import { shouldShowSnow } from '../../lib/snow';
import Tooltip from '../tooltip';
const totalBanners = 61;
@@ -47,7 +48,11 @@ const BoardHeader = () => {
<div className={styles.boardTitle}>
{title || (shortAddress ? (shortAddress.endsWith('.eth') || shortAddress.endsWith('.sol') ? shortAddress.slice(0, -4) : shortAddress) : subplebbitAddress)}
{(isOffline || isOnlineStatusLoading) && !isInAllView && !isInSubscriptionsView && (
<span className={`${styles.offlineIcon} ${offlineIconClass}`} title={offlineTitle} />
<span className={styles.offlineIconWrapper}>
<Tooltip content={offlineTitle}>
<span className={`${styles.offlineIcon} ${offlineIconClass}`} />
</Tooltip>
</span>
)}
</div>
<div className={styles.boardSubtitle}>{subtitle}</div>
@@ -0,0 +1,133 @@
import { useState } from 'react';
import { useLocation, useParams } from 'react-router-dom';
import { Trans, useTranslation } from 'react-i18next';
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
import { isPostPageView } from '../../lib/utils/view-utils';
import useIsMobile from '../../hooks/use-is-mobile';
import useStateString from '../../hooks/use-state-string';
import LoadingEllipsis from '../../components/loading-ellipsis';
import ReplyQuotePreview from '../../components/reply-quote-preview';
import Markdown from '../../components/markdown';
import Tooltip from '../../components/tooltip';
import { Comment, useComment } from '@plebbit/plebbit-react-hooks';
import styles from '../../views/post/post.module.css';
import _ from 'lodash';
const CommentContent = ({ comment: post }: { comment: Comment }) => {
const { t } = useTranslation();
const params = useParams();
const location = useLocation();
const isInPostView = isPostPageView(location.pathname, params);
const [showOriginal, setShowOriginal] = useState(false);
const isMobile = useIsMobile();
// TODO: commentAuthor is not yet available outside of editedComment, wait for API to be updated
const { cid, content, deleted, edit, isRules, original, parentCid, postCid, reason, removed, state } = post || {};
// const banned = !!post?.commentAuthor?.banExpiresAt;
const [showFullComment, setShowFullComment] = useState(false);
const displayContent =
content &&
(!isInPostView && content.length > 1000 && !showFullComment
? content.slice(0, 1000)
: isInPostView && content.length > 2000 && !showFullComment
? content.slice(0, 2000)
: content);
const quotelinkReply = useComment({ commentCid: parentCid });
const isReply = parentCid;
const isReplyingToReply = (postCid && postCid !== parentCid) || quotelinkReply?.postCid !== parentCid;
const stateString = useStateString(post);
const loadingString = <div className={styles.stateString}>{stateString !== 'Failed' ? <LoadingEllipsis string={stateString || t('loading')} /> : stateString}</div>;
return (
<blockquote className={`${styles.postMessage} ${!isReply && isMobile && styles.clampLines} ${isRules && styles.rulesMessage}`}>
{isReply && state !== 'failed' && isReplyingToReply && !(deleted || removed) && <ReplyQuotePreview isQuotelinkReply={true} quotelinkReply={quotelinkReply} />}
{removed ? (
reason ? (
<>
<span className={styles.redEditMessage}>({t('this_post_was_removed')})</span>
<br />
<br />
<span className={styles.grayEditMessage}>{`${_.capitalize(t('reason'))}: "${reason}"`}</span>
</>
) : (
<span className={styles.grayEditMessage}>{_.capitalize(t('this_post_was_removed'))}.</span>
)
) : deleted ? (
reason ? (
<>
<span className={styles.grayEditMessage}>{t('user_deleted_this_post')}</span>{' '}
<span className={styles.grayEditMessage}>{`${_.capitalize(t('reason'))}: "${reason}"`}</span>
</>
) : (
<span className={styles.grayEditMessage}>{t('user_deleted_this_post')}</span>
)
) : (
<>
{!showOriginal && <Markdown content={displayContent} />}
{((!isInPostView && content?.length > 1000 && !showFullComment) || (isInPostView && content?.length > 2000 && !showFullComment)) && (
<span className={styles.abbr}>
<br />
<br />
<Trans i18nKey={'comment_too_long'} shouldUnescape={true} components={{ 1: <span onClick={() => setShowFullComment(true)} /> }} />
</span>
)}
{edit && original?.content !== content && (
<span className={styles.editedInfo}>
{showOriginal && <Markdown content={original?.content} />}
<br />
<br />
<Trans
i18nKey={'comment_edited_at_timestamp'}
values={{ timestamp: getFormattedDate(edit?.timestamp) }}
shouldUnescape={true}
components={{ 1: <Tooltip content={getFormattedTimeAgo(edit?.timestamp)} children={<></>} /> }}
/>{' '}
{reason && <>{t('reason_reason', { reason: reason, interpolation: { escapeValue: false } })} </>}
{showOriginal ? (
<Trans
i18nKey={'click_here_to_hide_original'}
shouldUnescape={true}
components={{ 1: <span className={styles.showOriginal} onClick={() => setShowOriginal(!showOriginal)} /> }}
/>
) : (
<Trans
i18nKey={'click_here_to_show_original'}
shouldUnescape={true}
components={{ 1: <span className={styles.showOriginal} onClick={() => setShowOriginal(!showOriginal)} /> }}
/>
)}
</span>
)}
</>
)}
{/* TODO: commentAuthor is not yet available outside of editedComment, wait for API to be updated */}
{/* {banned && (
<span className={styles.removedContent}>
<br />
<br />
<Tooltip
children={`(${t('user_banned')})`}
content={`${t('ban_expires_at', {
address: subplebbitAddress && Plebbit.getShortAddress(subplebbitAddress),
timestamp: getFormattedDate(commentAuthor?.banExpiresAt),
interpolation: { escapeValue: false },
})}${reason ? `. ${_.capitalize(t('reason'))}: "${reason}"` : ''}`}
/>
</span>
)} */}
{!cid && state === 'pending' && stateString !== 'Failed' && (
<>
<br />
<br />
{loadingString}
</>
)}
</blockquote>
);
};
export default CommentContent;
+1
View File
@@ -0,0 +1 @@
export { default } from './comment-content';
+11 -2
View File
@@ -135,7 +135,7 @@ interface ImageProps {
const Image = ({ commentMediaInfo, displayHeight, displayWidth, isOutOfFeed, post }: ImageProps) => {
const { t } = useTranslation();
const { parentCid } = post || {};
const { parentCid, spoiler } = post || {};
const { type, url } = commentMediaInfo || {};
const isReply = parentCid;
const isMobile = useIsMobile();
@@ -151,6 +151,15 @@ const Image = ({ commentMediaInfo, displayHeight, displayWidth, isOutOfFeed, pos
const [hasError, setHasError] = useState(false);
const handleError = () => setHasError(true);
if (spoiler && !isImageExpanded) {
const spoilerDimensions = { '--width': '150px', '--height': '150px' } as React.CSSProperties;
return (
<span className={`${isOutOfFeed ? styles.subplebbitAvatar : styles.thumbnailBig} ${styles.thumbnail}`} style={spoilerDimensions}>
<img className={styles.spoiler} src='assets/spoiler.png' alt='' onClick={() => setIsImageExpanded(true)} />
</span>
);
}
return isMobile ? (
<span className={styles.thumbnail}>
<span
@@ -227,7 +236,7 @@ const CommentMedia = ({ commentMediaInfo, isFloatingEmbed, post, showThumbnail,
displayHeight = '100%';
}
const { isDescription, isRules } = post || {}; // custom properties, not from api
const isOutOfFeed = isDescription || isRules || isFloatingEmbed; // virtuoso wrapper unneeded
const isOutOfFeed = isDescription || isRules || isFloatingEmbed || spoiler; // virtuoso wrapper unneeded
return (
<span className={styles.content}>
+19 -130
View File
@@ -1,7 +1,7 @@
import { useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Link, useLocation, useParams } from 'react-router-dom';
import { Comment, useAuthorAvatar, useComment, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
import { Comment, useAuthorAvatar, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
import styles from '../../views/post/post.module.css';
import { getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils';
@@ -17,11 +17,11 @@ import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
import useHide from '../../hooks/use-hide';
import useReplies from '../../hooks/use-replies';
import useStateString from '../../hooks/use-state-string';
import CommentContent from '../comment-content';
import CommentMedia from '../comment-media';
import EditMenu from '../edit-menu/edit-menu';
import { canEmbed } from '../embed';
import LoadingEllipsis from '../loading-ellipsis';
import Markdown from '../markdown';
import PostMenuDesktop from './post-menu-desktop';
import ReplyQuotePreview from '../reply-quote-preview';
import Tooltip from '../tooltip';
@@ -251,8 +251,7 @@ const PostMediaContent = ({ post, hasThumbnail, spoiler, t }: { post: any; hasTh
{mediaDimensions && `, ${mediaDimensions}`})
{!showThumbnail && (type === 'iframe' || type === 'video' || type === 'audio') && (
<span>
{' '}
[
-[
<span className={styles.closeMedia} onClick={() => setShowThumbnail(true)}>
{t('close')}
</span>
@@ -285,120 +284,6 @@ const PostMediaContent = ({ post, hasThumbnail, spoiler, t }: { post: any; hasTh
);
};
const PostMessage = ({ post }: PostProps) => {
const { cid, content, deleted, edit, isRules, original, parentCid, postCid, reason, removed, state } = post || {};
// TODO: commentAuthor is not yet available outside of editedComment, wait for API to be updated
// const banned = !!post?.commentAuthor?.banExpiresAt;
const { t } = useTranslation();
const params = useParams();
const location = useLocation();
const isInPostView = isPostPageView(location.pathname, params);
const [showOriginal, setShowOriginal] = useState(false);
const [showFullComment, setShowFullComment] = useState(false);
const displayContent =
content &&
(!isInPostView && content.length > 1000 && !showFullComment
? content.slice(0, 1000)
: isInPostView && content.length > 2000 && !showFullComment
? content.slice(0, 2000)
: content);
const quotelinkReply = useComment({ commentCid: parentCid });
const isReply = parentCid;
const isReplyingToReply = (postCid && postCid !== parentCid) || quotelinkReply?.postCid !== parentCid;
const stateString = useStateString(post);
const loadingString = (
<div className={`${styles.stateString} ${styles.ellipsis}`}>{stateString !== 'Failed' ? <LoadingEllipsis string={stateString || t('loading')} /> : stateString}</div>
);
return (
<blockquote className={`${styles.postMessage} ${isRules && styles.rulesMessage}`}>
{isReply && state !== 'failed' && isReplyingToReply && !(deleted || removed) && <ReplyQuotePreview isQuotelinkReply={true} quotelinkReply={quotelinkReply} />}
{removed ? (
reason ? (
<>
<span className={styles.redEditMessage}>({t('this_post_was_removed')})</span>
<br />
<br />
<span className={styles.grayEditMessage}>{`${_.capitalize(t('reason'))}: "${reason}"`}</span>
</>
) : (
<span className={styles.grayEditMessage}>{_.capitalize(t('this_post_was_removed'))}.</span>
)
) : deleted ? (
reason ? (
<>
<span className={styles.grayEditMessage}>{t('user_deleted_this_post')}</span>{' '}
<span className={styles.grayEditMessage}>{`${_.capitalize(t('reason'))}: "${reason}"`}</span>
</>
) : (
<span className={styles.grayEditMessage}>{t('user_deleted_this_post')}</span>
)
) : (
<>
{!showOriginal && <Markdown content={displayContent} />}
{edit && original?.content !== post?.content && (
<span className={styles.editedInfo}>
{showOriginal && <Markdown content={original?.content} />}
<br />
<Trans
i18nKey={'comment_edited_at_timestamp'}
values={{ timestamp: getFormattedDate(edit?.timestamp) }}
shouldUnescape={true}
components={{ 1: <Tooltip content={getFormattedTimeAgo(edit?.timestamp)} children={<></>} /> }}
/>{' '}
{reason && <>{t('reason_reason', { reason: reason, interpolation: { escapeValue: false } })} </>}
{showOriginal ? (
<Trans
i18nKey={'click_here_to_hide_original'}
shouldUnescape={true}
components={{ 1: <span className={styles.showOriginal} onClick={() => setShowOriginal(!showOriginal)} /> }}
/>
) : (
<Trans
i18nKey={'click_here_to_show_original'}
shouldUnescape={true}
components={{ 1: <span className={styles.showOriginal} onClick={() => setShowOriginal(!showOriginal)} /> }}
/>
)}
</span>
)}
</>
)}
{/* TODO: commentAuthor is not yet available outside of editedComment, wait for API to be updated */}
{/* {banned && (
<span className={styles.removedContent}>
<br />
<br />
<Tooltip
children={`(${t('user_banned')})`}
content={`${t('ban_expires_at', {
address: subplebbitAddress && Plebbit.getShortAddress(subplebbitAddress),
timestamp: getFormattedDate(commentAuthor?.banExpiresAt),
interpolation: { escapeValue: false },
})}${reason ? `. ${_.capitalize(t('reason'))}: "${reason}"` : ''}`}
/>
</span>
)} */}
{((!isInPostView && content?.length > 1000 && !showFullComment) || (isInPostView && content?.length > 2000 && !showFullComment)) && (
<span className={styles.abbr}>
<br />
<Trans i18nKey={'comment_too_long'} shouldUnescape={true} components={{ 1: <span onClick={() => setShowFullComment(true)} /> }} />
</span>
)}
{!cid && state === 'pending' && stateString !== 'Failed' && (
<>
<br />
{loadingString}
</>
)}
</blockquote>
);
};
const Reply = ({ openReplyModal, postReplyCount, reply, roles }: PostProps) => {
let post = reply;
// handle pending mod or author edit
@@ -420,7 +305,7 @@ const Reply = ({ openReplyModal, postReplyCount, reply, roles }: PostProps) => {
<div className={`${styles.reply} ${isRouteLinkToReply && styles.highlight}`} data-cid={cid} data-author-address={author?.shortAddress} data-post-cid={postCid}>
<PostInfo openReplyModal={openReplyModal} post={post} postReplyCount={postReplyCount} roles={roles} isHidden={hidden} />
{link && !hidden && !(deleted || removed) && isValidURL(link) && <PostMedia post={post} hasThumbnail={hasThumbnail} />}
{!hidden && (!(removed || deleted) || ((removed || deleted) && reason)) && <PostMessage post={post} />}
{!hidden && (!(removed || deleted) || ((removed || deleted) && reason)) && <CommentContent comment={post} />}
</div>
</div>
);
@@ -483,7 +368,7 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies
{link && !isHidden && !(deleted || removed) && isValidURL(link) && <PostMedia post={post} hasThumbnail={hasThumbnail} />}
<PostInfo isHidden={hidden} openReplyModal={openReplyModal} post={post} postReplyCount={replyCount} roles={roles} />
{!isHidden && !content && !(deleted || removed) && <div className={styles.spacer} />}
{!isHidden && <PostMessage post={post} />}
{!isHidden && <CommentContent comment={post} />}
</div>
{!isHidden && !isDescription && !isRules && !isInPendingPostView && (replyCount > 5 || (pinned && repliesCount > 0)) && !isInPostPageView && (
<span className={styles.summary}>
@@ -522,16 +407,20 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies
)}
</div>
{!isInPendingPostView &&
!isDescription &&
!isRules &&
(stateString && stateString !== 'Failed' && state !== 'succeeded' && isInPostPageView ? (
<div className={styles.stateString}>
<br />
<LoadingEllipsis string={stateString} />
</div>
) : (
state === 'failed' && <span className={styles.error}>{t('failed')}</span>
))}
!isDescription &&
!isRules &&
stateString &&
stateString !== 'Failed' &&
state !== 'succeeded' &&
isInPostPageView &&
!(!showReplies && !showAllReplies) ? (
<div className={styles.stateString}>
<br />
<LoadingEllipsis string={stateString} />
</div>
) : (
state === 'failed' && <span className={styles.error}>{t('failed')}</span>
)}
</div>
);
};
+19 -130
View File
@@ -1,9 +1,10 @@
import { useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useTranslation } from 'react-i18next';
import { Link, useLocation, useParams } from 'react-router-dom';
import { Comment, useAuthorAvatar, useComment, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
import { Comment, useAuthorAvatar, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
import styles from '../../views/post/post.module.css';
import { shouldShowSnow } from '../../lib/snow';
import { getHasThumbnail } from '../../lib/utils/media-utils';
import { getTextColorForBackground, hashStringToColor } from '../../lib/utils/post-utils';
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
@@ -15,15 +16,14 @@ import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
import useHide from '../../hooks/use-hide';
import useReplies from '../../hooks/use-replies';
import useStateString from '../../hooks/use-state-string';
import CommentContent from '../comment-content';
import CommentMedia from '../comment-media';
import LoadingEllipsis from '../loading-ellipsis';
import Markdown from '../markdown';
import PostMenuMobile from './post-menu-mobile';
import ReplyQuotePreview from '../reply-quote-preview';
import Tooltip from '../tooltip';
import { PostProps } from '../../views/post/post';
import _ from 'lodash';
import { shouldShowSnow } from '../../lib/snow';
const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: PostProps) => {
const { t } = useTranslation();
@@ -215,121 +215,6 @@ const ReplyBacklinks = ({ post }: PostProps) => {
);
};
const PostMessageMobile = ({ post }: PostProps) => {
const { t } = useTranslation();
const { cid, content, deleted, edit, isRules, original, parentCid, postCid, reason, removed, state } = post || {};
// TODO: commentAuthor is not available outside of editedComment, update when available
// const banned = !!post?.commentAuthor?.banExpiresAt;
const [showOriginal, setShowOriginal] = useState(false);
const params = useParams();
const location = useLocation();
const isInPostView = isPostPageView(location.pathname, params);
const [showFullComment, setShowFullComment] = useState(false);
const displayContent =
content &&
(!isInPostView && content.length > 1000 && !showFullComment
? content.slice(0, 1000)
: isInPostView && content.length > 2000 && !showFullComment
? content.slice(0, 2000)
: content);
const quotelinkReply = useComment({ commentCid: parentCid });
const isReply = parentCid;
const isReplyingToReply = (postCid && postCid !== parentCid) || quotelinkReply?.postCid !== parentCid;
const stateString = useStateString(post);
const loadingString = (
<div className={`${styles.stateString} ${styles.ellipsis}`}>{stateString !== 'Failed' ? <LoadingEllipsis string={stateString || t('loading')} /> : stateString}</div>
);
return (
<blockquote className={`${styles.postMessage} ${!isReply && styles.clampLines} ${isRules && styles.rulesMessage}`}>
{isReply && !(removed || deleted) && state !== 'failed' && isReplyingToReply && <ReplyQuotePreview isQuotelinkReply={true} quotelinkReply={quotelinkReply} />}
{removed ? (
reason ? (
<>
<span className={styles.redEditMessage}>({t('this_post_was_removed')})</span>
<br />
<br />
<span className={styles.grayEditMessage}>{`${_.capitalize(t('reason'))}: "${reason}"`}</span>
</>
) : (
<span className={styles.grayEditMessage}>{_.capitalize(t('this_post_was_removed'))}.</span>
)
) : deleted ? (
reason ? (
<>
<span className={styles.grayEditMessage}>{t('user_deleted_this_post')}</span>{' '}
<span className={styles.grayEditMessage}>{`${_.capitalize(t('reason'))}: "${reason}"`}</span>
</>
) : (
<span className={styles.grayEditMessage}>{t('user_deleted_this_post')}</span>
)
) : (
<>
{!showOriginal && <Markdown content={displayContent} />}
{edit && original?.content !== post?.content && (
<span className={styles.editedInfo}>
{showOriginal && <Markdown content={original?.content} />}
<br />
<Trans
i18nKey={'comment_edited_at_timestamp'}
values={{ timestamp: getFormattedDate(edit?.timestamp) }}
shouldUnescape={true}
components={{ 1: <Tooltip content={getFormattedTimeAgo(edit?.timestamp)} children={<></>} /> }}
/>{' '}
{reason && <>{t('reason_reason', { reason: reason, interpolation: { escapeValue: false } })} </>}
{showOriginal ? (
<Trans
i18nKey={'click_here_to_hide_original'}
shouldUnescape={true}
components={{ 1: <span className={styles.showOriginal} onClick={() => setShowOriginal(!showOriginal)} /> }}
/>
) : (
<Trans
i18nKey={'click_here_to_show_original'}
shouldUnescape={true}
components={{ 1: <span className={styles.showOriginal} onClick={() => setShowOriginal(!showOriginal)} /> }}
/>
)}
</span>
)}
</>
)}
{/* TODO: commentAuthor is not available outside of editedComment, update when available */}
{/* {banned && (
<span className={styles.removedContent}>
<br />
<br />
<Tooltip
children={`(${t('user_banned')})`}
content={`${t('ban_expires_at', {
address: subplebbitAddress && Plebbit.getShortAddress(subplebbitAddress),
timestamp: getFormattedDate(commentAuthor?.banExpiresAt),
interpolation: { escapeValue: false },
})}${reason ? `. ${_.capitalize(t('reason'))}: "${reason}"` : ''}`}
/>
</span>
)} */}
{((!isInPostView && content?.length > 1000 && !showFullComment) || (isInPostView && content?.length > 2000 && !showFullComment)) && (
<span className={styles.abbr}>
<br />
<Trans i18nKey={'comment_too_long'} shouldUnescape={true} components={{ 1: <span onClick={() => setShowFullComment(true)} /> }} />
</span>
)}
{!cid && state === 'pending' && stateString !== 'Failed' && (
<>
<br />
{loadingString}
</>
)}
</blockquote>
);
};
const Reply = ({ openReplyModal, postReplyCount, reply, roles }: PostProps) => {
let post = reply;
// handle pending mod or author edit
@@ -351,7 +236,7 @@ const Reply = ({ openReplyModal, postReplyCount, reply, roles }: PostProps) => {
data-post-cid={postCid}
>
<PostInfoAndMedia openReplyModal={openReplyModal} post={post} postReplyCount={postReplyCount} roles={roles} />
{!hidden && (!(removed || deleted) || ((removed || deleted) && reason)) && <PostMessageMobile post={post} />}
{!hidden && (!(removed || deleted) || ((removed || deleted) && reason)) && <CommentContent comment={post} />}
<ReplyBacklinks post={reply} />
</div>
</div>
@@ -415,7 +300,7 @@ const PostMobile = ({ openReplyModal, post, roles, showAllReplies, showReplies =
>
{shouldShowSnow() && <img src={`${process.env.PUBLIC_URL}/assets/xmashat.gif`} className={styles.xmasHat} alt='' />}
<PostInfoAndMedia openReplyModal={openReplyModal} post={post} postReplyCount={replyCount} roles={roles} />
<PostMessageMobile post={post} />
<CommentContent comment={post} />
</div>
{!isInPostView && !isInPendingPostView && showReplies && (
<div className={styles.postLink}>
@@ -448,15 +333,19 @@ const PostMobile = ({ openReplyModal, post, roles, showAllReplies, showReplies =
)}
</div>
{!isInPendingPostView &&
!isDescription &&
!isRules &&
(stateString && stateString !== 'Failed' && state !== 'succeeded' && isInPostPageView ? (
<div className={styles.stateString}>
<LoadingEllipsis string={stateString} />
</div>
) : (
state === 'failed' && <span className={styles.error}>{t('failed')}</span>
))}
!isDescription &&
!isRules &&
stateString &&
stateString !== 'Failed' &&
state !== 'succeeded' &&
isInPostPageView &&
!(!showReplies && !showAllReplies) ? (
<div className={styles.stateString}>
<LoadingEllipsis string={stateString} />
</div>
) : (
state === 'failed' && <span className={styles.error}>{t('failed')}</span>
)}
</div>
)}
</>
@@ -137,7 +137,8 @@ const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, i
onMouseLeave={() => handleMouseLeave(backlinkReply?.cid)}
onClick={(e) => handleClick(e, backlinkReply?.cid, backlinkReply?.subplebbitAddress)}
>
c/{backlinkReply?.shortCid}
{'>>'}
{backlinkReply?.shortCid}
</Link>
{hoveredCid === backlinkReply?.cid &&
outOfViewCid === backlinkReply?.cid &&
@@ -164,7 +165,7 @@ const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, i
onMouseLeave={() => handleMouseLeave(quotelinkReply?.cid)}
onClick={(e) => handleClick(e, quotelinkReply?.cid, quotelinkReply?.subplebbitAddress)}
>
{quotelinkReply?.shortCid && `c/${quotelinkReply?.shortCid}`}
{quotelinkReply?.shortCid && `>>${quotelinkReply?.shortCid}`}
{(quotelinkReply?.author?.address === account?.author?.address || quotelinkReply?.author?.address === threadSigner?.address) && ' (You)'}
</Link>
<br />
@@ -239,7 +240,7 @@ const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, is
onMouseOver={() => handleMouseOver(backlinkReply?.cid)}
onMouseLeave={() => handleMouseLeave(backlinkReply?.cid)}
>
{backlinkReply?.shortCid && `c/${backlinkReply?.shortCid}`}
{backlinkReply?.shortCid && `>>${backlinkReply?.shortCid}`}
</span>
{backlinkReply?.shortCid && (
<Link
@@ -274,7 +275,7 @@ const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, is
onMouseOver={() => handleMouseOver(quotelinkReply?.cid)}
onMouseLeave={() => handleMouseLeave(quotelinkReply?.cid)}
>
{quotelinkReply?.shortCid && `c/${quotelinkReply?.shortCid}`}
{quotelinkReply?.shortCid && `>>${quotelinkReply?.shortCid}`}
{(quotelinkReply?.author?.address === account?.author?.address || quotelinkReply?.author?.address === threadSigner?.address) && ' (You)'}
</span>
{quotelinkReply?.shortCid && (
+10 -4
View File
@@ -1,6 +1,6 @@
.tooltip {
position: absolute;
background-color: #181f24;
background-color: var(--tooltip-background-color);
font-size: 11px;
line-height: 13px;
padding: 3px 6px;
@@ -9,7 +9,6 @@
white-space: pre-line;
max-width: 400px;
color: #fff;
overflow: hidden;
text-align: center;
}
@@ -21,8 +20,15 @@
position: absolute;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid #181f24;
border-top: 4px solid var(--tooltip-background-color);
margin-left: -4px;
bottom: -4px;
left: 50%;
}
}
@media (max-width: 640px) {
.tooltip {
max-width: 80vw;
}
}
+7 -6
View File
@@ -1,20 +1,21 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useAccount, useAccountComment } from '@plebbit/plebbit-react-hooks';
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
import { useAccount, useAccountComment } from '@plebbit/plebbit-react-hooks';
import { isAllView, isCatalogView, isSubscriptionsView } from '../../lib/utils/view-utils';
import styles from './topbar.module.css';
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
import _, { debounce } from 'lodash';
import { TimeFilter } from '../board-buttons';
import { useAutoSubscribe } from '../../hooks/use-auto-subscribe';
import { TimeFilter } from '../board-buttons';
import styles from './topbar.module.css';
import _, { debounce } from 'lodash';
const SearchBar = ({ setShowSearchBar }: { setShowSearchBar: (show: boolean) => void }) => {
const { t } = useTranslation();
const navigate = useNavigate();
const searchBarRef = useRef<HTMLDivElement>(null);
const searchInputRef = useRef<HTMLInputElement>(null);
const placeholder = `"community.eth/.sol" / "12D3KooW..."`;
const placeholder = _.lowerCase(t('enter_board_address'));
useEffect(() => {
searchInputRef.current?.focus();
+1 -1
View File
@@ -35,7 +35,7 @@ const useFeedStateString = (subplebbitAddresses?: string[]): string | undefined
if (states['resolving-address']) {
const { subplebbitAddresses, clientUrls } = states['resolving-address'];
if (subplebbitAddresses.length && clientUrls.length) {
stateString += `loading ${subplebbitAddresses.length} ${subplebbitAddresses.length === 1 ? 'address' : 'addresses'} from ${clientUrls
stateString += `resolving ${subplebbitAddresses.length} ${subplebbitAddresses.length === 1 ? 'address' : 'addresses'} from ${clientUrls
.map(getClientHost)
.join(', ')}`;
}
-1
View File
@@ -64,7 +64,6 @@ const useStateString = (commentOrSubplebbit: CommentOrSubplebbit): string | unde
.replace('ipfs', 'post')
.replace('ipns', 'subplebbit')
.replace('fetching', 'loading')
.replace('resolving', 'loading')
.replace('subplebbit subplebbit', 'board')
.replace('loading subplebbit', 'loading board');
}
+18
View File
@@ -196,6 +196,9 @@
/* stats */
--stats-font-size: 11px;
/* tooltip */
--tooltip-background-color: #181f24;
/* topbar */
--topbar-font-size: 9pt;
@@ -406,6 +409,9 @@
/* stats */
--stats-font-size: 11px;
/* tooltip */
--tooltip-background-color: #181f24;
/* topbar */
--topbar-font-size: 9pt;
--topbar-separator-color: #89a;
@@ -584,6 +590,9 @@
/* stats */
--stats-font-size: 10pt;
/* tooltip */
--tooltip-background-color: #181f24;
/* topbar */
--topbar-font-size: 11pt;
--topbar-separator-color: none;
@@ -769,6 +778,9 @@
/* stats */
--stats-font-size: 10pt;
/* tooltip */
--tooltip-background-color: #181f24;
/* topbar */
--topbar-font-size: 11pt;
--topbar-separator-color: none;
@@ -988,6 +1000,9 @@
/* stats */
--stats-font-size: 11px;
/* tooltip */
--tooltip-background-color: #000;
/* topbar */
--topbar-font-size: 9pt;
--topbar-separator-color: #c5c8c6;
@@ -1188,6 +1203,9 @@
/* stats */
--stats-font-size: 11px;
/* tooltip */
--tooltip-background-color: #181f24;
/* topbar */
--topbar-font-size: 9pt;
--topbar-separator-color: #333;
+1 -1
View File
@@ -1,6 +1,6 @@
.content {
overflow-x: hidden;
overflow-y: visible;
overflow-y: hidden;
}
.footer {
+1 -1
View File
@@ -165,7 +165,7 @@ const Board = () => {
const Footer = () => {
let footerContent;
if (feed.length === 0) {
if (combinedFeed.length === 0) {
footerContent = t('no_threads');
}
if (hasMore || (subplebbitAddresses && subplebbitAddresses.length === 0)) {
+45 -5
View File
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { Link, useLocation, useParams } from 'react-router-dom';
import { Trans, useTranslation } from 'react-i18next';
import { Comment, useAccount, useFeed, useSubplebbit, useBlock } from '@plebbit/plebbit-react-hooks';
import { Comment, useAccount, useFeed, useSubplebbit, useBlock, useAccountComments } from '@plebbit/plebbit-react-hooks';
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
@@ -90,6 +90,46 @@ const Catalog = () => {
}, [subplebbitAddresses, sortType, isInAllView, isInSubscriptionsView, postsPerPage, timeFilterSeconds, hideThreadsWithoutImages]);
const { feed, hasMore, loadMore, reset, subplebbitAddressesWithNewerPosts } = useFeed(feedOptions);
const { accountComments } = useAccountComments();
const resetTriggeredRef = useRef(false);
// show account comments instantly in the feed once published (cid defined), instead of waiting for the feed to update
const filteredComments = useMemo(
() =>
accountComments.filter((comment) => {
const { cid, deleted, postCid, removed, state, timestamp } = comment || {};
return (
!deleted &&
!removed &&
timestamp > Date.now() / 1000 - 60 * 60 &&
state === 'succeeded' &&
cid &&
(hideThreadsWithoutImages ? getHasThumbnail(getCommentMediaInfo(comment), comment?.link) : true) &&
cid === postCid &&
comment?.subplebbitAddress === subplebbitAddress &&
!feed.some((post) => post.cid === cid)
);
}),
[accountComments, subplebbitAddress, feed, hideThreadsWithoutImages],
);
// show newest account comment at the top of the feed but after pinned posts
const combinedFeed = useMemo(() => {
const newFeed = [...feed];
const lastPinnedIndex = newFeed.map((post) => post.pinned).lastIndexOf(true);
if (filteredComments.length > 0) {
newFeed.splice(lastPinnedIndex + 1, 0, ...filteredComments);
}
return newFeed;
}, [feed, filteredComments]);
useEffect(() => {
if (filteredComments.length > 0 && !resetTriggeredRef.current) {
reset();
resetTriggeredRef.current = true;
}
}, [filteredComments, reset]);
const handleNewerPostsButtonClick = () => {
window.scrollTo({ top: 0, left: 0 });
@@ -147,7 +187,7 @@ const Catalog = () => {
t('not_subscribed_to_any_board')
) : blocked ? (
t('you_have_blocked_this_board')
) : !hasMore && feed.length === 0 ? (
) : !hasMore && combinedFeed.length === 0 ? (
t('no_threads')
) : (
hasMore && <LoadingEllipsis string={loadingStateString} />
@@ -169,7 +209,7 @@ const Catalog = () => {
if (feed.length === 0) {
if (blocked) {
footerContent = t('you_have_blocked_this_board');
} else {
} else if (combinedFeed.length === 0) {
footerContent = t('no_threads');
}
}
@@ -222,7 +262,7 @@ const Catalog = () => {
const isFeedLoaded = feed.length > 0 || state === 'failed';
const rows = useCatalogFeedRows(columnCount, feed, isFeedLoaded, subplebbit);
const rows = useCatalogFeedRows(columnCount, combinedFeed, isFeedLoaded, subplebbit);
// save the last Virtuoso state to restore it when navigating back
const virtuosoRef = useRef<VirtuosoHandle | null>(null);
@@ -251,7 +291,7 @@ const Catalog = () => {
{location.pathname.endsWith('/settings') && <SettingsModal />}
<hr />
<div className={styles.catalog}>
{feed.length !== 0 ? (
{combinedFeed.length !== 0 ? (
<>
<Virtuoso
increaseViewportBy={{ bottom: 1200, top: 1200 }}
@@ -0,0 +1,104 @@
.boardsBox {
overflow: hidden;
min-height: 150px;
}
.boardsBox table {
display: table;
margin-bottom: 10px;
overflow: hidden;
table-layout: fixed;
width: calc(100% + 4px);
margin-left: -2px;
border-collapse: separate;
border-spacing: 2px 0;
}
.boardsBox table thead th {
background: #fca;
color: #800;
border: 1px solid #800;
padding: 4px 15px 5px 5px;
text-align: left;
white-space: nowrap;
text-transform: capitalize;
}
.boardsBox table tbody td {
margin: 0;
padding: 4px 15px 4px 4px;
text-align: left;
}
.boardsBox table tbody tr:nth-of-type( even ) {
background: #ede2d4;
}
.boardCell {
position: static;
margin: 0;
padding: 0;
box-sizing: border-box;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.offlineIconContainer {
float: right;
}
.offlineIcon {
vertical-align: text-top;
width: 13px;
height: 13px;
background-repeat: no-repeat;
background-size: contain;
image-rendering: pixelated;
display: inline-block;
position: relative;
}
.boardCell a {
color: inherit;
}
.loadingBoardCellValue {
color: var(--topbar-separator-color);
}
.loadMoreButton {
text-align: center;
margin-bottom: 14px;
color: var(--topbar-separator-color);
}
.displayCount {
font-size: 12px;
text-align: center;
margin-bottom: 10px;
text-transform: lowercase;
}
.loadMoreButton span {
color: var(--topbar-desktop-link-text-color);
text-transform: capitalize;
}
.loadMoreButton span:hover {
cursor: pointer;
color: var(--button-desktop-text-color-hover);
text-decoration: underline;
}
.nsfw {
color: red;
font-weight: 700;
text-transform: uppercase;
font-size: smaller;
}
.boardPPH {
width: 33px;
}
+29 -10
View File
@@ -3,17 +3,18 @@ import { Link, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
import { Subplebbit, useSubplebbit, useSubplebbitStats } from '@plebbit/plebbit-react-hooks';
import { useDefaultSubplebbitTags } from '../../../hooks/use-default-subplebbits-tags';
import { useDefaultSubplebbitsState } from '../../../hooks/use-default-subplebbits';
import { useDefaultSubplebbitTags } from '../../../hooks/use-default-subplebbits-tags';
import useIsMobile from '../../../hooks/use-is-mobile';
import useIsSubplebbitOffline from '../../../hooks/use-is-subplebbit-offline';
import styles from '../home.module.css';
import { nsfwTags } from '../home';
import LoadingEllipsis from '../../../components/loading-ellipsis';
import Tooltip from '../../../components/tooltip';
import styles from './boards-list.module.css';
import { nsfwTags } from '../home';
const Board = ({ subplebbit, isMobile }: { subplebbit: Subplebbit; isMobile: boolean }) => {
const { t } = useTranslation();
const { address, title, tags } = subplebbit || {};
const { address, tags } = subplebbit || {};
const nsfwTag = tags?.find((tag: string) => nsfwTags.includes(tag));
let stats = useSubplebbitStats({ subplebbitAddress: address });
@@ -24,22 +25,40 @@ const Board = ({ subplebbit, isMobile }: { subplebbit: Subplebbit; isMobile: boo
const displayAddress = address && Plebbit.getShortAddress(address);
const showOfflineIcon = address && (isOffline || isOnlineStatusLoading);
const title =
subplebbitData?.title ||
(subplebbitData?.updatedAt ? (
displayAddress.endsWith('.eth') || displayAddress.endsWith('.sol') ? (
displayAddress.slice(0, -4)
) : (
displayAddress
)
) : (
<span className={styles.loadingBoardCellValue}>{isOffline ? t('board_not_reachable') : t('loading_board')}</span>
));
return (
<tr className={styles.subplebbit} key={address}>
<td className={styles.boardAddress}>
<tr key={address}>
<td>
<p className={styles.boardCell}>
{showOfflineIcon && <span className={`${styles.offlineIcon} ${offlineIconClass}`} title={offlineTitle} />}
<Link to={`/p/${address}`}>{displayAddress}</Link>
{nsfwTag && <span className={styles.nsfw}> ({t(nsfwTag)})</span>}
{showOfflineIcon && (
<span className={styles.offlineIconContainer}>
<Tooltip content={offlineTitle}>
<span className={`${styles.offlineIcon} ${offlineIconClass}`} />
</Tooltip>
</span>
)}
</p>
</td>
<td className={styles.boardTitle}>
<p className={styles.boardCell}>{title || displayAddress}</p>
<td>
<p className={styles.boardCell}>{title}</p>
</td>
{!isMobile && (
<>
<td className={styles.boardPPH}>
<p className={styles.boardCell}>{stats.hourPostCount ?? '?'}</p>
<p className={styles.boardCell}>{stats.hourPostCount ?? <span className={styles.loadingBoardCellValue}>?</span>}</p>
</td>
<td className={styles.boardTags}>
<p className={styles.boardCell}>
-96
View File
@@ -29,11 +29,6 @@
border: 1px solid black;
}
.boardsBox {
overflow: hidden;
min-height: 150px;
}
.loading {
display: flex;
justify-content: center;
@@ -41,86 +36,6 @@
margin-top: 10px;
}
.offlineIcon {
display: inline-block;
vertical-align: text-top;
margin-right: 2px;
width: 13px;
height: 13px;
background-repeat: no-repeat;
background-size: contain;
image-rendering: pixelated;
}
.boardsBox table {
display: table;
margin-bottom: 10px;
overflow: hidden;
table-layout: fixed;
width: calc(100% + 4px);
margin-left: -2px;
border-collapse: separate;
border-spacing: 2px 0;
}
.boardsBox table thead th {
background: #fca;
color: #800;
border: 1px solid #800;
padding: 4px 15px 5px 5px;
text-align: left;
white-space: nowrap;
text-transform: capitalize;
}
.boardsBox table tbody td {
margin: 0;
padding: 4px 15px 4px 4px;
text-align: left;
}
.boardsBox table tbody tr:nth-of-type( even ) {
background: #ede2d4;
}
.boardCell {
position: static;
margin: 0;
padding: 0;
box-sizing: border-box;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.boardCell a {
color: inherit;
}
.loadMoreButton {
text-align: center;
margin-bottom: 14px;
color: var(--topbar-separator-color);
}
.displayCount {
font-size: 12px;
text-align: center;
margin-bottom: 10px;
text-transform: lowercase;
}
.loadMoreButton span {
color: var(--topbar-desktop-link-text-color);
text-transform: capitalize;
}
.loadMoreButton span:hover {
cursor: pointer;
color: var(--button-desktop-text-color-hover);
text-decoration: underline;
}
.boxBar, .infoboxBar {
padding-left: 0.5em;
line-height: 2em;
@@ -245,13 +160,6 @@
content: '✔ ';
}
.nsfw {
color: red;
font-weight: 700;
text-transform: uppercase;
font-size: smaller;
}
.footer a {
text-transform: capitalize;
}
@@ -338,10 +246,6 @@
text-transform: capitalize;
}
.boardPPH {
width: 33px;
}
@media (max-width: 640px) {
.content {
min-width: 0;
+2 -1
View File
@@ -9,6 +9,7 @@ import useSubplebbitsStats from '../../hooks/use-subplebbits-stats';
import PopularThreadsBox from './popular-threads-box';
import BoardsList from './boards-list';
import Version from '../../components/version';
import _ from 'lodash';
// https://github.com/plebbit/temporary-default-subplebbits/blob/master/README.md
// plebchan shouldn't consider 'vulgar' or 'anti' as nsfw tags, unlike more sfw-oriented clients
@@ -35,7 +36,7 @@ const SearchBar = () => {
spellCheck='false'
autoCapitalize='off'
type='text'
placeholder={`"board.eth/.sol" ${t('or')} "12D3KooW..."`}
placeholder={_.lowerCase(t('enter_board_address'))}
ref={searchInputRef}
/>
<button className={styles.searchButton}>{t('go')}</button>
-1
View File
@@ -221,7 +221,6 @@
.stateString {
color: var(--post-mobile-abbr-text-color);
padding-left: 20px;
}
.loadingString {