diff --git a/public/translations/ar/default.json b/public/translations/ar/default.json index 115d4bbd..7c0c963b 100644 --- a/public/translations/ar/default.json +++ b/public/translations/ar/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "حساب Bitsocial", "p2p_options": "خيارات P2P", "purge": "حذف نهائي", - "purge_confirm": "سيؤدي الحذف النهائي إلى إزالة هذا المحتوى وجميع الردود عليه من قاعدة البيانات بشكل دائم. متابعة؟" + "purge_confirm": "سيؤدي الحذف النهائي إلى إزالة هذا المحتوى وجميع الردود عليه من قاعدة البيانات بشكل دائم. متابعة؟", + "mod_queue": "قائمة انتظار الإشراف", + "moderation_queue": "قائمة انتظار الإشراف", + "queue_is_empty": "قائمة الانتظار فارغة", + "approve": "الموافقة", + "reject": "الرفض", + "waiting": "في الانتظار", + "filter_by_board": "تصفية حسب اللوحة", + "mod_queue_alert_threshold": "عتبة تنبيه قائمة انتظار الإشراف", + "mod_queue_alert_threshold_tip": "إظهار مؤشر أحمر وامض عندما تكون العناصر في الانتظار لأكثر من هذا الوقت", + "no_content": "لا يوجد محتوى", + "view": "عرض", + "excerpt": "مقتطف" } diff --git a/public/translations/bn/default.json b/public/translations/bn/default.json index a3b5dffa..6efa3f53 100644 --- a/public/translations/bn/default.json +++ b/public/translations/bn/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Bitsocial অ্যাকাউন্ট", "p2p_options": "P2P বিকল্প", "purge": "সর্বনাশ", - "purge_confirm": "পার্জ করা এই সামগ্রী এবং এর সমস্ত উত্তরকে ডাটাবেস থেকে স্থায়ীভাবে সরিয়ে দেবে। চালিয়ে যান?" + "purge_confirm": "পার্জ করা এই সামগ্রী এবং এর সমস্ত উত্তরকে ডাটাবেস থেকে স্থায়ীভাবে সরিয়ে দেবে। চালিয়ে যান?", + "mod_queue": "মডারেশন সারি", + "moderation_queue": "মডারেশন সারি", + "queue_is_empty": "সারি খালি", + "approve": "অনুমোদন করুন", + "reject": "প্রত্যাখ্যান করুন", + "waiting": "অপেক্ষা করছে", + "filter_by_board": "বোর্ড অনুযায়ী ফিল্টার করুন", + "mod_queue_alert_threshold": "মডারেশন সারি সতর্কতা থ্রেশহোল্ড", + "mod_queue_alert_threshold_tip": "আইটেমগুলি এই সময়ের চেয়ে বেশি অপেক্ষা করলে একটি ঝলমলে লাল সূচক দেখান", + "no_content": "কোনো বিষয়বস্তু নেই", + "view": "দেখুন", + "excerpt": "উদ্ধৃতি" } diff --git a/public/translations/cs/default.json b/public/translations/cs/default.json index e77ce71f..222f6672 100644 --- a/public/translations/cs/default.json +++ b/public/translations/cs/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Účet Bitsocial", "p2p_options": "Možnosti P2P", "purge": "vymazat", - "purge_confirm": "Vymazání trvale odstraní tento obsah a všechny jeho odpovědi z databáze. Pokračovat?" + "purge_confirm": "Vymazání trvale odstraní tento obsah a všechny jeho odpovědi z databáze. Pokračovat?", + "mod_queue": "Fronta moderování", + "moderation_queue": "Fronta moderování", + "queue_is_empty": "Fronta je prázdná", + "approve": "Schválit", + "reject": "Odmítnout", + "waiting": "Čeká", + "filter_by_board": "Filtrovat podle desky", + "mod_queue_alert_threshold": "Práh upozornění fronty moderování", + "mod_queue_alert_threshold_tip": "Zobrazit blikající červený indikátor, když položky čekají déle než tento čas", + "no_content": "Žádný obsah", + "view": "Zobrazit", + "excerpt": "Výňatek" } diff --git a/public/translations/da/default.json b/public/translations/da/default.json index 6e625193..3479f785 100644 --- a/public/translations/da/default.json +++ b/public/translations/da/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Bitsocial-konto", "p2p_options": "P2P-indstillinger", "purge": "slet", - "purge_confirm": "Sletning fjerner permanent dette indhold og alle dets svar fra databasen. Fortsætte?" + "purge_confirm": "Sletning fjerner permanent dette indhold og alle dets svar fra databasen. Fortsætte?", + "mod_queue": "Moderationskø", + "moderation_queue": "Moderationskø", + "queue_is_empty": "Køen er tom", + "approve": "Godkend", + "reject": "Afvis", + "waiting": "Venter", + "filter_by_board": "Filtrer efter tavle", + "mod_queue_alert_threshold": "Tærskel for moderationskø-alarm", + "mod_queue_alert_threshold_tip": "Vis en blinkende rød indikator, når elementer venter længere end denne tid", + "no_content": "Intet indhold", + "view": "Vis", + "excerpt": "Uddrag" } diff --git a/public/translations/de/default.json b/public/translations/de/default.json index 13193f65..461f01fe 100644 --- a/public/translations/de/default.json +++ b/public/translations/de/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Bitsocial-Konto", "p2p_options": "P2P-Optionen", "purge": "löschen", - "purge_confirm": "Das Löschen entfernt diesen Inhalt und alle Antworten dauerhaft aus der Datenbank. Fortfahren?" + "purge_confirm": "Das Löschen entfernt diesen Inhalt und alle Antworten dauerhaft aus der Datenbank. Fortfahren?", + "mod_queue": "Moderations-Warteschlange", + "moderation_queue": "Moderations-Warteschlange", + "queue_is_empty": "Warteschlange ist leer", + "approve": "Genehmigen", + "reject": "Ablehnen", + "waiting": "Wartend", + "filter_by_board": "Nach Forum filtern", + "mod_queue_alert_threshold": "Schwellenwert für Moderations-Warteschlangen-Alarm", + "mod_queue_alert_threshold_tip": "Einen blinkenden roten Indikator anzeigen, wenn Elemente länger als diese Zeit warten", + "no_content": "Kein Inhalt", + "view": "Anzeigen", + "excerpt": "Auszug" } diff --git a/public/translations/el/default.json b/public/translations/el/default.json index 761d9214..5a8fae48 100644 --- a/public/translations/el/default.json +++ b/public/translations/el/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Λογαριασμός Bitsocial", "p2p_options": "Επιλογές P2P", "purge": "εκκαθάριση", - "purge_confirm": "Η εκκαθάριση θα αφαιρέσει μόνιμα αυτό το περιεχόμενο και όλες τις απαντήσεις του από τη βάση δεδομένων. Συνέχεια;" + "purge_confirm": "Η εκκαθάριση θα αφαιρέσει μόνιμα αυτό το περιεχόμενο και όλες τις απαντήσεις του από τη βάση δεδομένων. Συνέχεια;", + "mod_queue": "Ουρά Συντονισμού", + "moderation_queue": "Ουρά Συντονισμού", + "queue_is_empty": "Η ουρά είναι άδεια", + "approve": "Έγκριση", + "reject": "Απόρριψη", + "waiting": "Αναμονή", + "filter_by_board": "Φίλτρο ανά σανίδα", + "mod_queue_alert_threshold": "Όριο ειδοποίησης ουράς συντονισμού", + "mod_queue_alert_threshold_tip": "Εμφάνιση αναβοσβήνοντος κόκκινου δείκτη όταν τα στοιχεία περιμένουν περισσότερο από αυτόν τον χρόνο", + "no_content": "Χωρίς περιεχόμενο", + "view": "Προβολή", + "excerpt": "Απόσπασμα" } diff --git a/public/translations/en/default.json b/public/translations/en/default.json index 41f5c25e..e514924d 100644 --- a/public/translations/en/default.json +++ b/public/translations/en/default.json @@ -253,5 +253,17 @@ "bitsocial_account": "Bitsocial Account", "p2p_options": "P2P options", "purge": "purge", - "purge_confirm": "Purging permanently removes this content and all its replies from the database. Continue?" + "purge_confirm": "Purging permanently removes this content and all its replies from the database. Continue?", + "mod_queue": "Mod Queue", + "moderation_queue": "Moderation Queue", + "queue_is_empty": "Queue is empty", + "approve": "Approve", + "reject": "Reject", + "waiting": "Waiting", + "filter_by_board": "Filter by board", + "mod_queue_alert_threshold": "Mod queue alert threshold", + "mod_queue_alert_threshold_tip": "Show a blinking red indicator when items are waiting for longer than this time", + "no_content": "No content", + "view": "View", + "excerpt": "Excerpt" } diff --git a/public/translations/es/default.json b/public/translations/es/default.json index ccd7cd8a..a73b094e 100644 --- a/public/translations/es/default.json +++ b/public/translations/es/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Cuenta Bitsocial", "p2p_options": "Opciones P2P", "purge": "purgar", - "purge_confirm": "Purgar eliminará permanentemente este contenido y todas sus respuestas de la base de datos. ¿Continuar?" + "purge_confirm": "Purgar eliminará permanentemente este contenido y todas sus respuestas de la base de datos. ¿Continuar?", + "mod_queue": "Cola de Moderación", + "moderation_queue": "Cola de Moderación", + "queue_is_empty": "La cola está vacía", + "approve": "Aprobar", + "reject": "Rechazar", + "waiting": "Esperando", + "filter_by_board": "Filtrar por foro", + "mod_queue_alert_threshold": "Umbral de alerta de cola de moderación", + "mod_queue_alert_threshold_tip": "Mostrar un indicador rojo parpadeante cuando los elementos están esperando más de este tiempo", + "no_content": "Sin contenido", + "view": "Ver", + "excerpt": "Extracto" } diff --git a/public/translations/fa/default.json b/public/translations/fa/default.json index ea9abd7d..9006d62c 100644 --- a/public/translations/fa/default.json +++ b/public/translations/fa/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "حساب Bitsocial", "p2p_options": "گزینه‌های P2P", "purge": "پاکسازی", - "purge_confirm": "پاکسازی این محتوا و تمام پاسخ‌های آن را به طور دائم از پایگاه داده حذف می‌کند. ادامه دهید؟" + "purge_confirm": "پاکسازی این محتوا و تمام پاسخ‌های آن را به طور دائم از پایگاه داده حذف می‌کند. ادامه دهید؟", + "mod_queue": "صف انتظار نظارت", + "moderation_queue": "صف انتظار نظارت", + "queue_is_empty": "صف خالی است", + "approve": "تایید", + "reject": "رد", + "waiting": "در انتظار", + "filter_by_board": "فیلتر بر اساس تابلو", + "mod_queue_alert_threshold": "آستانه هشدار صف انتظار نظارت", + "mod_queue_alert_threshold_tip": "نمایش نشانگر قرمز چشمک‌زن زمانی که موارد بیش از این زمان در انتظار هستند", + "no_content": "بدون محتوا", + "view": "مشاهده", + "excerpt": "گزیده" } diff --git a/public/translations/fi/default.json b/public/translations/fi/default.json index 519db32e..6e80ea09 100644 --- a/public/translations/fi/default.json +++ b/public/translations/fi/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Bitsocial-tili", "p2p_options": "P2P-asetukset", "purge": "poistaa", - "purge_confirm": "Poistaminen poistaa pysyvästi tämän sisällön ja kaikki sen vastaukset tietokannasta. Jatketaanko?" + "purge_confirm": "Poistaminen poistaa pysyvästi tämän sisällön ja kaikki sen vastaukset tietokannasta. Jatketaanko?", + "mod_queue": "Moderaatiojono", + "moderation_queue": "Moderaatiojono", + "queue_is_empty": "Jono on tyhjä", + "approve": "Hyväksy", + "reject": "Hylkää", + "waiting": "Odottaa", + "filter_by_board": "Suodata taulun mukaan", + "mod_queue_alert_threshold": "Moderaatiojonon hälytyskynnys", + "mod_queue_alert_threshold_tip": "Näytä vilkkuva punainen indikaattori, kun kohteet odottavat pidempään kuin tämä aika", + "no_content": "Ei sisältöä", + "view": "Näytä", + "excerpt": "Ote" } diff --git a/public/translations/fil/default.json b/public/translations/fil/default.json index 3ce435b3..6e0f6bce 100644 --- a/public/translations/fil/default.json +++ b/public/translations/fil/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Account ng Bitsocial", "p2p_options": "Mga Opsyon ng P2P", "purge": "purga", - "purge_confirm": "Ang pag-purge ay permanenteng mag-aalis ng nilalamang ito at lahat ng mga tugon nito mula sa database. Magpatuloy?" + "purge_confirm": "Ang pag-purge ay permanenteng mag-aalis ng nilalamang ito at lahat ng mga tugon nito mula sa database. Magpatuloy?", + "mod_queue": "Moderasyon Queue", + "moderation_queue": "Moderasyon Queue", + "queue_is_empty": "Walang laman ang queue", + "approve": "Aprubahan", + "reject": "Tanggihan", + "waiting": "Naghihintay", + "filter_by_board": "I-filter ayon sa board", + "mod_queue_alert_threshold": "Moderasyon queue alert threshold", + "mod_queue_alert_threshold_tip": "Magpakita ng kumikislap na pulang indicator kapag ang mga item ay naghihintay nang mas mahaba kaysa sa oras na ito", + "no_content": "Walang nilalaman", + "view": "Tingnan", + "excerpt": "Sipi" } diff --git a/public/translations/fr/default.json b/public/translations/fr/default.json index 4715fb81..09d17600 100644 --- a/public/translations/fr/default.json +++ b/public/translations/fr/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Compte Bitsocial", "p2p_options": "Options P2P", "purge": "purger", - "purge_confirm": "La purge supprime définitivement ce contenu et toutes ses réponses de la base de données. Continuer?" + "purge_confirm": "La purge supprime définitivement ce contenu et toutes ses réponses de la base de données. Continuer?", + "mod_queue": "File de Modération", + "moderation_queue": "File de Modération", + "queue_is_empty": "La file est vide", + "approve": "Approuver", + "reject": "Rejeter", + "waiting": "En attente", + "filter_by_board": "Filtrer par forum", + "mod_queue_alert_threshold": "Seuil d'alerte de la file de modération", + "mod_queue_alert_threshold_tip": "Afficher un indicateur rouge clignotant lorsque les éléments attendent plus longtemps que ce temps", + "no_content": "Aucun contenu", + "view": "Voir", + "excerpt": "Extrait" } diff --git a/public/translations/he/default.json b/public/translations/he/default.json index 61fd928d..be47e59e 100644 --- a/public/translations/he/default.json +++ b/public/translations/he/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "חשבון Bitsocial", "p2p_options": "אפשרויות P2P", "purge": "למחוק", - "purge_confirm": "מחיקה תסיר לצמיתות את התוכן הזה ואת כל התגובות שלו מהמסד הנתונים. להמשיך?" + "purge_confirm": "מחיקה תסיר לצמיתות את התוכן הזה ואת כל התגובות שלו מהמסד הנתונים. להמשיך?", + "mod_queue": "תור ניהול", + "moderation_queue": "תור ניהול", + "queue_is_empty": "התור ריק", + "approve": "אשר", + "reject": "דחה", + "waiting": "ממתין", + "filter_by_board": "סנן לפי לוח", + "mod_queue_alert_threshold": "סף התרעה של תור ניהול", + "mod_queue_alert_threshold_tip": "הצג אינדיקטור אדום מהבהב כאשר פריטים ממתינים יותר מזמן זה", + "no_content": "אין תוכן", + "view": "הצג", + "excerpt": "תקציר" } diff --git a/public/translations/hi/default.json b/public/translations/hi/default.json index 3ac211f1..edb657f6 100644 --- a/public/translations/hi/default.json +++ b/public/translations/hi/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Bitsocial खाता", "p2p_options": "P2P विकल्प", "purge": "स्थायी रूप से हटाएं", - "purge_confirm": "स्थायी रूप से हटाने से यह सामग्री और इसकी सभी प्रतिक्रियाएं डेटाबेस से स्थायी रूप से हट जाएंगी। जारी रखें?" + "purge_confirm": "स्थायी रूप से हटाने से यह सामग्री और इसकी सभी प्रतिक्रियाएं डेटाबेस से स्थायी रूप से हट जाएंगी। जारी रखें?", + "mod_queue": "मॉडरेटर कतार", + "moderation_queue": "मॉडरेटर कतार", + "queue_is_empty": "कतार खाली है", + "approve": "अनुमोदन करें", + "reject": "अस्वीकार करें", + "waiting": "प्रतीक्षा कर रहा है", + "filter_by_board": "बोर्ड के अनुसार फ़िल्टर करें", + "mod_queue_alert_threshold": "मॉडरेटर कतार सतर्कता सीमा", + "mod_queue_alert_threshold_tip": "जब आइटम इस समय से अधिक समय तक प्रतीक्षा कर रहे हों तो एक चमकता हुआ लाल संकेतक दिखाएं", + "no_content": "कोई सामग्री नहीं", + "view": "देखें", + "excerpt": "अंश" } diff --git a/public/translations/hu/default.json b/public/translations/hu/default.json index ac953fbe..b652ec2d 100644 --- a/public/translations/hu/default.json +++ b/public/translations/hu/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Bitsocial fiók", "p2p_options": "P2P beállítások", "purge": "törlés", - "purge_confirm": "A törlés véglegesen eltávolítja ezt a tartalmat és az összes válaszát az adatbázisból. Folytatás?" + "purge_confirm": "A törlés véglegesen eltávolítja ezt a tartalmat és az összes válaszát az adatbázisból. Folytatás?", + "mod_queue": "Moderációs Sor", + "moderation_queue": "Moderációs Sor", + "queue_is_empty": "A sor üres", + "approve": "Jóváhagyás", + "reject": "Elutasítás", + "waiting": "Várakozás", + "filter_by_board": "Szűrés tábla szerint", + "mod_queue_alert_threshold": "Moderációs sor riasztási küszöb", + "mod_queue_alert_threshold_tip": "Villogó piros jelző megjelenítése, amikor az elemek ennél hosszabb ideig várakoznak", + "no_content": "Nincs tartalom", + "view": "Megtekintés", + "excerpt": "Kivonat" } diff --git a/public/translations/id/default.json b/public/translations/id/default.json index 0550c9e2..a440b3f4 100644 --- a/public/translations/id/default.json +++ b/public/translations/id/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Akun Bitsocial", "p2p_options": "Opsi P2P", "purge": "hapus", - "purge_confirm": "Menghapus akan menghapus konten ini dan semua balasannya secara permanen dari database. Lanjutkan?" + "purge_confirm": "Menghapus akan menghapus konten ini dan semua balasannya secara permanen dari database. Lanjutkan?", + "mod_queue": "Antrian Moderasi", + "moderation_queue": "Antrian Moderasi", + "queue_is_empty": "Antrian kosong", + "approve": "Setujui", + "reject": "Tolak", + "waiting": "Menunggu", + "filter_by_board": "Filter berdasarkan papan", + "mod_queue_alert_threshold": "Ambang peringatan antrian moderasi", + "mod_queue_alert_threshold_tip": "Tampilkan indikator merah berkedip ketika item menunggu lebih lama dari waktu ini", + "no_content": "Tidak ada konten", + "view": "Lihat", + "excerpt": "Kutipan" } diff --git a/public/translations/it/default.json b/public/translations/it/default.json index 2a1c20f0..b7ac1d11 100644 --- a/public/translations/it/default.json +++ b/public/translations/it/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Account Bitsocial", "p2p_options": "Opzioni P2P", "purge": "elimina", - "purge_confirm": "L'eliminazione rimuoverà permanentemente questo contenuto e tutte le sue risposte dal database. Continuare?" + "purge_confirm": "L'eliminazione rimuoverà permanentemente questo contenuto e tutte le sue risposte dal database. Continuare?", + "mod_queue": "Coda Moderazione", + "moderation_queue": "Coda di Moderazione", + "queue_is_empty": "La coda è vuota", + "approve": "Approva", + "reject": "Rifiuta", + "waiting": "In attesa", + "filter_by_board": "Filtra per board", + "mod_queue_alert_threshold": "Soglia di allerta coda moderazione", + "mod_queue_alert_threshold_tip": "Mostra un indicatore rosso lampeggiante quando gli elementi attendono più di questo tempo", + "no_content": "Nessun contenuto", + "view": "Visualizza", + "excerpt": "Estratto" } diff --git a/public/translations/ja/default.json b/public/translations/ja/default.json index 46802ab9..3c854b78 100644 --- a/public/translations/ja/default.json +++ b/public/translations/ja/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Bitsocialアカウント", "p2p_options": "P2Pオプション", "purge": "削除", - "purge_confirm": "削除すると、このコンテンツとそのすべての返信がデータベースから永続的に削除されます。続行しますか?" + "purge_confirm": "削除すると、このコンテンツとそのすべての返信がデータベースから永続的に削除されます。続行しますか?", + "mod_queue": "モデレーションキュー", + "moderation_queue": "モデレーションキュー", + "queue_is_empty": "キューは空です", + "approve": "承認", + "reject": "拒否", + "waiting": "待機中", + "filter_by_board": "ボードでフィルター", + "mod_queue_alert_threshold": "モデレーションキューのアラート閾値", + "mod_queue_alert_threshold_tip": "アイテムがこの時間より長く待機している場合、点滅する赤いインジケーターを表示", + "no_content": "コンテンツなし", + "view": "表示", + "excerpt": "抜粋" } diff --git a/public/translations/ko/default.json b/public/translations/ko/default.json index 3036bf2a..d38916c9 100644 --- a/public/translations/ko/default.json +++ b/public/translations/ko/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Bitsocial 계정", "p2p_options": "P2P 옵션", "purge": "삭제", - "purge_confirm": "삭제하면 이 콘텐츠와 모든 답글이 데이터베이스에서 영구적으로 제거됩니다. 계속하시겠습니까?" + "purge_confirm": "삭제하면 이 콘텐츠와 모든 답글이 데이터베이스에서 영구적으로 제거됩니다. 계속하시겠습니까?", + "mod_queue": "관리자 대기열", + "moderation_queue": "관리자 대기열", + "queue_is_empty": "대기열이 비어 있습니다", + "approve": "승인", + "reject": "거부", + "waiting": "대기 중", + "filter_by_board": "게시판별 필터", + "mod_queue_alert_threshold": "관리자 대기열 알림 임계값", + "mod_queue_alert_threshold_tip": "항목이 이 시간보다 오래 대기 중일 때 깜빡이는 빨간색 표시기 표시", + "no_content": "콘텐츠 없음", + "view": "보기", + "excerpt": "요약" } diff --git a/public/translations/mr/default.json b/public/translations/mr/default.json index c7de38aa..3c246ce5 100644 --- a/public/translations/mr/default.json +++ b/public/translations/mr/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Bitsocial खाते", "p2p_options": "P2P पर्याय", "purge": "हटवा", - "purge_confirm": "पर्ज करणे या सामग्रीला आणि त्याच्या सर्व प्रतिक्रियांना डेटाबेसमधून कायमस्वरूपी काढून टाकेल. पुढे जाऊ?" + "purge_confirm": "पर्ज करणे या सामग्रीला आणि त्याच्या सर्व प्रतिक्रियांना डेटाबेसमधून कायमस्वरूपी काढून टाकेल. पुढे जाऊ?", + "mod_queue": "मॉडरेशन रांग", + "moderation_queue": "मॉडरेशन रांग", + "queue_is_empty": "रांग रिकामी आहे", + "approve": "मंजूर करा", + "reject": "नकार द्या", + "waiting": "प्रतीक्षा करत आहे", + "filter_by_board": "बोर्डनुसार फिल्टर करा", + "mod_queue_alert_threshold": "मॉडरेशन रांग सतर्कता मर्यादा", + "mod_queue_alert_threshold_tip": "जेव्हा आयटम या वेळेपेक्षा जास्त वेळ प्रतीक्षा करत असतात तेव्हा एक चमकणारा लाल सूचक दाखवा", + "no_content": "सामग्री नाही", + "view": "पहा", + "excerpt": "उतारा" } diff --git a/public/translations/nl/default.json b/public/translations/nl/default.json index 0cbdfe27..24dd3e1d 100644 --- a/public/translations/nl/default.json +++ b/public/translations/nl/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Bitsocial-account", "p2p_options": "P2P-opties", "purge": "verwijderen", - "purge_confirm": "Verwijderen verwijdert deze inhoud en alle antwoorden ervan permanent uit de database. Doorgaan?" + "purge_confirm": "Verwijderen verwijdert deze inhoud en alle antwoorden ervan permanent uit de database. Doorgaan?", + "mod_queue": "Moderatie Wachtrij", + "moderation_queue": "Moderatie Wachtrij", + "queue_is_empty": "Wachtrij is leeg", + "approve": "Goedkeuren", + "reject": "Afwijzen", + "waiting": "Wachten", + "filter_by_board": "Filteren op bord", + "mod_queue_alert_threshold": "Drempelwaarde voor moderatiewachtrijwaarschuwing", + "mod_queue_alert_threshold_tip": "Toon een knipperende rode indicator wanneer items langer dan deze tijd wachten", + "no_content": "Geen inhoud", + "view": "Bekijken", + "excerpt": "Uittreksel" } diff --git a/public/translations/no/default.json b/public/translations/no/default.json index 42cea7b8..5d75f174 100644 --- a/public/translations/no/default.json +++ b/public/translations/no/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Bitsocial-konto", "p2p_options": "P2P-alternativer", "purge": "slette", - "purge_confirm": "Sletting fjerner permanent dette innholdet og alle svarene fra databasen. Fortsette?" + "purge_confirm": "Sletting fjerner permanent dette innholdet og alle svarene fra databasen. Fortsette?", + "mod_queue": "Modereringskø", + "moderation_queue": "Modereringskø", + "queue_is_empty": "Køen er tom", + "approve": "Godkjenn", + "reject": "Avvis", + "waiting": "Venter", + "filter_by_board": "Filtrer etter tavle", + "mod_queue_alert_threshold": "Terskel for modereringskø-varsel", + "mod_queue_alert_threshold_tip": "Vis en blinkende rød indikator når elementer venter lenger enn denne tiden", + "no_content": "Ingen innhold", + "view": "Vis", + "excerpt": "Utdrag" } diff --git a/public/translations/pl/default.json b/public/translations/pl/default.json index a51fc4c8..1a32dbae 100644 --- a/public/translations/pl/default.json +++ b/public/translations/pl/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Konto Bitsocial", "p2p_options": "Opcje P2P", "purge": "usuń", - "purge_confirm": "Usunięcie trwale usunie tę zawartość i wszystkie odpowiedzi z bazy danych. Kontynuować?" + "purge_confirm": "Usunięcie trwale usunie tę zawartość i wszystkie odpowiedzi z bazy danych. Kontynuować?", + "mod_queue": "Kolejka Moderacji", + "moderation_queue": "Kolejka Moderacji", + "queue_is_empty": "Kolejka jest pusta", + "approve": "Zatwierdź", + "reject": "Odrzuć", + "waiting": "Oczekiwanie", + "filter_by_board": "Filtruj według tablicy", + "mod_queue_alert_threshold": "Próg alertu kolejki moderacji", + "mod_queue_alert_threshold_tip": "Pokaż migający czerwony wskaźnik, gdy elementy czekają dłużej niż ten czas", + "no_content": "Brak zawartości", + "view": "Zobacz", + "excerpt": "Fragment" } diff --git a/public/translations/pt/default.json b/public/translations/pt/default.json index ad18e9a4..382b2fd4 100644 --- a/public/translations/pt/default.json +++ b/public/translations/pt/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Conta Bitsocial", "p2p_options": "Opções P2P", "purge": "purgar", - "purge_confirm": "Purgar removerá permanentemente este conteúdo e todas as suas respostas do banco de dados. Continuar?" + "purge_confirm": "Purgar removerá permanentemente este conteúdo e todas as suas respostas do banco de dados. Continuar?", + "mod_queue": "Fila de Moderação", + "moderation_queue": "Fila de Moderação", + "queue_is_empty": "A fila está vazia", + "approve": "Aprovar", + "reject": "Rejeitar", + "waiting": "Aguardando", + "filter_by_board": "Filtrar por quadro", + "mod_queue_alert_threshold": "Limite de alerta da fila de moderação", + "mod_queue_alert_threshold_tip": "Mostrar um indicador vermelho piscante quando os itens estão aguardando por mais tempo que este tempo", + "no_content": "Sem conteúdo", + "view": "Ver", + "excerpt": "Trecho" } diff --git a/public/translations/ro/default.json b/public/translations/ro/default.json index fb483820..fc6bd6df 100644 --- a/public/translations/ro/default.json +++ b/public/translations/ro/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Cont Bitsocial", "p2p_options": "Opțiuni P2P", "purge": "șterge", - "purge_confirm": "Ștergerea va elimina permanent acest conținut și toate răspunsurile sale din baza de date. Continuați?" + "purge_confirm": "Ștergerea va elimina permanent acest conținut și toate răspunsurile sale din baza de date. Continuați?", + "mod_queue": "Coadă de Moderare", + "moderation_queue": "Coadă de Moderare", + "queue_is_empty": "Coada este goală", + "approve": "Aprobă", + "reject": "Respinge", + "waiting": "Așteaptă", + "filter_by_board": "Filtrează după panou", + "mod_queue_alert_threshold": "Prag de alertă pentru coada de moderare", + "mod_queue_alert_threshold_tip": "Afișează un indicator roșu intermitent când elementele așteaptă mai mult decât acest timp", + "no_content": "Fără conținut", + "view": "Vizualizează", + "excerpt": "Fragment" } diff --git a/public/translations/ru/default.json b/public/translations/ru/default.json index df744ef6..49e4c0bd 100644 --- a/public/translations/ru/default.json +++ b/public/translations/ru/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Аккаунт Bitsocial", "p2p_options": "Опции P2P", "purge": "удалить", - "purge_confirm": "Удаление навсегда удалит этот контент и все ответы на него из базы данных. Продолжить?" + "purge_confirm": "Удаление навсегда удалит этот контент и все ответы на него из базы данных. Продолжить?", + "mod_queue": "Очередь Модерации", + "moderation_queue": "Очередь Модерации", + "queue_is_empty": "Очередь пуста", + "approve": "Одобрить", + "reject": "Отклонить", + "waiting": "Ожидание", + "filter_by_board": "Фильтр по доске", + "mod_queue_alert_threshold": "Порог предупреждения очереди модерации", + "mod_queue_alert_threshold_tip": "Показывать мигающий красный индикатор, когда элементы ждут дольше этого времени", + "no_content": "Нет содержимого", + "view": "Просмотр", + "excerpt": "Выдержка" } diff --git a/public/translations/sq/default.json b/public/translations/sq/default.json index 0e89d472..bf2eb69d 100644 --- a/public/translations/sq/default.json +++ b/public/translations/sq/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Llogaria Bitsocial", "p2p_options": "Opsionet P2P", "purge": "fshi", - "purge_confirm": "Fshirja do të heqë përgjithmonë këtë përmbajtje dhe të gjitha përgjigjet e saj nga baza e të dhënave. Vazhdo?" + "purge_confirm": "Fshirja do të heqë përgjithmonë këtë përmbajtje dhe të gjitha përgjigjet e saj nga baza e të dhënave. Vazhdo?", + "mod_queue": "Radha e Moderimit", + "moderation_queue": "Radha e Moderimit", + "queue_is_empty": "Radha është e zbrazët", + "approve": "Aprovo", + "reject": "Refuzo", + "waiting": "Duke pritur", + "filter_by_board": "Filtro sipas tabelës", + "mod_queue_alert_threshold": "Pragu i alarmit të radhës së moderimit", + "mod_queue_alert_threshold_tip": "Shfaq një tregues të kuq që blinkon kur elementët po presin më shumë se kjo kohë", + "no_content": "Pa përmbajtje", + "view": "Shiko", + "excerpt": "Fragment" } diff --git a/public/translations/sv/default.json b/public/translations/sv/default.json index 595a30ef..af028034 100644 --- a/public/translations/sv/default.json +++ b/public/translations/sv/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Bitsocial-konto", "p2p_options": "P2P-alternativ", "purge": "radera", - "purge_confirm": "Raderande tar permanent bort detta innehåll och alla dess svar från databasen. Fortsätta?" + "purge_confirm": "Raderande tar permanent bort detta innehåll och alla dess svar från databasen. Fortsätta?", + "mod_queue": "Modereringskö", + "moderation_queue": "Modereringskö", + "queue_is_empty": "Kön är tom", + "approve": "Godkänn", + "reject": "Avvisa", + "waiting": "Väntar", + "filter_by_board": "Filtrera efter tavla", + "mod_queue_alert_threshold": "Tröskelvärde för modereringskö-varning", + "mod_queue_alert_threshold_tip": "Visa en blinkande röd indikator när objekt väntar längre än denna tid", + "no_content": "Inget innehåll", + "view": "Visa", + "excerpt": "Utdrag" } diff --git a/public/translations/te/default.json b/public/translations/te/default.json index b3325a29..09becb46 100644 --- a/public/translations/te/default.json +++ b/public/translations/te/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Bitsocial ఖాతా", "p2p_options": "P2P ఎంపికలు", "purge": "తొలగించు", - "purge_confirm": "పర్జ్ చేయడం ఈ కంటెంట్ మరియు దాని అన్ని ప్రతిస్పందనలను డేటాబేస్ నుండి శాశ్వతంగా తొలగిస్తుంది. కొనసాగించాలా?" + "purge_confirm": "పర్జ్ చేయడం ఈ కంటెంట్ మరియు దాని అన్ని ప్రతిస్పందనలను డేటాబేస్ నుండి శాశ్వతంగా తొలగిస్తుంది. కొనసాగించాలా?", + "mod_queue": "మోడరేషన్ క్యూ", + "moderation_queue": "మోడరేషన్ క్యూ", + "queue_is_empty": "క్యూ ఖాళీగా ఉంది", + "approve": "ఆమోదించు", + "reject": "తిరస్కరించు", + "waiting": "వేచి ఉంది", + "filter_by_board": "బోర్డ్ ద్వారా ఫిల్టర్ చేయి", + "mod_queue_alert_threshold": "మోడరేషన్ క్యూ హెచ్చరిక థ్రెషోల్డ్", + "mod_queue_alert_threshold_tip": "అంశాలు ఈ సమయం కంటే ఎక్కువ సమయం వేచి ఉన్నప్పుడు మెరిసే ఎరుపు సూచికను చూపించు", + "no_content": "కంటెంట్ లేదు", + "view": "చూడు", + "excerpt": "ఎక్సెర్ప్ట్" } diff --git a/public/translations/th/default.json b/public/translations/th/default.json index 290c59c1..3886e88a 100644 --- a/public/translations/th/default.json +++ b/public/translations/th/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "บัญชี Bitsocial", "p2p_options": "ตัวเลือก P2P", "purge": "ลบ", - "purge_confirm": "การลบจะลบเนื้อหานี้และคำตอบทั้งหมดออกจากฐานข้อมูลอย่างถาวร ดำเนินการต่อหรือไม่?" + "purge_confirm": "การลบจะลบเนื้อหานี้และคำตอบทั้งหมดออกจากฐานข้อมูลอย่างถาวร ดำเนินการต่อหรือไม่?", + "mod_queue": "คิวผู้ดูแล", + "moderation_queue": "คิวผู้ดูแล", + "queue_is_empty": "คิวว่างเปล่า", + "approve": "อนุมัติ", + "reject": "ปฏิเสธ", + "waiting": "กำลังรอ", + "filter_by_board": "กรองตามบอร์ด", + "mod_queue_alert_threshold": "เกณฑ์การแจ้งเตือนคิวผู้ดูแล", + "mod_queue_alert_threshold_tip": "แสดงตัวบ่งชี้สีแดงกะพริบเมื่อรายการรอเกินเวลานี้", + "no_content": "ไม่มีเนื้อหา", + "view": "ดู", + "excerpt": "ข้อความย่อ" } diff --git a/public/translations/tr/default.json b/public/translations/tr/default.json index 2dc96e16..bea286f6 100644 --- a/public/translations/tr/default.json +++ b/public/translations/tr/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Bitsocial Hesabı", "p2p_options": "P2P Seçenekleri", "purge": "temizle", - "purge_confirm": "Temizleme, bu içeriği ve tüm yanıtlarını veritabanından kalıcı olarak kaldıracaktır. Devam edilsin mi?" + "purge_confirm": "Temizleme, bu içeriği ve tüm yanıtlarını veritabanından kalıcı olarak kaldıracaktır. Devam edilsin mi?", + "mod_queue": "Moderasyon Kuyruğu", + "moderation_queue": "Moderasyon Kuyruğu", + "queue_is_empty": "Kuyruk boş", + "approve": "Onayla", + "reject": "Reddet", + "waiting": "Bekliyor", + "filter_by_board": "Panoya göre filtrele", + "mod_queue_alert_threshold": "Moderasyon kuyruğu uyarı eşiği", + "mod_queue_alert_threshold_tip": "Öğeler bu süreden daha uzun süre beklediğinde yanıp sönen kırmızı bir gösterge göster", + "no_content": "İçerik yok", + "view": "Görüntüle", + "excerpt": "Alıntı" } diff --git a/public/translations/uk/default.json b/public/translations/uk/default.json index 9e794af8..1930d1f2 100644 --- a/public/translations/uk/default.json +++ b/public/translations/uk/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Обліковий запис Bitsocial", "p2p_options": "Опції P2P", "purge": "видалити", - "purge_confirm": "Видалення назавжди видалить цей контент і всі відповіді на нього з бази даних. Продовжити?" + "purge_confirm": "Видалення назавжди видалить цей контент і всі відповіді на нього з бази даних. Продовжити?", + "mod_queue": "Черга Модерації", + "moderation_queue": "Черга Модерації", + "queue_is_empty": "Черга порожня", + "approve": "Схвалити", + "reject": "Відхилити", + "waiting": "Очікування", + "filter_by_board": "Фільтр за дошкою", + "mod_queue_alert_threshold": "Поріг попередження черги модерації", + "mod_queue_alert_threshold_tip": "Показувати мигаючий червоний індикатор, коли елементи чекають довше цього часу", + "no_content": "Немає вмісту", + "view": "Перегляд", + "excerpt": "Витяг" } diff --git a/public/translations/ur/default.json b/public/translations/ur/default.json index bdf11d49..e7d6cbb1 100644 --- a/public/translations/ur/default.json +++ b/public/translations/ur/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Bitsocial اکاؤنٹ", "p2p_options": "P2P اختیارات", "purge": "ختم کرنا", - "purge_confirm": "پرج کرنا اس مواد اور اس کی تمام جوابات کو ڈیٹا بیس سے مستقل طور پر ہٹا دے گا۔ جاری رکھیں؟" + "purge_confirm": "پرج کرنا اس مواد اور اس کی تمام جوابات کو ڈیٹا بیس سے مستقل طور پر ہٹا دے گا۔ جاری رکھیں؟", + "mod_queue": "نگرانی کی قطار", + "moderation_queue": "نگرانی کی قطار", + "queue_is_empty": "قطار خالی ہے", + "approve": "منظور کریں", + "reject": "مسترد کریں", + "waiting": "انتظار کر رہا ہے", + "filter_by_board": "بورڈ کے لحاظ سے فلٹر کریں", + "mod_queue_alert_threshold": "نگرانی کی قطار کی انتباہ کی حد", + "mod_queue_alert_threshold_tip": "جب آئٹمز اس وقت سے زیادہ انتظار کر رہے ہوں تو ایک ٹمٹمانے والا سرخ اشارہ دکھائیں", + "no_content": "کوئی مواد نہیں", + "view": "دیکھیں", + "excerpt": "اقتباس" } diff --git a/public/translations/vi/default.json b/public/translations/vi/default.json index 2e9225c0..a451e8bd 100644 --- a/public/translations/vi/default.json +++ b/public/translations/vi/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Tài khoản Bitsocial", "p2p_options": "Tùy chọn P2P", "purge": "xóa", - "purge_confirm": "Xóa sẽ loại bỏ vĩnh viễn nội dung này và tất cả các phản hồi của nó khỏi cơ sở dữ liệu. Tiếp tục?" + "purge_confirm": "Xóa sẽ loại bỏ vĩnh viễn nội dung này và tất cả các phản hồi của nó khỏi cơ sở dữ liệu. Tiếp tục?", + "mod_queue": "Hàng Đợi Kiểm Duyệt", + "moderation_queue": "Hàng Đợi Kiểm Duyệt", + "queue_is_empty": "Hàng đợi trống", + "approve": "Phê duyệt", + "reject": "Từ chối", + "waiting": "Đang chờ", + "filter_by_board": "Lọc theo bảng", + "mod_queue_alert_threshold": "Ngưỡng cảnh báo hàng đợi kiểm duyệt", + "mod_queue_alert_threshold_tip": "Hiển thị chỉ báo màu đỏ nhấp nháy khi các mục đang chờ lâu hơn thời gian này", + "no_content": "Không có nội dung", + "view": "Xem", + "excerpt": "Trích đoạn" } diff --git a/public/translations/zh/default.json b/public/translations/zh/default.json index e59e5214..c53209f0 100644 --- a/public/translations/zh/default.json +++ b/public/translations/zh/default.json @@ -254,5 +254,17 @@ "bitsocial_account": "Bitsocial账户", "p2p_options": "P2P选项", "purge": "清除", - "purge_confirm": "清除将永久删除此内容及其所有回复。继续吗?" + "purge_confirm": "清除将永久删除此内容及其所有回复。继续吗?", + "mod_queue": "审核队列", + "moderation_queue": "审核队列", + "queue_is_empty": "队列为空", + "approve": "批准", + "reject": "拒绝", + "waiting": "等待中", + "filter_by_board": "按板块筛选", + "mod_queue_alert_threshold": "审核队列警报阈值", + "mod_queue_alert_threshold_tip": "当项目等待时间超过此时长时显示闪烁的红色指示器", + "no_content": "无内容", + "view": "查看", + "excerpt": "摘要" } diff --git a/src/app.tsx b/src/app.tsx index 48b75251..50dbab8c 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -10,7 +10,7 @@ import useSpecialThemeStore from './stores/use-special-theme-store'; import useIsMobile from './hooks/use-is-mobile'; import useTheme from './hooks/use-theme'; import { useDefaultSubplebbits } from './hooks/use-default-subplebbits'; -import { getSubplebbitAddress, isPostRoute, isPendingPostRoute } from './lib/utils/route-utils'; +import { getSubplebbitAddress, isPostRoute, isPendingPostRoute, isModQueueRoute } from './lib/utils/route-utils'; import styles from './app.module.css'; import FAQ from './views/faq'; import Home from './views/home'; @@ -18,6 +18,7 @@ import Rules from './views/rules'; import NotFound from './views/not-found'; import PendingPost from './views/pending-post'; import Post from './views/post'; +import ModQueueView from './views/mod-queue'; import { DesktopBoardButtons, MobileBoardButtons } from './components/board-buttons'; import BoardHeader from './components/board-header'; import ChallengeModal from './components/challenge-modal'; @@ -50,6 +51,7 @@ const BoardLayout = () => { const isOnPostRoute = isPostRoute(location.pathname); const isOnPendingPostRoute = isPendingPostRoute(location.pathname); + const isOnModQueueRoute = isModQueueRoute(location.pathname); // Christmas theme const { isEnabled: isSpecialEnabled } = useSpecialThemeStore(); @@ -95,7 +97,7 @@ const BoardLayout = () => { )} - {(isOnPostRoute || isOnPendingPostRoute) && } + {(isOnPostRoute || isOnPendingPostRoute || isOnModQueueRoute) && } ); }; @@ -168,11 +170,17 @@ const App = () => ( + } /> + } /> + + } /> + } /> + } /> } /> diff --git a/src/components/board-buttons/board-buttons.tsx b/src/components/board-buttons/board-buttons.tsx index 1b942a63..52618310 100644 --- a/src/components/board-buttons/board-buttons.tsx +++ b/src/components/board-buttons/board-buttons.tsx @@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'; import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'; import { useAccountComment, useSubscribe } from '@plebbit/plebbit-react-hooks'; import useSubplebbitsPagesStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits-pages'; -import { isAllView, isCatalogView, isModView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; +import { isAllView, isCatalogView, isModView, isModQueueView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits'; import { getBoardPath, isDirectoryBoard } from '../../lib/utils/route-utils'; import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address'; @@ -26,6 +26,7 @@ interface BoardButtonsProps { isInCatalogView?: boolean; isInSubscriptionsView?: boolean; isInModView?: boolean; + isInModQueueView?: boolean; isTopbar?: boolean; } @@ -72,7 +73,7 @@ const SubscribeButton = ({ address }: BoardButtonsProps) => { ); }; -const ReturnButton = ({ address, isInAllView, isInSubscriptionsView, isInModView }: BoardButtonsProps) => { +const ReturnButton = ({ address, isInAllView, isInSubscriptionsView, isInModView, isInModQueueView }: BoardButtonsProps) => { const { t } = useTranslation(); const params = useParams(); const defaultSubplebbits = useDefaultSubplebbits(); @@ -84,6 +85,12 @@ const ReturnButton = ({ address, isInAllView, isInSubscriptionsView, isInModView } else if (isInSubscriptionsView) { if (params?.timeFilterName) return `/subs/${params.timeFilterName}`; return `/subs`; + } else if (isInModQueueView) { + // If in mod queue view, return to /mod or /:boardIdentifier + if (params?.boardIdentifier) { + return `/${params.boardIdentifier}`; + } + return `/mod`; } else if (isInModView) { if (params?.timeFilterName) return `/mod/${params.timeFilterName}`; return `/mod`; @@ -309,6 +316,7 @@ export const MobileBoardButtons = () => { const isInPostView = isPostPageView(location.pathname, params); const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams()); const isInModView = isModView(location.pathname); + const isInModQueueView = isModQueueView(location.pathname); const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any }); const resolvedAddress = useResolvedSubplebbitAddress(); @@ -334,6 +342,17 @@ export const MobileBoardButtons = () => { + ) : isInModQueueView ? ( + <> + + + ) : ( <> {isInCatalogView ? ( @@ -419,6 +438,7 @@ export const DesktopBoardButtons = () => { const isInPostView = isPostPageView(location.pathname, params); const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams()); const isInModView = isModView(location.pathname); + const isInModQueueView = isModQueueView(location.pathname); const { filteredCount, searchText } = useCatalogFiltersStore(); @@ -442,6 +462,19 @@ export const DesktopBoardButtons = () => { + ) : isInModQueueView ? ( + <> + [ + + ] [ + ] + ) : ( <> {isInCatalogView ? ( diff --git a/src/hooks/use-account-subplebbits-with-metadata.ts b/src/hooks/use-account-subplebbits-with-metadata.ts new file mode 100644 index 00000000..3b89e2a7 --- /dev/null +++ b/src/hooks/use-account-subplebbits-with-metadata.ts @@ -0,0 +1,15 @@ +import { useAccountSubplebbits } from '@plebbit/plebbit-react-hooks'; +import { useMemo } from 'react'; +import { MultisubSubplebbit } from './use-default-subplebbits'; + +export const useAccountSubplebbitsWithMetadata = (): MultisubSubplebbit[] => { + const { accountSubplebbits } = useAccountSubplebbits(); + + return useMemo(() => { + return Object.entries(accountSubplebbits || {}).map(([address, sub]) => ({ + address, + title: (sub as any).title, + // We can map other fields if needed + })); + }, [accountSubplebbits]); +}; diff --git a/src/hooks/use-stable-subplebbit.ts b/src/hooks/use-stable-subplebbit.ts index d9125050..38e0db1f 100644 --- a/src/hooks/use-stable-subplebbit.ts +++ b/src/hooks/use-stable-subplebbit.ts @@ -1,4 +1,3 @@ -import { useMemo } from 'react'; import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits'; /** diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts new file mode 100644 index 00000000..d78778c4 --- /dev/null +++ b/src/lib/utils/index.ts @@ -0,0 +1,29 @@ +import { + isAllView, + isBoardView, + isCatalogView, + isHomeView, + isModView, + isModQueueView, + isPendingPostView, + isPostPageView, + isSettingsView, + isSubscriptionsView, + isNotFoundView, + ParamsType, +} from './view-utils'; + +export { + isAllView, + isBoardView, + isCatalogView, + isHomeView, + isModView, + isModQueueView, + isPendingPostView, + isPostPageView, + isSettingsView, + isSubscriptionsView, + isNotFoundView, +}; +export type { ParamsType }; diff --git a/src/lib/utils/route-utils.ts b/src/lib/utils/route-utils.ts index 6089d40a..c3782c1e 100644 --- a/src/lib/utils/route-utils.ts +++ b/src/lib/utils/route-utils.ts @@ -106,6 +106,7 @@ export const isFeedRoute = (pathname: string): boolean => { if (normalizedPath.includes('/thread/')) return false; if (normalizedPath.startsWith('/pending/')) return false; + if (normalizedPath.includes('/queue')) return false; const pathWithoutSettings = normalizedPath.replace(/\/settings$/, ''); @@ -137,6 +138,11 @@ export const isPendingPostRoute = (pathname: string): boolean => { return normalizedPath.startsWith('/pending/'); }; +export const isModQueueRoute = (pathname: string): boolean => { + const normalizedPath = pathname.replace(/\/settings$/, ''); + return normalizedPath.includes('/queue'); +}; + export const getFeedCacheKey = (pathname: string): string | null => { let normalizedPath = pathname.endsWith('/') ? pathname.slice(0, -1) : pathname; normalizedPath = normalizedPath.replace(/\/settings$/, ''); @@ -150,6 +156,10 @@ export const getFeedCacheKey = (pathname: string): string | null => { return null; } + if (normalizedPath.includes('/queue')) { + return null; + } + if (isFeedRoute(pathname)) { return normalizedPath; } diff --git a/src/lib/utils/view-utils.ts b/src/lib/utils/view-utils.ts index 45e97ac9..6bf60d77 100644 --- a/src/lib/utils/view-utils.ts +++ b/src/lib/utils/view-utils.ts @@ -59,6 +59,10 @@ export const isModView = (pathname: string): boolean => { return pathname === `/mod` || pathname.startsWith(`/mod/`); }; +export const isModQueueView = (pathname: string): boolean => { + return pathname.includes('/queue'); +}; + export const isPendingPostView = (pathname: string, params: ParamsType): boolean => { return pathname === `/pending/${params.accountCommentIndex}` || pathname === `/pending/${params.accountCommentIndex}/settings`; }; @@ -101,6 +105,7 @@ export const isNotFoundView = (pathname: string, params: ParamsType): boolean => !isPendingPostView(pathname, params) && !isPostPageView(pathname, params) && !isSettingsView(pathname, params) && - !isSubscriptionsView(pathname, params) + !isSubscriptionsView(pathname, params) && + !isModQueueView(pathname) ); }; diff --git a/src/stores/use-mod-queue-store.ts b/src/stores/use-mod-queue-store.ts new file mode 100644 index 00000000..f5b24aac --- /dev/null +++ b/src/stores/use-mod-queue-store.ts @@ -0,0 +1,25 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +interface ModQueueState { + alertThresholdHours: number; + selectedBoardFilter: string | null; + setAlertThresholdHours: (hours: number) => void; + setSelectedBoardFilter: (boardAddress: string | null) => void; +} + +const useModQueueStore = create()( + persist( + (set) => ({ + alertThresholdHours: 6, + selectedBoardFilter: null, + setAlertThresholdHours: (hours) => set({ alertThresholdHours: hours }), + setSelectedBoardFilter: (boardAddress) => set({ selectedBoardFilter: boardAddress }), + }), + { + name: 'mod-queue-storage', + }, + ), +); + +export default useModQueueStore; diff --git a/src/views/mod-queue/index.ts b/src/views/mod-queue/index.ts new file mode 100644 index 00000000..ff4dc818 --- /dev/null +++ b/src/views/mod-queue/index.ts @@ -0,0 +1,3 @@ +import ModQueueView from './mod-queue'; + +export default ModQueueView; diff --git a/src/views/mod-queue/mod-queue.module.css b/src/views/mod-queue/mod-queue.module.css new file mode 100644 index 00000000..4c156806 --- /dev/null +++ b/src/views/mod-queue/mod-queue.module.css @@ -0,0 +1,252 @@ +.container { + padding: 10px; + max-width: 1000px; + margin: 0 auto; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; + flex-wrap: wrap; + gap: 10px; +} + +.title { + font-size: 1.5em; + font-weight: bold; +} + +.alertThresholdSetting { + display: flex; + align-items: center; + gap: 8px; +} + +.alertThresholdSetting label { + display: flex; + align-items: center; + gap: 8px; + font-size: 13px; +} + +.alertThresholdInput { + width: 50px; + padding: 4px; + border: 1px solid var(--border-color); + background: var(--input-background-color); + color: var(--text-color); +} + +.tableHeader { + display: flex; + padding: 8px; + border-bottom: 2px solid var(--border-color); + background: var(--header-background-color); + font-weight: bold; + font-size: 13px; +} + +.boardHeader { + width: 50px; + flex-shrink: 0; +} + +.excerptHeader { + flex-grow: 1; +} + +.timeHeader { + width: 80px; + text-align: right; + flex-shrink: 0; +} + +.actionsHeader { + width: 150px; + flex-shrink: 0; + text-align: right; +} + +.empty { + padding: 20px; + text-align: center; + color: var(--secondary-text-color); +} + +/* ModQueueRow styles */ +.row { + display: flex; + padding: 4px 8px; + border-bottom: 1px solid var(--border-color); + align-items: center; + gap: 8px; + background-color: var(--post-background-color); + color: var(--text-color); + font-size: 13px; +} + +.row:hover { + background-color: var(--post-highlight-color); +} + +.board { + width: 50px; + flex-shrink: 0; + font-weight: bold; +} + +.excerpt { + flex-grow: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} + +.excerpt a { + text-decoration: none; + color: var(--link-color); +} + +.excerpt a:hover { + text-decoration: underline; +} + +.time { + width: 80px; + flex-shrink: 0; + text-align: right; + color: var(--secondary-text-color); +} + +.time.alert { + color: red; + font-weight: bold; + animation: blink 2s infinite; +} + +@keyframes blink { + 0% { opacity: 1; } + 50% { opacity: 0.5; } + 100% { opacity: 1; } +} + +.actions { + width: 150px; + flex-shrink: 0; + display: flex; + gap: 4px; + justify-content: flex-end; +} + +.button { + cursor: pointer; + padding: 2px 6px; + border: 1px solid var(--border-color); + background: var(--button-background); + color: var(--button-text); + font-size: 11px; + text-decoration: none; + display: inline-block; +} + +.button:hover { + background: var(--button-hover-background); +} + +.approve { + color: green; +} + +.reject { + color: red; +} + +/* ModQueueBoardFilter styles */ +.filterContainer { + margin-bottom: 10px; + display: flex; + align-items: center; + gap: 8px; +} + +.filterSelect { + padding: 4px; + border: 1px solid var(--border-color); + background: var(--input-background-color); + color: var(--text-color); +} + +/* ModQueueButton styles */ +.modQueueButton { + cursor: pointer; + display: inline-flex; + align-items: center; + margin-left: 8px; + text-decoration: none; + color: var(--text-color); + font-size: 13px; +} + +.modQueueButton:hover { + text-decoration: underline; +} + +.modQueueButtonCount { + margin-left: 4px; + font-weight: bold; +} + +.modQueueButtonAlert { + color: red; + animation: blink 2s infinite; +} + +/* Mobile Layout */ +@media (max-width: 600px) { + .tableHeader { + display: none; + } + + .row { + flex-wrap: wrap; + padding: 8px; + } + + .board { + width: auto; + margin-right: 8px; + } + + .time { + margin-left: auto; + width: auto; + } + + .excerpt { + width: 100%; + margin: 4px 0; + white-space: normal; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + } + + .actions { + width: 100%; + justify-content: flex-start; + } + + .button { + flex: 1; + text-align: center; + padding: 8px; + } + + .header { + flex-direction: column; + align-items: flex-start; + } +} diff --git a/src/views/mod-queue/mod-queue.tsx b/src/views/mod-queue/mod-queue.tsx new file mode 100644 index 00000000..5ec7e4be --- /dev/null +++ b/src/views/mod-queue/mod-queue.tsx @@ -0,0 +1,303 @@ +import React, { useMemo, useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useParams, Link } from 'react-router-dom'; +import { useAccountSubplebbits, useFeed, Comment, usePublishCommentModeration } from '@plebbit/plebbit-react-hooks'; +import { Virtuoso } from 'react-virtuoso'; +import { formatDistanceToNow } from 'date-fns'; +import styles from './mod-queue.module.css'; +import useModQueueStore from '../../stores/use-mod-queue-store'; +import { useAccountSubplebbitsWithMetadata } from '../../hooks/use-account-subplebbits-with-metadata'; +import LoadingEllipsis from '../../components/loading-ellipsis'; +import { useFeedStateString } from '../../hooks/use-state-string'; +import { getSubplebbitAddress, getBoardPath } from '../../lib/utils/route-utils'; +import { useDefaultSubplebbits, MultisubSubplebbit } from '../../hooks/use-default-subplebbits'; +import { useBoardPath } from '../../hooks/use-resolved-subplebbit-address'; +import { getHasThumbnail, getCommentMediaInfo } from '../../lib/utils/media-utils'; +import useFeedResetStore from '../../stores/use-feed-reset-store'; + +interface ModQueueViewProps { + boardIdentifier?: string; // If provided, shows queue for single board +} + +interface ModQueueRowProps { + comment: Comment; + showBoardColumn?: boolean; +} + +const ModQueueRow = ({ comment, showBoardColumn = false }: ModQueueRowProps) => { + const { t } = useTranslation(); + const { alertThresholdHours } = useModQueueStore(); + const [isModerating, setIsModerating] = useState(false); + + const { content, title, timestamp, subplebbitAddress, cid, threadCid, link, thumbnailUrl, linkWidth, linkHeight } = comment; + + const boardPath = useBoardPath(subplebbitAddress); + const timeWaiting = Date.now() / 1000 - timestamp; + const isOverThreshold = timeWaiting > alertThresholdHours * 3600; + + const { publishCommentModeration: approve } = usePublishCommentModeration({ + commentCid: cid, + subplebbitAddress, + commentModeration: { approved: true }, + }); + + const { publishCommentModeration: reject } = usePublishCommentModeration({ + commentCid: cid, + subplebbitAddress, + commentModeration: { removed: true }, + }); + + const handleApprove = async () => { + try { + setIsModerating(true); + await approve(); + } catch (e) { + console.error(e); + setIsModerating(false); + } + }; + + const handleReject = async () => { + try { + setIsModerating(true); + await reject(); + } catch (e) { + console.error(e); + setIsModerating(false); + } + }; + + if (isModerating) { + return null; // Or show a loading state / fade out + } + + const excerpt = title || content || (getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), link) ? t('image') : t('no_content')); + const postUrl = `/${boardPath}/thread/${threadCid || cid}`; + + return ( +
+ {showBoardColumn && ( +
+ /{boardPath}/ +
+ )} +
+ + {excerpt} + +
+
{formatDistanceToNow(timestamp * 1000, { addSuffix: false })}
+
+ + + + {t('view')} + +
+
+ ); +}; + +interface ModQueueBoardFilterProps { + subplebbits: MultisubSubplebbit[]; +} + +const ModQueueBoardFilter = ({ subplebbits }: ModQueueBoardFilterProps) => { + const { t } = useTranslation(); + const { selectedBoardFilter, setSelectedBoardFilter } = useModQueueStore(); + + const handleChange = (e: React.ChangeEvent) => { + const value = e.target.value; + setSelectedBoardFilter(value === '' ? null : value); + }; + + if (!subplebbits || subplebbits.length === 0) { + return null; + } + + return ( +
+ + +
+ ); +}; + +interface ModQueueButtonProps { + boardIdentifier?: string; + isMobile?: boolean; +} + +export const ModQueueButton = ({ boardIdentifier, isMobile }: ModQueueButtonProps) => { + const { t } = useTranslation(); + const { alertThresholdHours } = useModQueueStore(); + const { accountSubplebbits } = useAccountSubplebbits(); + const accountSubplebbitAddresses = useMemo(() => Object.keys(accountSubplebbits || {}), [accountSubplebbits]); + + const subplebbitAddresses = useMemo(() => { + if (boardIdentifier) { + return [boardIdentifier]; + } + return accountSubplebbitAddresses; + }, [boardIdentifier, accountSubplebbitAddresses]); + + // If specific board, check if user is mod + const isModOfBoard = boardIdentifier ? accountSubplebbitAddresses.includes(boardIdentifier) : true; + + // Only fetch if we have addresses to check and permissions + const shouldFetch = subplebbitAddresses.length > 0 && isModOfBoard; + + const { feed } = useFeed({ + subplebbitAddresses: shouldFetch ? subplebbitAddresses : [], + modQueue: ['pendingApproval'], + postsPerPage: 100, // Fetch enough to check timestamps + }); + + if (!shouldFetch || subplebbitAddresses.length === 0) { + return null; + } + + const count = feed.length; + + const hasAlert = feed.some((item) => { + const timeWaiting = Date.now() / 1000 - item.timestamp; + return timeWaiting > alertThresholdHours * 3600; + }); + + const to = boardIdentifier ? `/${boardIdentifier}/queue` : '/mod/queue'; + + if (count === 0) { + // If we are on a specific board and the user is a mod, we still show the button + // If we are in global mod view, we show it too + return ( + + [{t('mod_queue')}] + + ); + } + + return ( + + [{t('mod_queue')} {count}] + + ); +}; + +export const ModQueueView = ({ boardIdentifier: propBoardIdentifier }: ModQueueViewProps) => { + const { t } = useTranslation(); + const params = useParams(); + const { selectedBoardFilter, alertThresholdHours, setAlertThresholdHours } = useModQueueStore(); + const { accountSubplebbits } = useAccountSubplebbits(); + const accountSubplebbitAddresses = Object.keys(accountSubplebbits); + const defaultSubplebbits = useDefaultSubplebbits(); + + const boardIdentifier = propBoardIdentifier || params.boardIdentifier; + + // Resolve boardIdentifier to address if it exists + const resolvedAddress = useMemo(() => { + if (boardIdentifier) { + return getSubplebbitAddress(boardIdentifier, defaultSubplebbits); + } + return undefined; + }, [boardIdentifier, defaultSubplebbits]); + + // Get metadata for filter dropdown + const subplebbitsWithMetadata = useAccountSubplebbitsWithMetadata(); + + const subplebbitAddresses = useMemo(() => { + if (resolvedAddress) { + return [resolvedAddress]; + } + + if (selectedBoardFilter) { + return [selectedBoardFilter]; + } + + return accountSubplebbitAddresses; + }, [resolvedAddress, selectedBoardFilter, accountSubplebbitAddresses]); + + const { feed, hasMore, loadMore, reset } = useFeed({ + subplebbitAddresses, + modQueue: ['pendingApproval'], + postsPerPage: 50, + }); + + // Register reset function with feed reset store so refresh button works + const setResetFunction = useFeedResetStore((state) => state.setResetFunction); + useEffect(() => { + setResetFunction(reset); + }, [reset, setResetFunction]); + + const loadingStateString = useFeedStateString(subplebbitAddresses) || t('loading'); + const showBoardColumn = !resolvedAddress && !selectedBoardFilter; + + const Footer = () => { + return hasMore ? ( +
+ +
+ ) : null; + }; + + return ( +
+
+
{t('moderation_queue')}
+
+ +
+
+ + {!resolvedAddress && } + + {feed.length === 0 && !hasMore ? ( +
{t('queue_is_empty')}
+ ) : ( + <> +
+ {showBoardColumn &&
{t('board')}
} +
{t('excerpt')}
+
{t('waiting')}
+
{t('actions')}
+
+ + } + components={{ Footer }} + /> + + )} +
+ ); +}; + +export default ModQueueView;