feat(catalog): add catalog filters

This commit is contained in:
Tom (plebeius.eth)
2025-03-06 00:07:08 +01:00
parent 64c857fb9d
commit b127876ce4
41 changed files with 411 additions and 173 deletions
+2 -1
View File
@@ -291,5 +291,6 @@
"this_thread_was_removed": "تم إزالة هذا الموضوع بواسطة مشرفي المنتدى", "this_thread_was_removed": "تم إزالة هذا الموضوع بواسطة مشرفي المنتدى",
"this_reply_was_deleted": "تم حذف هذه الرد من قبل مؤلفه", "this_reply_was_deleted": "تم حذف هذه الرد من قبل مؤلفه",
"this_reply_was_removed": "تمت إزالة هذه الرد من قبل مشرفي المنتدى", "this_reply_was_removed": "تمت إزالة هذه الرد من قبل مشرفي المنتدى",
"not_mod_of_any_board": "أنت لست مشرفًا في أي لوحة." "not_mod_of_any_board": "أنت لست مشرفًا في أي لوحة.",
"filters_and_highlights": "الفلاتر والإبرازات"
} }
+2 -1
View File
@@ -291,5 +291,6 @@
"this_thread_was_removed": "এই থ্রেডটি বোর্ড moderators দ্বারা মুছে ফেলা হয়েছে", "this_thread_was_removed": "এই থ্রেডটি বোর্ড moderators দ্বারা মুছে ফেলা হয়েছে",
"this_reply_was_deleted": "এই প্রতিক্রিয়া তার লেখক দ্বারা মুছে ফেলা হয়েছে", "this_reply_was_deleted": "এই প্রতিক্রিয়া তার লেখক দ্বারা মুছে ফেলা হয়েছে",
"this_reply_was_removed": "এই প্রতিক্রিয়া বোর্ড পরিচালকদের দ্বারা সরানো হয়েছে", "this_reply_was_removed": "এই প্রতিক্রিয়া বোর্ড পরিচালকদের দ্বারা সরানো হয়েছে",
"not_mod_of_any_board": "আপনি কোনও বোর্ডের মড নন।" "not_mod_of_any_board": "আপনি কোনও বোর্ডের মড নন।",
"filters_and_highlights": "ফিল্টার এবং হাইলাইট"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "Čeká na schválení", "pending": "Čeká na schválení",
"failed": "Nepodařilo se", "failed": "Nepodařilo se",
"add": "Přidat", "add": "Přidat",
"pattern": "Vzor", "pattern": "vzor",
"enable": "Povolit", "enable": "Povolit",
"order": "Pořadí", "order": "Pořadí",
"text": "Text", "text": "Text",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Toto vlákno bylo odstraněno moderátory fóra", "this_thread_was_removed": "Toto vlákno bylo odstraněno moderátory fóra",
"this_reply_was_deleted": "Tato odpověď byla smazána autorem", "this_reply_was_deleted": "Tato odpověď byla smazána autorem",
"this_reply_was_removed": "Tato odpověď byla odstraněna moderátory fóra", "this_reply_was_removed": "Tato odpověď byla odstraněna moderátory fóra",
"not_mod_of_any_board": "Nejste moderátor žádné desky." "not_mod_of_any_board": "Nejste moderátor žádné desky.",
"filters_and_highlights": "Filtry a zvýraznění"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "Afventer", "pending": "Afventer",
"failed": "Mislykkedes", "failed": "Mislykkedes",
"add": "Tilføj", "add": "Tilføj",
"pattern": "Mønster", "pattern": "mønster",
"enable": "Aktiver", "enable": "Aktiver",
"order": "Rækkefølge", "order": "Rækkefølge",
"text": "Tekst", "text": "Tekst",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Denne tråd blev fjernet af boardmoderatorer", "this_thread_was_removed": "Denne tråd blev fjernet af boardmoderatorer",
"this_reply_was_deleted": "Dette svar blev slettet af forfatteren", "this_reply_was_deleted": "Dette svar blev slettet af forfatteren",
"this_reply_was_removed": "Dette svar blev fjernet af forummoderatorer", "this_reply_was_removed": "Dette svar blev fjernet af forummoderatorer",
"not_mod_of_any_board": "Du er ikke moderator for nogen tavler." "not_mod_of_any_board": "Du er ikke moderator for nogen tavler.",
"filters_and_highlights": "Filtre og fremhævninger"
} }
+2 -1
View File
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Dieser Thread wurde von den Moderatoren des Boards entfernt", "this_thread_was_removed": "Dieser Thread wurde von den Moderatoren des Boards entfernt",
"this_reply_was_deleted": "Diese Antwort wurde vom Autor gelöscht", "this_reply_was_deleted": "Diese Antwort wurde vom Autor gelöscht",
"this_reply_was_removed": "Diese Antwort wurde von den Forenmoderatoren entfernt", "this_reply_was_removed": "Diese Antwort wurde von den Forenmoderatoren entfernt",
"not_mod_of_any_board": "Du bist kein Moderator eines Boards." "not_mod_of_any_board": "Du bist kein Moderator eines Boards.",
"filters_and_highlights": "Filter & Highlights"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "Σε εκκρεμότητα", "pending": "Σε εκκρεμότητα",
"failed": "Απέτυχε", "failed": "Απέτυχε",
"add": "Προσθήκη", "add": "Προσθήκη",
"pattern": "Μοτίβο", "pattern": "μοτίβο",
"enable": "Ενεργοποιήστε", "enable": "Ενεργοποιήστε",
"order": "Σειρά", "order": "Σειρά",
"text": "Κείμενο", "text": "Κείμενο",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Αυτή η συζήτηση αφαιρέθηκε από τους διαχειριστές του φόρουμ", "this_thread_was_removed": "Αυτή η συζήτηση αφαιρέθηκε από τους διαχειριστές του φόρουμ",
"this_reply_was_deleted": "Αυτή η απάντηση διαγράφηκε από τον συγγραφέα της", "this_reply_was_deleted": "Αυτή η απάντηση διαγράφηκε από τον συγγραφέα της",
"this_reply_was_removed": "Η απάντηση αυτή αφαιρέθηκε από τους διαχειριστές του φόρουμ", "this_reply_was_removed": "Η απάντηση αυτή αφαιρέθηκε από τους διαχειριστές του φόρουμ",
"not_mod_of_any_board": "Δεν είστε διαχειριστής σε καμία κοινότητα." "not_mod_of_any_board": "Δεν είστε διαχειριστής σε καμία κοινότητα.",
"filters_and_highlights": "Φίλτρα και επισημάνσεις"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "Pending", "pending": "Pending",
"failed": "Failed", "failed": "Failed",
"add": "Add", "add": "Add",
"pattern": "Pattern", "pattern": "pattern",
"enable": "Enable", "enable": "Enable",
"order": "Order", "order": "Order",
"text": "Text", "text": "Text",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "This thread was removed by board moderators", "this_thread_was_removed": "This thread was removed by board moderators",
"this_reply_was_deleted": "This reply was deleted by its author", "this_reply_was_deleted": "This reply was deleted by its author",
"this_reply_was_removed": "This reply was removed by board moderators", "this_reply_was_removed": "This reply was removed by board moderators",
"not_mod_of_any_board": "You are not a mod of any board." "not_mod_of_any_board": "You are not a mod of any board.",
"filters_and_highlights": "Filters & Highlights"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "Pendiente", "pending": "Pendiente",
"failed": "Fallido", "failed": "Fallido",
"add": "Añadir", "add": "Añadir",
"pattern": "Patrón", "pattern": "patrón",
"enable": "Habilitar", "enable": "Habilitar",
"order": "Orden", "order": "Orden",
"text": "Texto", "text": "Texto",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Este hilo fue eliminado por los moderadores del foro", "this_thread_was_removed": "Este hilo fue eliminado por los moderadores del foro",
"this_reply_was_deleted": "Esta respuesta fue eliminada por su autor", "this_reply_was_deleted": "Esta respuesta fue eliminada por su autor",
"this_reply_was_removed": "Esta respuesta fue eliminada por los moderadores del foro", "this_reply_was_removed": "Esta respuesta fue eliminada por los moderadores del foro",
"not_mod_of_any_board": "No eres moderador de ningún tablero." "not_mod_of_any_board": "No eres moderador de ningún tablero.",
"filters_and_highlights": "Filtros y resaltar"
} }
+2 -1
View File
@@ -291,5 +291,6 @@
"this_thread_was_removed": "این تاپیک توسط مدیران انجمن حذف شد", "this_thread_was_removed": "این تاپیک توسط مدیران انجمن حذف شد",
"this_reply_was_deleted": "این پاسخ توسط نویسنده‌اش حذف شد", "this_reply_was_deleted": "این پاسخ توسط نویسنده‌اش حذف شد",
"this_reply_was_removed": "این پاسخ توسط مدیران انجمن حذف شد", "this_reply_was_removed": "این پاسخ توسط مدیران انجمن حذف شد",
"not_mod_of_any_board": "شما هیچ تابلو مدیر نیستید." "not_mod_of_any_board": "شما هیچ تابلو مدیر نیستید.",
"filters_and_highlights": "فیلترها و هایلایت‌ها"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "Odottaa", "pending": "Odottaa",
"failed": "Epäonnistui", "failed": "Epäonnistui",
"add": "Lisää", "add": "Lisää",
"pattern": "Malli", "pattern": "malli",
"enable": "Ota käyttöön", "enable": "Ota käyttöön",
"order": "Järjestys", "order": "Järjestys",
"text": "Teksti", "text": "Teksti",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Tämä ketju poistettiin foorumin moderaattoreiden toimesta", "this_thread_was_removed": "Tämä ketju poistettiin foorumin moderaattoreiden toimesta",
"this_reply_was_deleted": "Tämä vastaus poistettiin sen kirjoittajalta", "this_reply_was_deleted": "Tämä vastaus poistettiin sen kirjoittajalta",
"this_reply_was_removed": "Tämä vastaus poistettiin foorumin moderaattoreiden toimesta", "this_reply_was_removed": "Tämä vastaus poistettiin foorumin moderaattoreiden toimesta",
"not_mod_of_any_board": "Et ole moderaattori missään taulussa." "not_mod_of_any_board": "Et ole moderaattori missään taulussa.",
"filters_and_highlights": "Suodattimet ja korostukset"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "Naghihintay", "pending": "Naghihintay",
"failed": "Nabigo", "failed": "Nabigo",
"add": "Idagdag", "add": "Idagdag",
"pattern": "Pattern", "pattern": "pattern",
"enable": "I-enable", "enable": "I-enable",
"order": "Kaayusan", "order": "Kaayusan",
"text": "Teksto", "text": "Teksto",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Inalis ang thread na ito ng mga moderator ng board", "this_thread_was_removed": "Inalis ang thread na ito ng mga moderator ng board",
"this_reply_was_deleted": "Ang tugon na ito ay tinanggal ng may-akda nito", "this_reply_was_deleted": "Ang tugon na ito ay tinanggal ng may-akda nito",
"this_reply_was_removed": "Ang sagot na ito ay tinanggal ng mga moderator ng board", "this_reply_was_removed": "Ang sagot na ito ay tinanggal ng mga moderator ng board",
"not_mod_of_any_board": "Hindi ka mod ng anumang board." "not_mod_of_any_board": "Hindi ka mod ng anumang board.",
"filters_and_highlights": "Mga filter at highlight"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "En attente", "pending": "En attente",
"failed": "Échoué", "failed": "Échoué",
"add": "Ajouter", "add": "Ajouter",
"pattern": "Modèle", "pattern": "motif",
"enable": "Activer", "enable": "Activer",
"order": "Ordre", "order": "Ordre",
"text": "Texte", "text": "Texte",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Ce sujet a été supprimé par les modérateurs du forum", "this_thread_was_removed": "Ce sujet a été supprimé par les modérateurs du forum",
"this_reply_was_deleted": "Cette réponse a été supprimée par son auteur", "this_reply_was_deleted": "Cette réponse a été supprimée par son auteur",
"this_reply_was_removed": "Cette réponse a été supprimée par les modérateurs du forum", "this_reply_was_removed": "Cette réponse a été supprimée par les modérateurs du forum",
"not_mod_of_any_board": "Vous n'êtes pas modérateur de n'aucun forum." "not_mod_of_any_board": "Vous n'êtes pas modérateur de n'aucun forum.",
"filters_and_highlights": "Filtres et mises en évidence"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "ממתין", "pending": "ממתין",
"failed": "נכשל", "failed": "נכשל",
"add": "הוסף", "add": "הוסף",
"pattern": "תבנית", "pattern": "דפוס",
"enable": "הפעל", "enable": "הפעל",
"order": "סדר", "order": "סדר",
"text": "טקסט", "text": "טקסט",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "השרשור הזה הוסר על ידי מנהלי הפורום", "this_thread_was_removed": "השרשור הזה הוסר על ידי מנהלי הפורום",
"this_reply_was_deleted": "תשובה זו נמחקה על ידי המחבר שלה", "this_reply_was_deleted": "תשובה זו נמחקה על ידי המחבר שלה",
"this_reply_was_removed": "תשובה זו הוסרה על ידי מנהלי הפורום", "this_reply_was_removed": "תשובה זו הוסרה על ידי מנהלי הפורום",
"not_mod_of_any_board": "אתה לא מוד בכל לוח." "not_mod_of_any_board": "אתה לא מוד בכל לוח.",
"filters_and_highlights": "סינונים והדגשות"
} }
+2 -1
View File
@@ -291,5 +291,6 @@
"this_thread_was_removed": "यह थ्रेड बोर्ड मॉडरेटर्स द्वारा हटा दिया गया था", "this_thread_was_removed": "यह थ्रेड बोर्ड मॉडरेटर्स द्वारा हटा दिया गया था",
"this_reply_was_deleted": "इस उत्तर को इसके लेखक द्वारा हटा दिया गया था", "this_reply_was_deleted": "इस उत्तर को इसके लेखक द्वारा हटा दिया गया था",
"this_reply_was_removed": "यह उत्तर बोर्ड मॉडरेटर द्वारा हटा दिया गया था", "this_reply_was_removed": "यह उत्तर बोर्ड मॉडरेटर द्वारा हटा दिया गया था",
"not_mod_of_any_board": "आप किसी बोर्ड के मॉड नहीं हैं।" "not_mod_of_any_board": "आप किसी बोर्ड के मॉड नहीं हैं।",
"filters_and_highlights": "फ़िल्टर और हाइलाइट्स"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "Függőben", "pending": "Függőben",
"failed": "Sikertelen", "failed": "Sikertelen",
"add": "Hozzáad", "add": "Hozzáad",
"pattern": "Minta", "pattern": "minta",
"enable": "Engedélyez", "enable": "Engedélyez",
"order": "Rendelés", "order": "Rendelés",
"text": "Szöveg", "text": "Szöveg",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Ezt a témát a fórum moderátorai eltávolították", "this_thread_was_removed": "Ezt a témát a fórum moderátorai eltávolították",
"this_reply_was_deleted": "Ez a válasz törölve lett a szerzője által", "this_reply_was_deleted": "Ez a válasz törölve lett a szerzője által",
"this_reply_was_removed": "Ez a válasz törlésre került a fórum moderátorai által", "this_reply_was_removed": "Ez a válasz törlésre került a fórum moderátorai által",
"not_mod_of_any_board": "Nem vagy moderátor egyetlen táblán sem." "not_mod_of_any_board": "Nem vagy moderátor egyetlen táblán sem.",
"filters_and_highlights": "Szűrők és kiemelések"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "Tertunda", "pending": "Tertunda",
"failed": "Gagal", "failed": "Gagal",
"add": "Tambahkan", "add": "Tambahkan",
"pattern": "Pola", "pattern": "pola",
"enable": "Aktifkan", "enable": "Aktifkan",
"order": "Urutan", "order": "Urutan",
"text": "Teks", "text": "Teks",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Thread ini dihapus oleh moderator papan", "this_thread_was_removed": "Thread ini dihapus oleh moderator papan",
"this_reply_was_deleted": "Balasan ini dihapus oleh penulisnya", "this_reply_was_deleted": "Balasan ini dihapus oleh penulisnya",
"this_reply_was_removed": "Balasan ini dihapus oleh moderator papan", "this_reply_was_removed": "Balasan ini dihapus oleh moderator papan",
"not_mod_of_any_board": "Anda bukan mod di papan manapun." "not_mod_of_any_board": "Anda bukan mod di papan manapun.",
"filters_and_highlights": "Filter dan Sorotan"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "In attesa", "pending": "In attesa",
"failed": "Fallito", "failed": "Fallito",
"add": "Aggiungi", "add": "Aggiungi",
"pattern": "Modello", "pattern": "modello",
"enable": "Abilita", "enable": "Abilita",
"order": "Ordine", "order": "Ordine",
"text": "Testo", "text": "Testo",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Questo thread è stato rimosso dai moderatori del board", "this_thread_was_removed": "Questo thread è stato rimosso dai moderatori del board",
"this_reply_was_deleted": "Questa risposta è stata eliminata dal suo autore", "this_reply_was_deleted": "Questa risposta è stata eliminata dal suo autore",
"this_reply_was_removed": "Questa risposta è stata rimossa dai moderatori del forum", "this_reply_was_removed": "Questa risposta è stata rimossa dai moderatori del forum",
"not_mod_of_any_board": "Non sei un mod di nessuna board." "not_mod_of_any_board": "Non sei un mod di nessuna board.",
"filters_and_highlights": "Filtri e Evidenziazioni"
} }
+2 -1
View File
@@ -291,5 +291,6 @@
"this_thread_was_removed": "このスレッドはボードのモデレーターによって削除されました", "this_thread_was_removed": "このスレッドはボードのモデレーターによって削除されました",
"this_reply_was_deleted": "この返信はその著者によって削除されました", "this_reply_was_deleted": "この返信はその著者によって削除されました",
"this_reply_was_removed": "この返信は掲示板のモデレーターによって削除されました", "this_reply_was_removed": "この返信は掲示板のモデレーターによって削除されました",
"not_mod_of_any_board": "あなたはどのボードのモデレーターでもありません。" "not_mod_of_any_board": "あなたはどのボードのモデレーターでもありません。",
"filters_and_highlights": "フィルターとハイライト"
} }
+2 -1
View File
@@ -291,5 +291,6 @@
"this_thread_was_removed": "이 스레드는 보드 관리자에 의해 제거되었습니다", "this_thread_was_removed": "이 스레드는 보드 관리자에 의해 제거되었습니다",
"this_reply_was_deleted": "이 답글은 작성자에 의해 삭제되었습니다", "this_reply_was_deleted": "이 답글은 작성자에 의해 삭제되었습니다",
"this_reply_was_removed": "이 답글은 게시판 관리자에 의해 삭제되었습니다", "this_reply_was_removed": "이 답글은 게시판 관리자에 의해 삭제되었습니다",
"not_mod_of_any_board": "당신은 어떤 게시판의 관리자도 아닙니다." "not_mod_of_any_board": "당신은 어떤 게시판의 관리자도 아닙니다.",
"filters_and_highlights": "필터 및 하이라이트"
} }
+2 -1
View File
@@ -291,5 +291,6 @@
"this_thread_was_removed": "हे थ्रेड बोर्ड मध्यमाने काढले गेले आहे", "this_thread_was_removed": "हे थ्रेड बोर्ड मध्यमाने काढले गेले आहे",
"this_reply_was_deleted": "हा प्रतिसाद त्याच्या लेखकाने हटवला आहे", "this_reply_was_deleted": "हा प्रतिसाद त्याच्या लेखकाने हटवला आहे",
"this_reply_was_removed": "या उत्तराला बोर्ड मॉडरेटरने काढले आहे", "this_reply_was_removed": "या उत्तराला बोर्ड मॉडरेटरने काढले आहे",
"not_mod_of_any_board": "तुम्ही कोणत्याही बोर्डचे मॉड नाहीत." "not_mod_of_any_board": "तुम्ही कोणत्याही बोर्डचे मॉड नाहीत.",
"filters_and_highlights": "फिल्टर आणि हायलाइट्स"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "In afwachting", "pending": "In afwachting",
"failed": "Mislukt", "failed": "Mislukt",
"add": "Toevoegen", "add": "Toevoegen",
"pattern": "Patroon", "pattern": "patroon",
"enable": "Inschakelen", "enable": "Inschakelen",
"order": "Volgorde", "order": "Volgorde",
"text": "Tekst", "text": "Tekst",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Deze thread werd verwijderd door boardmoderators", "this_thread_was_removed": "Deze thread werd verwijderd door boardmoderators",
"this_reply_was_deleted": "Dit antwoord werd verwijderd door de auteur", "this_reply_was_deleted": "Dit antwoord werd verwijderd door de auteur",
"this_reply_was_removed": "Dit antwoord is verwijderd door de forum moderators", "this_reply_was_removed": "Dit antwoord is verwijderd door de forum moderators",
"not_mod_of_any_board": "Je bent geen mod van een bord." "not_mod_of_any_board": "Je bent geen mod van een bord.",
"filters_and_highlights": "Filters en markeringen"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "Venter", "pending": "Venter",
"failed": "Feilet", "failed": "Feilet",
"add": "Legg til", "add": "Legg til",
"pattern": "Mønster", "pattern": "mønster",
"enable": "Aktiver", "enable": "Aktiver",
"order": "Rekkefølge", "order": "Rekkefølge",
"text": "Tekst", "text": "Tekst",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Denne tråden ble fjernet av boardmoderatorer", "this_thread_was_removed": "Denne tråden ble fjernet av boardmoderatorer",
"this_reply_was_deleted": "Dette svaret ble slettet av forfatteren", "this_reply_was_deleted": "Dette svaret ble slettet av forfatteren",
"this_reply_was_removed": "Dette svaret ble fjernet av forummoderatorer", "this_reply_was_removed": "Dette svaret ble fjernet av forummoderatorer",
"not_mod_of_any_board": "Du er ikke moderator på noen brett." "not_mod_of_any_board": "Du er ikke moderator på noen brett.",
"filters_and_highlights": "Filtre og fremhevinger"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "Oczekujące", "pending": "Oczekujące",
"failed": "Nieudane", "failed": "Nieudane",
"add": "Dodaj", "add": "Dodaj",
"pattern": "Wzór", "pattern": "wzór",
"enable": "Włącz", "enable": "Włącz",
"order": "Kolejność", "order": "Kolejność",
"text": "Tekst", "text": "Tekst",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Wątek został usunięty przez moderatorów forum", "this_thread_was_removed": "Wątek został usunięty przez moderatorów forum",
"this_reply_was_deleted": "Ta odpowiedź została usunięta przez autora", "this_reply_was_deleted": "Ta odpowiedź została usunięta przez autora",
"this_reply_was_removed": "Ta odpowiedź została usunięta przez moderatorów forum", "this_reply_was_removed": "Ta odpowiedź została usunięta przez moderatorów forum",
"not_mod_of_any_board": "Nie jesteś moderatorem żadnej tablicy." "not_mod_of_any_board": "Nie jesteś moderatorem żadnej tablicy.",
"filters_and_highlights": "Filtry i wyróżnienia"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "Pendente", "pending": "Pendente",
"failed": "Falhou", "failed": "Falhou",
"add": "Adicionar", "add": "Adicionar",
"pattern": "Padrão", "pattern": "padrão",
"enable": "Ativar", "enable": "Ativar",
"order": "Ordem", "order": "Ordem",
"text": "Texto", "text": "Texto",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Este tópico foi removido pelos moderadores do fórum", "this_thread_was_removed": "Este tópico foi removido pelos moderadores do fórum",
"this_reply_was_deleted": "Esta resposta foi excluída pelo autor", "this_reply_was_deleted": "Esta resposta foi excluída pelo autor",
"this_reply_was_removed": "Esta resposta foi removida pelos moderadores do fórum", "this_reply_was_removed": "Esta resposta foi removida pelos moderadores do fórum",
"not_mod_of_any_board": "Você não é um moderador de nenhum quadro." "not_mod_of_any_board": "Você não é um moderador de nenhum quadro.",
"filters_and_highlights": "Filtros e Destaques"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "În așteptare", "pending": "În așteptare",
"failed": "Eșuat", "failed": "Eșuat",
"add": "Adaugă", "add": "Adaugă",
"pattern": "Model", "pattern": "model",
"enable": "Activare", "enable": "Activare",
"order": "Ordine", "order": "Ordine",
"text": "Text", "text": "Text",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Acest thread a fost eliminat de moderatorii board-ului", "this_thread_was_removed": "Acest thread a fost eliminat de moderatorii board-ului",
"this_reply_was_deleted": "Acest răspuns a fost șters de autorul său", "this_reply_was_deleted": "Acest răspuns a fost șters de autorul său",
"this_reply_was_removed": "Acest răspuns a fost eliminat de moderatorii forumului", "this_reply_was_removed": "Acest răspuns a fost eliminat de moderatorii forumului",
"not_mod_of_any_board": "Nu ești moderator pe niciun tablou." "not_mod_of_any_board": "Nu ești moderator pe niciun tablou.",
"filters_and_highlights": "Filtre și evidențiere"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "В ожидании", "pending": "В ожидании",
"failed": "Не удалось", "failed": "Не удалось",
"add": "Добавить", "add": "Добавить",
"pattern": "Шаблон", "pattern": "узор",
"enable": "Включить", "enable": "Включить",
"order": "Порядок", "order": "Порядок",
"text": "Текст", "text": "Текст",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Эта тема была удалена модераторами форума", "this_thread_was_removed": "Эта тема была удалена модераторами форума",
"this_reply_was_deleted": "Этот ответ был удален автором", "this_reply_was_deleted": "Этот ответ был удален автором",
"this_reply_was_removed": "Этот ответ был удален модераторами форума", "this_reply_was_removed": "Этот ответ был удален модераторами форума",
"not_mod_of_any_board": "Вы не модератор ни одной доски." "not_mod_of_any_board": "Вы не модератор ни одной доски.",
"filters_and_highlights": "Фильтры и выделения"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "Në pritje", "pending": "Në pritje",
"failed": "Dështuar", "failed": "Dështuar",
"add": "Shto", "add": "Shto",
"pattern": "Model", "pattern": "model",
"enable": "Aktivizo", "enable": "Aktivizo",
"order": "Rendit", "order": "Rendit",
"text": "Tekst", "text": "Tekst",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Ky thread u hoq nga moderatorët e bordit", "this_thread_was_removed": "Ky thread u hoq nga moderatorët e bordit",
"this_reply_was_deleted": "Ky përgjigje u fshi nga autori i saj", "this_reply_was_deleted": "Ky përgjigje u fshi nga autori i saj",
"this_reply_was_removed": "Ky përgjigje u hoq nga moderatorët e forumit", "this_reply_was_removed": "Ky përgjigje u hoq nga moderatorët e forumit",
"not_mod_of_any_board": "Nuk jeni mod në asnjë bord." "not_mod_of_any_board": "Nuk jeni mod në asnjë bord.",
"filters_and_highlights": "Filtra dhe theksime"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "Väntande", "pending": "Väntande",
"failed": "Misslyckades", "failed": "Misslyckades",
"add": "Lägg till", "add": "Lägg till",
"pattern": "Mönster", "pattern": "mönster",
"enable": "Aktivera", "enable": "Aktivera",
"order": "Ordning", "order": "Ordning",
"text": "Text", "text": "Text",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Denna tråd togs bort av forumets moderatorer", "this_thread_was_removed": "Denna tråd togs bort av forumets moderatorer",
"this_reply_was_deleted": "Detta svar togs bort av författaren", "this_reply_was_deleted": "Detta svar togs bort av författaren",
"this_reply_was_removed": "Detta svar togs bort av forumets moderatorer", "this_reply_was_removed": "Detta svar togs bort av forumets moderatorer",
"not_mod_of_any_board": "Du är inte moderator på någon tavla." "not_mod_of_any_board": "Du är inte moderator på någon tavla.",
"filters_and_highlights": "Filter och markeringar"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "అనుమతించబడాలి", "pending": "అనుమతించబడాలి",
"failed": "విజయవంతం కాలేదు", "failed": "విజయవంతం కాలేదు",
"add": "చేర్చండి", "add": "చేర్చండి",
"pattern": "ప్యాటర్న్", "pattern": "పాటర్న్",
"enable": "చేర్చు", "enable": "చేర్చు",
"order": "ఆర్డర్", "order": "ఆర్డర్",
"text": "పాఠం", "text": "పాఠం",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "ఈ థ్రెడ్ బోర్డ్ మోడరేటర్ల ద్వారా తీసివేయబడింది", "this_thread_was_removed": "ఈ థ్రెడ్ బోర్డ్ మోడరేటర్ల ద్వారా తీసివేయబడింది",
"this_reply_was_deleted": "ఈ స్పందనను దాని రచయిత తొలగించారు", "this_reply_was_deleted": "ఈ స్పందనను దాని రచయిత తొలగించారు",
"this_reply_was_removed": "ఈ స్పందన బోర్డ్ మోడరేటర్లచే తొలగించబడింది", "this_reply_was_removed": "ఈ స్పందన బోర్డ్ మోడరేటర్లచే తొలగించబడింది",
"not_mod_of_any_board": "మీరు ఎటువంటి బోర్డుకు మోడ్ కంటే లేరు." "not_mod_of_any_board": "మీరు ఎటువంటి బోర్డుకు మోడ్ కంటే లేరు.",
"filters_and_highlights": "ఫిల్టర్లు మరియు హైలైట్‌లు"
} }
+2 -1
View File
@@ -291,5 +291,6 @@
"this_thread_was_removed": "กระทู้นี้ถูกลบโดยผู้ดูแลบอร์ด", "this_thread_was_removed": "กระทู้นี้ถูกลบโดยผู้ดูแลบอร์ด",
"this_reply_was_deleted": "การตอบกลับนี้ถูกลบโดยผู้แต่ง", "this_reply_was_deleted": "การตอบกลับนี้ถูกลบโดยผู้แต่ง",
"this_reply_was_removed": "คำตอบนี้ถูกลบโดยผู้ดูแลบอร์ด", "this_reply_was_removed": "คำตอบนี้ถูกลบโดยผู้ดูแลบอร์ด",
"not_mod_of_any_board": "คุณไม่ใช่ผู้ดูแลบอร์ดใดๆ" "not_mod_of_any_board": "คุณไม่ใช่ผู้ดูแลบอร์ดใดๆ",
"filters_and_highlights": "ตัวกรองและไฮไลท์"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "Beklemede", "pending": "Beklemede",
"failed": "Başarısız", "failed": "Başarısız",
"add": "Ekle", "add": "Ekle",
"pattern": "Desen", "pattern": "desen",
"enable": "Etkinleştir", "enable": "Etkinleştir",
"order": "Sıra", "order": "Sıra",
"text": "Metin", "text": "Metin",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Bu konu, board moderatörleri tarafından kaldırıldı", "this_thread_was_removed": "Bu konu, board moderatörleri tarafından kaldırıldı",
"this_reply_was_deleted": "Bu yanıt, yazar tarafından silindi", "this_reply_was_deleted": "Bu yanıt, yazar tarafından silindi",
"this_reply_was_removed": "Bu yanıt, forum moderatörleri tarafından kaldırıldı", "this_reply_was_removed": "Bu yanıt, forum moderatörleri tarafından kaldırıldı",
"not_mod_of_any_board": "Hiçbir tahtanın moderatörü değilsiniz." "not_mod_of_any_board": "Hiçbir tahtanın moderatörü değilsiniz.",
"filters_and_highlights": "Filtreler ve Vurgular"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "Очікує", "pending": "Очікує",
"failed": "Не вдалося", "failed": "Не вдалося",
"add": "Додати", "add": "Додати",
"pattern": "Шаблон", "pattern": "шаблон",
"enable": "Увімкнути", "enable": "Увімкнути",
"order": "Порядок", "order": "Порядок",
"text": "Текст", "text": "Текст",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Цю тему було видалено модераторами форуму", "this_thread_was_removed": "Цю тему було видалено модераторами форуму",
"this_reply_was_deleted": "Цю відповідь було видалено її автором", "this_reply_was_deleted": "Цю відповідь було видалено її автором",
"this_reply_was_removed": "Цю відповідь було видалено модераторами форуму", "this_reply_was_removed": "Цю відповідь було видалено модераторами форуму",
"not_mod_of_any_board": "Ви не модератор жодної дошки." "not_mod_of_any_board": "Ви не модератор жодної дошки.",
"filters_and_highlights": "Фільтри та виділення"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "منتظر", "pending": "منتظر",
"failed": "ناکام", "failed": "ناکام",
"add": "شامل کریں", "add": "شامل کریں",
"pattern": "نمونہ", "pattern": "پیٹرن",
"enable": "فعال کریں", "enable": "فعال کریں",
"order": "ترتیب", "order": "ترتیب",
"text": "متن", "text": "متن",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "اس تھریڈ کو بورڈ کے موڈریٹرز نے ہٹا دیا ہے", "this_thread_was_removed": "اس تھریڈ کو بورڈ کے موڈریٹرز نے ہٹا دیا ہے",
"this_reply_was_deleted": "اس جواب کو اس کے مصنف نے حذف کر دیا تھا", "this_reply_was_deleted": "اس جواب کو اس کے مصنف نے حذف کر دیا تھا",
"this_reply_was_removed": "اس جواب کو بورڈ کے منتظمین نے حذف کر دیا ہے", "this_reply_was_removed": "اس جواب کو بورڈ کے منتظمین نے حذف کر دیا ہے",
"not_mod_of_any_board": "آپ کسی بورڈ کے موڈ نہیں ہیں۔" "not_mod_of_any_board": "آپ کسی بورڈ کے موڈ نہیں ہیں۔",
"filters_and_highlights": "فلٹرز اور ہائیلائٹس"
} }
+3 -2
View File
@@ -228,7 +228,7 @@
"pending": "Đang chờ", "pending": "Đang chờ",
"failed": "Thất bại", "failed": "Thất bại",
"add": "Thêm", "add": "Thêm",
"pattern": "Mẫu", "pattern": "mẫu",
"enable": "Kích hoạt", "enable": "Kích hoạt",
"order": "Thứ tự", "order": "Thứ tự",
"text": "Văn bản", "text": "Văn bản",
@@ -291,5 +291,6 @@
"this_thread_was_removed": "Chủ đề này đã bị các quản trị viên diễn đàn xóa", "this_thread_was_removed": "Chủ đề này đã bị các quản trị viên diễn đàn xóa",
"this_reply_was_deleted": "Câu trả lời này đã bị tác giả của nó xóa", "this_reply_was_deleted": "Câu trả lời này đã bị tác giả của nó xóa",
"this_reply_was_removed": "Phản hồi này đã bị xóa bởi các quản trị viên diễn đàn", "this_reply_was_removed": "Phản hồi này đã bị xóa bởi các quản trị viên diễn đàn",
"not_mod_of_any_board": "Bạn không phải là quản trị viên của bất kỳ bảng nào." "not_mod_of_any_board": "Bạn không phải là quản trị viên của bất kỳ bảng nào.",
"filters_and_highlights": "Bộ lọc và Nổi bật"
} }
+2 -1
View File
@@ -291,5 +291,6 @@
"this_thread_was_removed": "此帖子已被论坛版主删除", "this_thread_was_removed": "此帖子已被论坛版主删除",
"this_reply_was_deleted": "该回复已被其作者删除", "this_reply_was_deleted": "该回复已被其作者删除",
"this_reply_was_removed": "该回复已被版主删除", "this_reply_was_removed": "该回复已被版主删除",
"not_mod_of_any_board": "您不是任何板块的管理员。" "not_mod_of_any_board": "您不是任何板块的管理员。",
"filters_and_highlights": "筛选器和高亮"
} }
+2 -15
View File
@@ -13,7 +13,6 @@ import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
import _ from 'lodash'; import _ from 'lodash';
import useIsMobile from '../../hooks/use-is-mobile'; import useIsMobile from '../../hooks/use-is-mobile';
import useCatalogFiltersStore from '../../stores/use-catalog-filters-store'; import useCatalogFiltersStore from '../../stores/use-catalog-filters-store';
import { useEffect } from 'react';
interface BoardButtonsProps { interface BoardButtonsProps {
address?: string | undefined; address?: string | undefined;
@@ -244,13 +243,7 @@ export const MobileBoardButtons = () => {
const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any }); const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any });
const subplebbitAddress = params?.subplebbitAddress || accountComment?.subplebbitAddress; const subplebbitAddress = params?.subplebbitAddress || accountComment?.subplebbitAddress;
const { filteredCount, resetFilteredCount } = useCatalogFiltersStore(); const { filteredCount } = useCatalogFiltersStore();
useEffect(() => {
if (subplebbitAddress) {
resetFilteredCount();
}
}, [subplebbitAddress, resetFilteredCount]);
return ( return (
<div className={`${styles.mobileBoardButtons} ${!isInCatalogView ? styles.addMargin : ''}`}> <div className={`${styles.mobileBoardButtons} ${!isInCatalogView ? styles.addMargin : ''}`}>
@@ -334,13 +327,7 @@ export const DesktopBoardButtons = () => {
const isInPostView = isPostPageView(location.pathname, params); const isInPostView = isPostPageView(location.pathname, params);
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams()); const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
const { filteredCount, resetFilteredCount } = useCatalogFiltersStore(); const { filteredCount } = useCatalogFiltersStore();
useEffect(() => {
if (subplebbitAddress) {
resetFilteredCount();
}
}, [subplebbitAddress, resetFilteredCount]);
return ( return (
<> <>
+86 -48
View File
@@ -1,11 +1,14 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import { Comment } from '@plebbit/plebbit-react-hooks'; import { Comment } from '@plebbit/plebbit-react-hooks';
import { getCommentMediaInfo, getHasThumbnail } from '../lib/utils/media-utils';
interface FilterItem { interface FilterItem {
text: string; text: string;
enabled: boolean; enabled: boolean;
count: number;
filteredCids: Set<string>;
hide: boolean;
top: boolean;
} }
interface CatalogFiltersStore { interface CatalogFiltersStore {
@@ -20,7 +23,9 @@ interface CatalogFiltersStore {
updateFilter: () => void; updateFilter: () => void;
initializeFilter: () => void; initializeFilter: () => void;
filteredCount: number; filteredCount: number;
resetFilteredCount: () => void; filteredCids: Set<string>;
incrementFilterCount: (filterIndex: number, cid: string) => void;
recalcFilteredCount: () => void;
} }
const useCatalogFiltersStore = create( const useCatalogFiltersStore = create(
@@ -28,76 +33,109 @@ const useCatalogFiltersStore = create(
(set, get) => ({ (set, get) => ({
showTextOnlyThreads: false, showTextOnlyThreads: false,
setShowTextOnlyThreads: (value: boolean) => { setShowTextOnlyThreads: (value: boolean) => {
set({ showTextOnlyThreads: value, filteredCount: 0 }); set({ showTextOnlyThreads: value });
get().updateFilter(); get().updateFilter();
}, },
filterText: '', filterText: '',
setFilterText: (value: string) => set({ filterText: value }), setFilterText: (value: string) => set({ filterText: value }),
filterItems: [], filterItems: [],
filteredCount: 0,
filteredCids: new Set<string>(),
setFilterItems: (items: FilterItem[]) => { setFilterItems: (items: FilterItem[]) => {
const nonEmptyItems = items.filter((item) => item.text.trim() !== ''); const nonEmptyItems = items
.filter((item) => item.text.trim() !== '')
.map((item) => ({
...item,
count: item.count || 0,
filteredCids: item.filteredCids || new Set(),
hide: item.hide ?? true,
top: item.top ?? false,
}));
set({ filterItems: nonEmptyItems }); set({ filterItems: nonEmptyItems });
get().recalcFilteredCount();
}, },
saveAndApplyFilters: (items: FilterItem[]) => { saveAndApplyFilters: (items: FilterItem[]) => {
const nonEmptyItems = items.filter((item) => item.text.trim() !== ''); const nonEmptyItems = items
set({ filterItems: nonEmptyItems, filteredCount: 0 }); .filter((item) => item.text.trim() !== '')
.map((item) => ({
...item,
count: item.count || 0,
filteredCids: item.filteredCids || new Set(),
hide: item.hide ?? true,
top: item.top ?? false,
}));
set({
filterItems: nonEmptyItems,
filteredCids: new Set<string>(),
});
get().recalcFilteredCount();
get().updateFilter(); get().updateFilter();
}, },
filter: undefined, filter: undefined,
updateFilter: () => { updateFilter: () => {
// const filteredCids = new Set<string>();
set((state) => ({ set((state) => ({
filter: (comment: Comment) => { filter: (comment: Comment) => {
const { if (!comment?.cid) return true;
showTextOnlyThreads, const { filterItems } = state;
// filterItems let shouldHide = false;
} = state; for (let i = 0; i < filterItems.length; i++) {
const item = filterItems[i];
const { link, linkHeight, linkWidth, thumbnailUrl } = comment || {}; if (item.enabled && item.text.trim() !== '') {
const pattern = item.text.toLowerCase();
const hasThumbnail = getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), link); const title = comment?.title?.toLowerCase() || '';
// const title = comment?.title?.toLowerCase() || ''; const content = comment?.content?.toLowerCase() || '';
// const content = comment?.content?.toLowerCase() || ''; if (title.includes(pattern) || content.includes(pattern)) {
if (item.hide) {
// const matchesFilterItems = filterItems shouldHide = true;
// .filter((item) => item.enabled) }
// .some((item) => { }
// const text = item.text.toLowerCase(); }
// return title.includes(text) || content.includes(text);
// });
const shouldShow = showTextOnlyThreads || hasThumbnail;
if (
!shouldShow
// || matchesFilterItems
) {
// if (
// matchesFilterItems &&
// !filteredCids.has(comment.cid)) {
// filteredCids.add(comment.cid);
// set((state) => ({ filteredCount: state.filteredCount + 1 }));
// }
return false;
} }
return !shouldHide;
return true;
}, },
})); }));
}, },
initializeFilter: () => { initializeFilter: () => {
const { updateFilter } = get(); get().updateFilter();
updateFilter(); },
incrementFilterCount: (filterIndex: number, cid: string) => {
set((state) => {
const newFilterItems = [...state.filterItems];
if (newFilterItems[filterIndex]) {
const item = newFilterItems[filterIndex];
if (!item.filteredCids.has(cid)) {
const newItemFilteredCids = new Set(item.filteredCids);
newItemFilteredCids.add(cid);
newFilterItems[filterIndex] = {
...item,
count: item.count + 1,
filteredCids: newItemFilteredCids,
};
const newFilteredCount = newFilterItems.reduce((sum, filt) => (filt.hide ? sum + filt.count : sum), 0);
return { filterItems: newFilterItems, filteredCount: newFilteredCount };
}
}
return state;
});
},
recalcFilteredCount: () => {
set((state) => ({
filteredCount: state.filterItems.reduce((sum, item) => (item.hide ? sum + item.count : sum), 0),
}));
}, },
filteredCount: 0,
resetFilteredCount: () => set({ filteredCount: 0 }),
}), }),
{ {
name: 'catalog-filters-storage', name: 'catalog-filters-storage',
onRehydrateStorage: () => (state) => { partialize: (state) => {
if (state) { return {
state.updateFilter(); showTextOnlyThreads: state.showTextOnlyThreads,
} filterItems: state.filterItems.map((item) => ({
text: item.text,
enabled: item.enabled,
hide: item.hide,
top: item.top,
})),
} as any;
}, },
}, },
), ),
@@ -35,7 +35,7 @@
top: 60px; top: 60px;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
width: 340px; width: 380px;
height: auto; height: auto;
max-height: 80%; max-height: 80%;
overflow-y: auto; overflow-y: auto;
@@ -51,6 +51,12 @@
border-left: var(--settings-modal-border-left); border-left: var(--settings-modal-border-left);
} }
@media (max-width: 420px) {
.modal {
width: 90vw;
}
}
.header { .header {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
@@ -142,7 +148,7 @@
outline: none; outline: none;
border: var(--reply-modal-field-input-border, revert); border: var(--reply-modal-field-input-border, revert);
font-size: 11px; font-size: 11px;
width: 200px; width: 150px;
} }
.filtersTable input[type='text']:focus { .filtersTable input[type='text']:focus {
@@ -164,3 +170,22 @@
.saveButton { .saveButton {
float: right; float: right;
} }
.filterHits {
font-size: 12px !important;
text-transform: none;
}
.clickbox {
width: 16px;
height: 16px;
line-height: 16px;
font-size: 10px;
display: block;
text-align: center;
background-color: #fff;
border: 1px solid #aaa;
text-decoration: none;
color: #000;
margin: auto;
}
@@ -1,4 +1,4 @@
import { useState, useCallback, useRef } from 'react'; import { useState, useCallback, useRef, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import useCatalogFiltersStore from '../../../stores/use-catalog-filters-store'; import useCatalogFiltersStore from '../../../stores/use-catalog-filters-store';
import styles from './catalog-filters.module.css'; import styles from './catalog-filters.module.css';
@@ -7,16 +7,44 @@ const FiltersTable = ({ onSave }: { onSave: () => void }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { filterItems, saveAndApplyFilters } = useCatalogFiltersStore(); const { filterItems, saveAndApplyFilters } = useCatalogFiltersStore();
const [localFilterItems, setLocalFilterItems] = useState(filterItems); const [localFilterItems, setLocalFilterItems] = useState(
filterItems.map((item) => ({
...item,
hide: item.hide ?? true,
top: item.top ?? false,
})),
);
const inputRefs = useRef<(HTMLInputElement | null)[]>([]); const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
// Update local items when store changes
useEffect(() => {
setLocalFilterItems(
filterItems.map((item) => ({
...item,
hide: item.hide ?? true,
top: item.top ?? false,
})),
);
}, [filterItems]);
const handleAddFilter = useCallback(() => { const handleAddFilter = useCallback(() => {
setLocalFilterItems((prev) => { setLocalFilterItems((prev) => {
const newIndex = prev.length; const newIndex = prev.length;
setTimeout(() => { setTimeout(() => {
inputRefs.current[newIndex]?.focus(); inputRefs.current[newIndex]?.focus();
}, 0); }, 0);
return [...prev, { text: '', enabled: true }]; return [
...prev,
{
text: '',
enabled: true,
count: 0,
filteredCids: new Set<string>(),
hide: true,
top: false,
},
];
}); });
}, []); }, []);
@@ -47,10 +75,13 @@ const FiltersTable = ({ onSave }: { onSave: () => void }) => {
<table className={styles.filtersTable}> <table className={styles.filtersTable}>
<thead> <thead>
<tr> <tr>
<th>{t('order')}</th> <th>order</th>
<th>{t('enable')}</th> <th>on</th>
<th>{t('text')}</th> <th>pattern</th>
<th>{t('delete')}</th> <th>color</th>
<th>hide</th>
<th>top</th>
<th>del</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -80,17 +111,27 @@ const FiltersTable = ({ onSave }: { onSave: () => void }) => {
ref={(el) => (inputRefs.current[index] = el)} ref={(el) => (inputRefs.current[index] = el)}
/> />
</td> </td>
<td>
<span className={styles.clickbox} />
</td>
<td>
<input type='checkbox' checked={item.hide} onChange={(e) => updateLocalFilterItem(index, { ...item, hide: e.target.checked })} />
</td>
<td>
<input type='checkbox' checked={item.top} onChange={(e) => updateLocalFilterItem(index, { ...item, top: e.target.checked })} />
</td>
<td> <td>
<span className={styles.deleteButton} onClick={() => removeLocalFilterItem(index)}> <span className={styles.deleteButton} onClick={() => removeLocalFilterItem(index)}>
× ×
</span> </span>
</td> </td>
<td className={styles.filterHits}>{item.count > 0 && `x${item.count}`}</td>
</tr> </tr>
))} ))}
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <tr>
<td colSpan={4}> <td colSpan={7}>
<button className={styles.addButton} onClick={handleAddFilter}> <button className={styles.addButton} onClick={handleAddFilter}>
{t('add')} {t('add')}
</button> </button>
@@ -112,7 +153,7 @@ const FiltersModal = ({ closeModal }: { closeModal: () => void }) => {
<div className={styles.overlay} onClick={closeModal} /> <div className={styles.overlay} onClick={closeModal} />
<div className={styles.modal}> <div className={styles.modal}>
<div className={styles.header}> <div className={styles.header}>
<span className={styles.title}>{t('filters')}</span> <span className={styles.title}>{t('filters_and_highlights')}</span>
<span className={styles.closeButton} title='close' onClick={closeModal} /> <span className={styles.closeButton} title='close' onClick={closeModal} />
</div> </div>
<FiltersTable onSave={closeModal} /> <FiltersTable onSave={closeModal} />
@@ -125,13 +166,23 @@ const CatalogFilters = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const closeModal = () => { const closeModal = useCallback(() => {
setShowModal(false); setShowModal(false);
}; }, []);
useEffect(() => {
const onEscapeKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
closeModal();
}
};
document.addEventListener('keydown', onEscapeKey);
return () => document.removeEventListener('keydown', onEscapeKey);
}, [closeModal]);
return ( return (
<> <>
<span className={`${styles.filtersButton} button`} onClick={() => setShowModal(false)} style={{ cursor: 'not-allowed' }}> <span className={`${styles.filtersButton} button`} onClick={() => setShowModal(true)}>
{t('filters')} {t('filters')}
</span> </span>
{showModal && <FiltersModal closeModal={closeModal} />} {showModal && <FiltersModal closeModal={closeModal} />}
+138 -20
View File
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
import { Link, useLocation, useParams } from 'react-router-dom'; import { Link, useLocation, useParams } from 'react-router-dom';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { Comment, useAccount, useFeed, useSubplebbit, useBlock, useAccountComments } from '@plebbit/plebbit-react-hooks'; import { Comment, useAccount, useFeed, useSubplebbit, useBlock, useAccountComments } from '@plebbit/plebbit-react-hooks';
@@ -14,22 +14,97 @@ import useCatalogStyleStore from '../../stores/use-catalog-style-store';
import useFeedResetStore from '../../stores/use-feed-reset-store'; import useFeedResetStore from '../../stores/use-feed-reset-store';
import useInterfaceSettingsStore from '../../stores/use-interface-settings-store'; import useInterfaceSettingsStore from '../../stores/use-interface-settings-store';
import useSortingStore from '../../stores/use-sorting-store'; import useSortingStore from '../../stores/use-sorting-store';
import useCatalogFiltersStore from '../../stores/use-catalog-filters-store';
import CatalogRow from '../../components/catalog-row'; import CatalogRow from '../../components/catalog-row';
import LoadingEllipsis from '../../components/loading-ellipsis'; import LoadingEllipsis from '../../components/loading-ellipsis';
import styles from './catalog.module.css'; import styles from './catalog.module.css';
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {}; const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
const createThreadsWithoutImagesFilter = () => ({ const createContentFilter = (
filter: (comment: Comment) => { filterItems: { text: string; enabled: boolean; count: number; filteredCids: Set<string>; hide: boolean; top: boolean }[],
const { link, linkHeight, linkWidth, thumbnailUrl } = comment || {}; onFilterMatch?: (filterIndex: number, cid: string) => void,
if (!getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), link)) { ) => {
return false; // Create a unique key based on the enabled filter items
} const enabledFilters = filterItems.filter((item) => item.enabled && item.text.trim() !== '');
return true; const filterKey =
}, enabledFilters.length > 0
key: 'threads-with-images-only', ? `content-filter-${enabledFilters.map((item) => `${item.text}-${item.hide ? 'hide' : ''}-${item.top ? 'top' : ''}`).join('-')}`
}); : 'no-content-filter';
return {
filter: (comment: Comment) => {
if (!comment?.cid) return true;
if (enabledFilters.length === 0) return true;
const titleLower = comment?.title?.toLowerCase() || '';
const contentLower = comment?.content?.toLowerCase() || '';
// Check if any enabled filter matches the content
for (let i = 0; i < enabledFilters.length; i++) {
const item = enabledFilters[i];
const pattern = item.text.toLowerCase();
if (titleLower.includes(pattern) || contentLower.includes(pattern)) {
// Find the original filter index to increment count
const filterIndex = filterItems.findIndex((f) => f.text === item.text && f.enabled);
if (filterIndex !== -1) {
if (onFilterMatch) {
onFilterMatch(filterIndex, comment.cid);
} else {
// Fallback to the store method if no callback provided
useCatalogFiltersStore.getState().incrementFilterCount(filterIndex, comment.cid);
}
}
// If this filter is set to hide, filter out the comment
if (item.hide) {
return false;
}
// If this filter is set to top, we'll handle it separately in the component
// (we don't filter it out here)
}
}
return true;
},
key: filterKey,
};
};
const createImageFilter = (showTextOnlyThreads: boolean) => {
return {
filter: (comment: Comment) => {
if (showTextOnlyThreads) return true;
const { link, linkHeight, linkWidth, thumbnailUrl } = comment || {};
const hasThumbnail = getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), link);
return hasThumbnail;
},
key: showTextOnlyThreads ? 'no-image-filter' : 'threads-with-images-only',
};
};
const createCombinedFilter = (
showTextOnlyThreads: boolean,
filterItems: { text: string; enabled: boolean; count: number; filteredCids: Set<string>; hide: boolean; top: boolean }[],
onFilterMatch?: (filterIndex: number, cid: string) => void,
) => {
const imageFilter = createImageFilter(showTextOnlyThreads);
const contentFilter = createContentFilter(filterItems, onFilterMatch);
return {
filter: (comment: Comment) => {
if (!imageFilter.filter(comment)) return false;
return contentFilter.filter(comment);
},
key: `${imageFilter.key}-${contentFilter.key}`,
};
};
const Catalog = () => { const Catalog = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -39,7 +114,7 @@ const Catalog = () => {
const isInAllView = isAllView(location.pathname); const isInAllView = isAllView(location.pathname);
const defaultSubplebbits = useDefaultSubplebbits(); const defaultSubplebbits = useDefaultSubplebbits();
const { hideAdultBoards, hideGoreBoards } = useInterfaceSettingsStore(); const { hideAdultBoards, hideGoreBoards } = useInterfaceSettingsStore();
const { hideThreadsWithoutImages } = useInterfaceSettingsStore(); const { showTextOnlyThreads, filterItems } = useCatalogFiltersStore();
const account = useAccount(); const account = useAccount();
const subscriptions = account?.subscriptions; const subscriptions = account?.subscriptions;
@@ -77,12 +152,17 @@ const Catalog = () => {
const { timeFilterSeconds, timeFilterName } = useTimeFilter(); const { timeFilterSeconds, timeFilterName } = useTimeFilter();
const { sortType } = useSortingStore(); const { sortType } = useSortingStore();
// stable callback for filter matching
const handleFilterMatch = useCallback((filterIndex: number, cid: string) => {
useCatalogFiltersStore.getState().incrementFilterCount(filterIndex, cid);
}, []);
const feedOptions = useMemo(() => { const feedOptions = useMemo(() => {
const options: any = { const options: any = {
subplebbitAddresses, subplebbitAddresses,
sortType, sortType,
postsPerPage: isInAllView || isInSubscriptionsView ? 10 : postsPerPage, postsPerPage: isInAllView || isInSubscriptionsView ? 10 : postsPerPage,
filter: hideThreadsWithoutImages ? createThreadsWithoutImagesFilter() : undefined, filter: createCombinedFilter(showTextOnlyThreads, filterItems, handleFilterMatch),
}; };
if (isInAllView || isInSubscriptionsView) { if (isInAllView || isInSubscriptionsView) {
@@ -90,7 +170,7 @@ const Catalog = () => {
} }
return options; return options;
}, [subplebbitAddresses, sortType, isInAllView, isInSubscriptionsView, postsPerPage, timeFilterSeconds, hideThreadsWithoutImages]); }, [subplebbitAddresses, sortType, isInAllView, isInSubscriptionsView, postsPerPage, timeFilterSeconds, showTextOnlyThreads, filterItems, handleFilterMatch]);
const { feed, hasMore, loadMore, reset, subplebbitAddressesWithNewerPosts } = useFeed(feedOptions); const { feed, hasMore, loadMore, reset, subplebbitAddressesWithNewerPosts } = useFeed(feedOptions);
const { accountComments } = useAccountComments(); const { accountComments } = useAccountComments();
@@ -108,13 +188,13 @@ const Catalog = () => {
timestamp > Date.now() / 1000 - 60 * 60 && timestamp > Date.now() / 1000 - 60 * 60 &&
state === 'succeeded' && state === 'succeeded' &&
cid && cid &&
(hideThreadsWithoutImages ? getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), comment?.link) : true) && (showTextOnlyThreads ? getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), comment?.link) : true) &&
cid === postCid && cid === postCid &&
comment?.subplebbitAddress === subplebbitAddress && comment?.subplebbitAddress === subplebbitAddress &&
!feed.some((post) => post.cid === cid) !feed.some((post) => post.cid === cid)
); );
}), }),
[accountComments, subplebbitAddress, feed, hideThreadsWithoutImages], [accountComments, subplebbitAddress, feed, showTextOnlyThreads],
); );
// show newest account comment at the top of the feed but after pinned posts // show newest account comment at the top of the feed but after pinned posts
@@ -146,13 +226,14 @@ const Catalog = () => {
subplebbitAddresses, subplebbitAddresses,
sortType, sortType,
newerThan: 60 * 60 * 24 * 7, newerThan: 60 * 60 * 24 * 7,
filter: hideThreadsWithoutImages ? createThreadsWithoutImagesFilter() : undefined, filter: createCombinedFilter(showTextOnlyThreads, filterItems, handleFilterMatch),
}); });
const { feed: monthlyFeed } = useFeed({ const { feed: monthlyFeed } = useFeed({
subplebbitAddresses, subplebbitAddresses,
sortType, sortType,
newerThan: 60 * 60 * 24 * 30, newerThan: 60 * 60 * 24 * 30,
filter: hideThreadsWithoutImages ? createThreadsWithoutImagesFilter() : undefined, filter: createCombinedFilter(showTextOnlyThreads, filterItems, handleFilterMatch),
}); });
const [showMorePostsSuggestion, setShowMorePostsSuggestion] = useState(false); const [showMorePostsSuggestion, setShowMorePostsSuggestion] = useState(false);
@@ -265,7 +346,44 @@ const Catalog = () => {
const isFeedLoaded = feed.length > 0 || state === 'failed'; const isFeedLoaded = feed.length > 0 || state === 'failed';
const rows = useCatalogFeedRows(columnCount, combinedFeed, isFeedLoaded, subplebbit); // Process the feed to move "top" posts to the top
const processedFeed = useMemo(() => {
if (!combinedFeed || combinedFeed.length === 0) return combinedFeed;
const enabledTopFilters = filterItems.filter((item) => item.enabled && item.text.trim() !== '' && item.top);
if (enabledTopFilters.length === 0) return combinedFeed;
// Separate posts that match "top" filters
const topPosts: Comment[] = [];
const regularPosts: Comment[] = [];
combinedFeed.forEach((comment) => {
if (!comment) return;
const titleLower = comment?.title?.toLowerCase() || '';
const contentLower = comment?.content?.toLowerCase() || '';
let isTop = false;
for (const filter of enabledTopFilters) {
const pattern = filter.text.toLowerCase();
if (titleLower.includes(pattern) || contentLower.includes(pattern)) {
isTop = true;
break;
}
}
if (isTop) {
topPosts.push(comment);
} else {
regularPosts.push(comment);
}
});
// Return top posts followed by regular posts
return [...topPosts, ...regularPosts];
}, [combinedFeed, filterItems]);
const rows = useCatalogFeedRows(columnCount, processedFeed, isFeedLoaded, subplebbit);
// save the last Virtuoso state to restore it when navigating back // save the last Virtuoso state to restore it when navigating back
const virtuosoRef = useRef<VirtuosoHandle | null>(null); const virtuosoRef = useRef<VirtuosoHandle | null>(null);
@@ -293,7 +411,7 @@ const Catalog = () => {
<div className={styles.content}> <div className={styles.content}>
<hr /> <hr />
<div className={styles.catalog}> <div className={styles.catalog}>
{combinedFeed.length !== 0 ? ( {processedFeed.length !== 0 ? (
<> <>
<Virtuoso <Virtuoso
increaseViewportBy={{ bottom: 1200, top: 1200 }} increaseViewportBy={{ bottom: 1200, top: 1200 }}
-16
View File
@@ -3085,22 +3085,6 @@
uuid "8.3.2" uuid "8.3.2"
zustand "4.0.0" zustand "4.0.0"
"@plebbit/plebbit-react-hooks@https://github.com/plebbit/plebbit-react-hooks.git#6d35eb3b4dc84f8fdcbb46d95e3b1d78f1750b5f":
version "0.0.1"
resolved "https://github.com/plebbit/plebbit-react-hooks.git#6d35eb3b4dc84f8fdcbb46d95e3b1d78f1750b5f"
dependencies:
"@plebbit/plebbit-js" "https://github.com/plebbit/plebbit-js.git#5ff48e9b9a906a098a51b474a52692eebfb6561d"
"@plebbit/plebbit-logger" "https://github.com/plebbit/plebbit-logger.git"
assert "2.0.0"
ethers "5.6.9"
localforage "1.10.0"
lodash.isequal "4.5.0"
memoizee "0.4.15"
quick-lru "5.1.1"
uint8arrays "3.1.1"
uuid "8.3.2"
zustand "4.0.0"
"@plebbit/proper-lockfile@github:plebbit/node-proper-lockfile#7fd6332117340c1d3d98dd0afee2d31cc06f72b8": "@plebbit/proper-lockfile@github:plebbit/node-proper-lockfile#7fd6332117340c1d3d98dd0afee2d31cc06f72b8":
version "4.1.2" version "4.1.2"
resolved "https://codeload.github.com/plebbit/node-proper-lockfile/tar.gz/7fd6332117340c1d3d98dd0afee2d31cc06f72b8" resolved "https://codeload.github.com/plebbit/node-proper-lockfile/tar.gz/7fd6332117340c1d3d98dd0afee2d31cc06f72b8"