From d7a9c1640babd5b468e7551ead4280a6f4819390 Mon Sep 17 00:00:00 2001 From: "Tom (plebeius.eth)" Date: Thu, 6 Mar 2025 23:48:17 +0100 Subject: [PATCH] feat(catalog filter): add filtering with complex patterns, including regex, help modal --- .../{question-blue.png => help-blue.png} | Bin .../{question-dark.png => help-dark.png} | Bin .../{question-photon.png => help-photon.png} | Bin .../{question-red.png => help-red.png} | Bin public/translations/ar/default.json | 5 +- public/translations/bn/default.json | 5 +- public/translations/cs/default.json | 5 +- public/translations/da/default.json | 5 +- public/translations/de/default.json | 5 +- public/translations/el/default.json | 5 +- public/translations/en/default.json | 5 +- public/translations/es/default.json | 5 +- public/translations/fa/default.json | 5 +- public/translations/fi/default.json | 5 +- public/translations/fil/default.json | 5 +- public/translations/fr/default.json | 5 +- public/translations/he/default.json | 5 +- public/translations/hi/default.json | 5 +- public/translations/hu/default.json | 5 +- public/translations/id/default.json | 5 +- public/translations/it/default.json | 5 +- public/translations/ja/default.json | 5 +- public/translations/ko/default.json | 5 +- public/translations/mr/default.json | 5 +- public/translations/nl/default.json | 5 +- public/translations/no/default.json | 5 +- public/translations/pl/default.json | 5 +- public/translations/pt/default.json | 5 +- public/translations/ro/default.json | 5 +- public/translations/ru/default.json | 5 +- public/translations/sq/default.json | 5 +- public/translations/sv/default.json | 5 +- public/translations/te/default.json | 5 +- public/translations/th/default.json | 5 +- public/translations/tr/default.json | 5 +- public/translations/uk/default.json | 5 +- public/translations/ur/default.json | 5 +- public/translations/vi/default.json | 5 +- public/translations/zh/default.json | 5 +- .../catalog-filters.module.css | 78 ++++++++++-- .../catalog-filters/catalog-filters.tsx | 115 ++++++++++++++++-- src/lib/utils/pattern-utils.ts | 110 +++++++++++++++++ src/stores/use-catalog-filters-store.ts | 77 ++++++++++-- src/themes.css | 9 +- src/views/catalog/catalog.tsx | 21 +--- 45 files changed, 500 insertions(+), 85 deletions(-) rename public/assets/buttons/{question-blue.png => help-blue.png} (100%) rename public/assets/buttons/{question-dark.png => help-dark.png} (100%) rename public/assets/buttons/{question-photon.png => help-photon.png} (100%) rename public/assets/buttons/{question-red.png => help-red.png} (100%) create mode 100644 src/lib/utils/pattern-utils.ts diff --git a/public/assets/buttons/question-blue.png b/public/assets/buttons/help-blue.png similarity index 100% rename from public/assets/buttons/question-blue.png rename to public/assets/buttons/help-blue.png diff --git a/public/assets/buttons/question-dark.png b/public/assets/buttons/help-dark.png similarity index 100% rename from public/assets/buttons/question-dark.png rename to public/assets/buttons/help-dark.png diff --git a/public/assets/buttons/question-photon.png b/public/assets/buttons/help-photon.png similarity index 100% rename from public/assets/buttons/question-photon.png rename to public/assets/buttons/help-photon.png diff --git a/public/assets/buttons/question-red.png b/public/assets/buttons/help-red.png similarity index 100% rename from public/assets/buttons/question-red.png rename to public/assets/buttons/help-red.png diff --git a/public/translations/ar/default.json b/public/translations/ar/default.json index d1efbc9c..113e9c0e 100644 --- a/public/translations/ar/default.json +++ b/public/translations/ar/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "تمت إزالة هذه الرد من قبل مشرفي المنتدى", "not_mod_of_any_board": "أنت لست مشرفًا في أي لوحة.", "filters_and_highlights": "الفلاتر والإبرازات", - "search_results_for": "نتائج البحث عن" + "search_results_for": "نتائج البحث عن", + "help": "مساعدة", + "filter_and_highlights_help": "مساعدة الفلاتر والإبرازات", + "filter_and_highlights": "التصفية والتسليط الضوء" } \ No newline at end of file diff --git a/public/translations/bn/default.json b/public/translations/bn/default.json index 83602f2a..16fef571 100644 --- a/public/translations/bn/default.json +++ b/public/translations/bn/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "এই প্রতিক্রিয়া বোর্ড পরিচালকদের দ্বারা সরানো হয়েছে", "not_mod_of_any_board": "আপনি কোনও বোর্ডের মড নন।", "filters_and_highlights": "ফিল্টার এবং হাইলাইট", - "search_results_for": "অনুসন্ধান ফলাফল" + "search_results_for": "অনুসন্ধান ফলাফল", + "help": "সাহায্য", + "filter_and_highlights_help": "ফিল্টার এবং হাইলাইট সাহায্য", + "filter_and_highlights": "ফিল্টার এবং হাইলাইট" } \ No newline at end of file diff --git a/public/translations/cs/default.json b/public/translations/cs/default.json index 513d6992..70fabc09 100644 --- a/public/translations/cs/default.json +++ b/public/translations/cs/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "Tato odpověď byla odstraněna moderátory fóra", "not_mod_of_any_board": "Nejste moderátor žádné desky.", "filters_and_highlights": "Filtry a zvýraznění", - "search_results_for": "Výsledky hledání pro" + "search_results_for": "Výsledky hledání pro", + "help": "Pomoc", + "filter_and_highlights_help": "Pomoc s filtry a zvýrazněním", + "filter_and_highlights": "Filtry a zvýraznění" } \ No newline at end of file diff --git a/public/translations/da/default.json b/public/translations/da/default.json index 3def0138..daaa9a56 100644 --- a/public/translations/da/default.json +++ b/public/translations/da/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "Dette svar blev fjernet af forummoderatorer", "not_mod_of_any_board": "Du er ikke moderator for nogen tavler.", "filters_and_highlights": "Filtre og fremhævninger", - "search_results_for": "Søgeresultater for" + "search_results_for": "Søgeresultater for", + "help": "Hjælp", + "filter_and_highlights_help": "Filtre og fremhævelser Hjælp", + "filter_and_highlights": "Filtre og fremhævninger" } \ No newline at end of file diff --git a/public/translations/de/default.json b/public/translations/de/default.json index 9750d5a4..59b755c2 100644 --- a/public/translations/de/default.json +++ b/public/translations/de/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "Diese Antwort wurde von den Forenmoderatoren entfernt", "not_mod_of_any_board": "Du bist kein Moderator eines Boards.", "filters_and_highlights": "Filter & Highlights", - "search_results_for": "Suchergebnisse für" + "search_results_for": "Suchergebnisse für", + "help": "Hilfe", + "filter_and_highlights_help": "Filter- und Markierungs-Hilfe", + "filter_and_highlights": "Filter und Hervorhebungen" } \ No newline at end of file diff --git a/public/translations/el/default.json b/public/translations/el/default.json index 0a1c369c..2929b622 100644 --- a/public/translations/el/default.json +++ b/public/translations/el/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "Η απάντηση αυτή αφαιρέθηκε από τους διαχειριστές του φόρουμ", "not_mod_of_any_board": "Δεν είστε διαχειριστής σε καμία κοινότητα.", "filters_and_highlights": "Φίλτρα και επισημάνσεις", - "search_results_for": "Αποτελέσματα αναζήτησης για" + "search_results_for": "Αποτελέσματα αναζήτησης για", + "help": "Βοήθεια", + "filter_and_highlights_help": "Βοήθεια φίλτρων και επισήμανσης", + "filter_and_highlights": "Φίλτρα και Επισημάνσεις" } \ No newline at end of file diff --git a/public/translations/en/default.json b/public/translations/en/default.json index f17a13c5..0c9ee101 100644 --- a/public/translations/en/default.json +++ b/public/translations/en/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "This reply was removed by board moderators", "not_mod_of_any_board": "You are not a mod of any board.", "filters_and_highlights": "Filters & Highlights", - "search_results_for": "Search results for" + "search_results_for": "Search results for", + "help": "Help", + "filter_and_highlights_help": "Filters & Highlights Help", + "filter_and_highlights": "Filters & Highlights" } \ No newline at end of file diff --git a/public/translations/es/default.json b/public/translations/es/default.json index ea303d00..9d1732fa 100644 --- a/public/translations/es/default.json +++ b/public/translations/es/default.json @@ -293,5 +293,8 @@ "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.", "filters_and_highlights": "Filtros y resaltar", - "search_results_for": "Resultados de búsqueda para" + "search_results_for": "Resultados de búsqueda para", + "help": "Ayuda", + "filter_and_highlights_help": "Ayuda de filtros y resaltes", + "filter_and_highlights": "Filtros y resaltados" } \ No newline at end of file diff --git a/public/translations/fa/default.json b/public/translations/fa/default.json index fb331102..704609a8 100644 --- a/public/translations/fa/default.json +++ b/public/translations/fa/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "این پاسخ توسط مدیران انجمن حذف شد", "not_mod_of_any_board": "شما هیچ تابلو مدیر نیستید.", "filters_and_highlights": "فیلترها و هایلایت‌ها", - "search_results_for": "نتایج جستجو برای" + "search_results_for": "نتایج جستجو برای", + "help": "کمک", + "filter_and_highlights_help": "کمک فیلترها و برجسته‌ها", + "filter_and_highlights": "فیلترها و برجسته‌ها" } \ No newline at end of file diff --git a/public/translations/fi/default.json b/public/translations/fi/default.json index 2e66654c..f95e9ee9 100644 --- a/public/translations/fi/default.json +++ b/public/translations/fi/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "Tämä vastaus poistettiin foorumin moderaattoreiden toimesta", "not_mod_of_any_board": "Et ole moderaattori missään taulussa.", "filters_and_highlights": "Suodattimet ja korostukset", - "search_results_for": "Hakutulokset hakusanalle" + "search_results_for": "Hakutulokset hakusanalle", + "help": "Apua", + "filter_and_highlights_help": "Suodattimet ja korostukset -apu", + "filter_and_highlights": "Suodattimet ja korostukset" } \ No newline at end of file diff --git a/public/translations/fil/default.json b/public/translations/fil/default.json index d233af04..fe1679f5 100644 --- a/public/translations/fil/default.json +++ b/public/translations/fil/default.json @@ -293,5 +293,8 @@ "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.", "filters_and_highlights": "Mga filter at highlight", - "search_results_for": "Mga resulta ng paghahanap para sa" + "search_results_for": "Mga resulta ng paghahanap para sa", + "help": "Tulong", + "filter_and_highlights_help": "Tulong sa mga filter at highlight", + "filter_and_highlights": "Mga filter at highlight" } \ No newline at end of file diff --git a/public/translations/fr/default.json b/public/translations/fr/default.json index 98b51f6a..d987ed39 100644 --- a/public/translations/fr/default.json +++ b/public/translations/fr/default.json @@ -293,5 +293,8 @@ "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.", "filters_and_highlights": "Filtres et mises en évidence", - "search_results_for": "Résultats de recherche pour" + "search_results_for": "Résultats de recherche pour", + "help": "Aide", + "filter_and_highlights_help": "Aide sur les filtres et surlignages", + "filter_and_highlights": "Filtres et surlignages" } \ No newline at end of file diff --git a/public/translations/he/default.json b/public/translations/he/default.json index 337312cc..728a16d2 100644 --- a/public/translations/he/default.json +++ b/public/translations/he/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "תשובה זו הוסרה על ידי מנהלי הפורום", "not_mod_of_any_board": "אתה לא מוד בכל לוח.", "filters_and_highlights": "סינונים והדגשות", - "search_results_for": "תוצאות חיפוש עבור" + "search_results_for": "תוצאות חיפוש עבור", + "help": "עזרה", + "filter_and_highlights_help": "עזרה במסננים והדגשות", + "filter_and_highlights": "מסננים והדגשות" } \ No newline at end of file diff --git a/public/translations/hi/default.json b/public/translations/hi/default.json index 423665e1..e9ca1f34 100644 --- a/public/translations/hi/default.json +++ b/public/translations/hi/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "यह उत्तर बोर्ड मॉडरेटर द्वारा हटा दिया गया था", "not_mod_of_any_board": "आप किसी बोर्ड के मॉड नहीं हैं।", "filters_and_highlights": "फ़िल्टर और हाइलाइट्स", - "search_results_for": "के लिए खोज परिणाम" + "search_results_for": "के लिए खोज परिणाम", + "help": "मदद", + "filter_and_highlights_help": "फ़िल्टर और हाइलाइट्स सहायता", + "filter_and_highlights": "फ़िल्टर और हाइलाइट्स" } \ No newline at end of file diff --git a/public/translations/hu/default.json b/public/translations/hu/default.json index 76500163..33fe9f0f 100644 --- a/public/translations/hu/default.json +++ b/public/translations/hu/default.json @@ -293,5 +293,8 @@ "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.", "filters_and_highlights": "Szűrők és kiemelések", - "search_results_for": "Keresési eredmények" + "search_results_for": "Keresési eredmények", + "help": "Segítség", + "filter_and_highlights_help": "Szűrők és kiemelések súgó", + "filter_and_highlights": "Szűrők és kiemelések" } \ No newline at end of file diff --git a/public/translations/id/default.json b/public/translations/id/default.json index 8e6ba818..a07da699 100644 --- a/public/translations/id/default.json +++ b/public/translations/id/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "Balasan ini dihapus oleh moderator papan", "not_mod_of_any_board": "Anda bukan mod di papan manapun.", "filters_and_highlights": "Filter dan Sorotan", - "search_results_for": "Hasil pencarian untuk" + "search_results_for": "Hasil pencarian untuk", + "help": "Bantuan", + "filter_and_highlights_help": "Bantuan Filter dan Sorotan", + "filter_and_highlights": "Filter dan Sorotan" } \ No newline at end of file diff --git a/public/translations/it/default.json b/public/translations/it/default.json index 483ed455..d38e0082 100644 --- a/public/translations/it/default.json +++ b/public/translations/it/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "Questa risposta è stata rimossa dai moderatori del forum", "not_mod_of_any_board": "Non sei un mod di nessuna board.", "filters_and_highlights": "Filtri e Evidenziazioni", - "search_results_for": "Risultati di ricerca per" + "search_results_for": "Risultati di ricerca per", + "help": "Aiuto", + "filter_and_highlights_help": "Aiuto su Filtri e Evidenziazioni", + "filter_and_highlights": "Filtri e Evidenziazioni" } \ No newline at end of file diff --git a/public/translations/ja/default.json b/public/translations/ja/default.json index ecef105c..86c89a10 100644 --- a/public/translations/ja/default.json +++ b/public/translations/ja/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "この返信は掲示板のモデレーターによって削除されました", "not_mod_of_any_board": "あなたはどのボードのモデレーターでもありません。", "filters_and_highlights": "フィルターとハイライト", - "search_results_for": "検索結果" + "search_results_for": "検索結果", + "help": "ヘルプ", + "filter_and_highlights_help": "フィルターとハイライトのヘルプ", + "filter_and_highlights": "フィルターとハイライト" } \ No newline at end of file diff --git a/public/translations/ko/default.json b/public/translations/ko/default.json index 12a791f3..d0c5093c 100644 --- a/public/translations/ko/default.json +++ b/public/translations/ko/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "이 답글은 게시판 관리자에 의해 삭제되었습니다", "not_mod_of_any_board": "당신은 어떤 게시판의 관리자도 아닙니다.", "filters_and_highlights": "필터 및 하이라이트", - "search_results_for": "검색 결과" + "search_results_for": "검색 결과", + "help": "도움말", + "filter_and_highlights_help": "필터 및 하이라이트 도움말", + "filter_and_highlights": "필터 및 하이라이트" } \ No newline at end of file diff --git a/public/translations/mr/default.json b/public/translations/mr/default.json index d8c983cb..43a896bd 100644 --- a/public/translations/mr/default.json +++ b/public/translations/mr/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "या उत्तराला बोर्ड मॉडरेटरने काढले आहे", "not_mod_of_any_board": "तुम्ही कोणत्याही बोर्डचे मॉड नाहीत.", "filters_and_highlights": "फिल्टर आणि हायलाइट्स", - "search_results_for": "सर्च परिणाम" + "search_results_for": "सर्च परिणाम", + "help": "मदत", + "filter_and_highlights_help": "फिल्टर आणि हायलाइट्स सहाय्य", + "filter_and_highlights": "फिल्टर आणि हायलाइट्स" } \ No newline at end of file diff --git a/public/translations/nl/default.json b/public/translations/nl/default.json index 933ff4e5..fc9a2b5d 100644 --- a/public/translations/nl/default.json +++ b/public/translations/nl/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "Dit antwoord is verwijderd door de forum moderators", "not_mod_of_any_board": "Je bent geen mod van een bord.", "filters_and_highlights": "Filters en markeringen", - "search_results_for": "Zoekresultaten voor" + "search_results_for": "Zoekresultaten voor", + "help": "Help", + "filter_and_highlights_help": "Filters en Markeringen Help", + "filter_and_highlights": "Filters en markeringen" } \ No newline at end of file diff --git a/public/translations/no/default.json b/public/translations/no/default.json index d2ff1f21..6d78cf80 100644 --- a/public/translations/no/default.json +++ b/public/translations/no/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "Dette svaret ble fjernet av forummoderatorer", "not_mod_of_any_board": "Du er ikke moderator på noen brett.", "filters_and_highlights": "Filtre og fremhevinger", - "search_results_for": "Søkeresultater for" + "search_results_for": "Søkeresultater for", + "help": "Hjelp", + "filter_and_highlights_help": "Filtre og fremhevinger Hjelp", + "filter_and_highlights": "Filtre og uthevinger" } \ No newline at end of file diff --git a/public/translations/pl/default.json b/public/translations/pl/default.json index e1299990..688d198c 100644 --- a/public/translations/pl/default.json +++ b/public/translations/pl/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "Ta odpowiedź została usunięta przez moderatorów forum", "not_mod_of_any_board": "Nie jesteś moderatorem żadnej tablicy.", "filters_and_highlights": "Filtry i wyróżnienia", - "search_results_for": "Wyniki wyszukiwania dla" + "search_results_for": "Wyniki wyszukiwania dla", + "help": "Pomoc", + "filter_and_highlights_help": "Pomoc dotycząca filtrów i wyróżnień", + "filter_and_highlights": "Filtry i wyróżnienia" } \ No newline at end of file diff --git a/public/translations/pt/default.json b/public/translations/pt/default.json index 36fed0ef..fb52e2f8 100644 --- a/public/translations/pt/default.json +++ b/public/translations/pt/default.json @@ -293,5 +293,8 @@ "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.", "filters_and_highlights": "Filtros e Destaques", - "search_results_for": "Resultados de pesquisa para" + "search_results_for": "Resultados de pesquisa para", + "help": "Ajuda", + "filter_and_highlights_help": "Ajuda de Filtros e Realces", + "filter_and_highlights": "Filtros e Destaques" } \ No newline at end of file diff --git a/public/translations/ro/default.json b/public/translations/ro/default.json index 70bd1821..57181d73 100644 --- a/public/translations/ro/default.json +++ b/public/translations/ro/default.json @@ -293,5 +293,8 @@ "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.", "filters_and_highlights": "Filtre și evidențiere", - "search_results_for": "Rezultatele căutării pentru" + "search_results_for": "Rezultatele căutării pentru", + "help": "Ajutor", + "filter_and_highlights_help": "Ajutor pentru filtre și evidențieri", + "filter_and_highlights": "Filtre și evidențieri" } \ No newline at end of file diff --git a/public/translations/ru/default.json b/public/translations/ru/default.json index 950de4d8..443d2ae5 100644 --- a/public/translations/ru/default.json +++ b/public/translations/ru/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "Этот ответ был удален модераторами форума", "not_mod_of_any_board": "Вы не модератор ни одной доски.", "filters_and_highlights": "Фильтры и выделения", - "search_results_for": "Результаты поиска для" + "search_results_for": "Результаты поиска для", + "help": "Помощь", + "filter_and_highlights_help": "Помощь с фильтрами и выделениями", + "filter_and_highlights": "Фильтры и выделения" } \ No newline at end of file diff --git a/public/translations/sq/default.json b/public/translations/sq/default.json index 6752e2b9..a3cc9707 100644 --- a/public/translations/sq/default.json +++ b/public/translations/sq/default.json @@ -293,5 +293,8 @@ "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.", "filters_and_highlights": "Filtra dhe theksime", - "search_results_for": "Rezultatet e kërkimit për" + "search_results_for": "Rezultatet e kërkimit për", + "help": "Ndihmë", + "filter_and_highlights_help": "Ndihma për Filtro & Thekso", + "filter_and_highlights": "Filtra dhe thekson" } \ No newline at end of file diff --git a/public/translations/sv/default.json b/public/translations/sv/default.json index 0c3bccb3..63883358 100644 --- a/public/translations/sv/default.json +++ b/public/translations/sv/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "Detta svar togs bort av forumets moderatorer", "not_mod_of_any_board": "Du är inte moderator på någon tavla.", "filters_and_highlights": "Filter och markeringar", - "search_results_for": "Sökresultat för" + "search_results_for": "Sökresultat för", + "help": "Hjälp", + "filter_and_highlights_help": "Filter och markeringar Hjälp", + "filter_and_highlights": "Filter och markeringar" } \ No newline at end of file diff --git a/public/translations/te/default.json b/public/translations/te/default.json index b0aec7b8..ffb84a90 100644 --- a/public/translations/te/default.json +++ b/public/translations/te/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "ఈ స్పందన బోర్డ్ మోడరేటర్లచే తొలగించబడింది", "not_mod_of_any_board": "మీరు ఎటువంటి బోర్డుకు మోడ్ కంటే లేరు.", "filters_and_highlights": "ఫిల్టర్లు మరియు హైలైట్‌లు", - "search_results_for": "శోధన ఫలితాలు" + "search_results_for": "శోధన ఫలితాలు", + "help": "సహాయం", + "filter_and_highlights_help": "ఫిల్టర్లు మరియు హైలైట్ సహాయం", + "filter_and_highlights": "ఫిల్టర్లు మరియు హైలైట్స్" } \ No newline at end of file diff --git a/public/translations/th/default.json b/public/translations/th/default.json index 92adbe11..885880a5 100644 --- a/public/translations/th/default.json +++ b/public/translations/th/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "คำตอบนี้ถูกลบโดยผู้ดูแลบอร์ด", "not_mod_of_any_board": "คุณไม่ใช่ผู้ดูแลบอร์ดใดๆ", "filters_and_highlights": "ตัวกรองและไฮไลท์", - "search_results_for": "ผลการค้นหาสำหรับ" + "search_results_for": "ผลการค้นหาสำหรับ", + "help": "ช่วยเหลือ", + "filter_and_highlights_help": "ตัวกรองและการเน้นความช่วยเหลือ", + "filter_and_highlights": "ตัวกรองและไฮไลท์" } \ No newline at end of file diff --git a/public/translations/tr/default.json b/public/translations/tr/default.json index 1f74ba69..b74ec005 100644 --- a/public/translations/tr/default.json +++ b/public/translations/tr/default.json @@ -293,5 +293,8 @@ "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.", "filters_and_highlights": "Filtreler ve Vurgular", - "search_results_for": "Arama sonuçları için" + "search_results_for": "Arama sonuçları için", + "help": "Yardım", + "filter_and_highlights_help": "Filtreler ve Vurgular Yardımı", + "filter_and_highlights": "Filtreler ve Vurgular" } \ No newline at end of file diff --git a/public/translations/uk/default.json b/public/translations/uk/default.json index 2414bdee..81c3feff 100644 --- a/public/translations/uk/default.json +++ b/public/translations/uk/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "Цю відповідь було видалено модераторами форуму", "not_mod_of_any_board": "Ви не модератор жодної дошки.", "filters_and_highlights": "Фільтри та виділення", - "search_results_for": "Результати пошуку для" + "search_results_for": "Результати пошуку для", + "help": "Допомога", + "filter_and_highlights_help": "Допомога з фільтрами та підсвічуваннями", + "filter_and_highlights": "Фільтри та виділення" } \ No newline at end of file diff --git a/public/translations/ur/default.json b/public/translations/ur/default.json index 93e8c58d..03240bd8 100644 --- a/public/translations/ur/default.json +++ b/public/translations/ur/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "اس جواب کو بورڈ کے منتظمین نے حذف کر دیا ہے", "not_mod_of_any_board": "آپ کسی بورڈ کے موڈ نہیں ہیں۔", "filters_and_highlights": "فلٹرز اور ہائیلائٹس", - "search_results_for": "تلاش کے نتائج" + "search_results_for": "تلاش کے نتائج", + "help": "مدد", + "filter_and_highlights_help": "فلٹرز اور ہائی لائٹس کی مدد", + "filter_and_highlights": "فلٹرز اور ہائلाइटس" } \ No newline at end of file diff --git a/public/translations/vi/default.json b/public/translations/vi/default.json index 010e1085..9c5cb55d 100644 --- a/public/translations/vi/default.json +++ b/public/translations/vi/default.json @@ -293,5 +293,8 @@ "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.", "filters_and_highlights": "Bộ lọc và Nổi bật", - "search_results_for": "Kết quả tìm kiếm cho" + "search_results_for": "Kết quả tìm kiếm cho", + "help": "Trợ giúp", + "filter_and_highlights_help": "Trợ giúp bộ lọc và đánh dấu", + "filter_and_highlights": "Bộ lọc và Nổi bật" } \ No newline at end of file diff --git a/public/translations/zh/default.json b/public/translations/zh/default.json index 22bd4a80..b8fc65bc 100644 --- a/public/translations/zh/default.json +++ b/public/translations/zh/default.json @@ -293,5 +293,8 @@ "this_reply_was_removed": "该回复已被版主删除", "not_mod_of_any_board": "您不是任何板块的管理员。", "filters_and_highlights": "筛选器和高亮", - "search_results_for": "搜索结果" + "search_results_for": "搜索结果", + "help": "帮助", + "filter_and_highlights_help": "过滤器和高亮帮助", + "filter_and_highlights": "筛选器与高亮" } \ No newline at end of file diff --git a/src/components/catalog-filters/catalog-filters.module.css b/src/components/catalog-filters/catalog-filters.module.css index 52aecd44..48302ecf 100644 --- a/src/components/catalog-filters/catalog-filters.module.css +++ b/src/components/catalog-filters/catalog-filters.module.css @@ -33,7 +33,7 @@ .modal { top: 60px; - left: 50%; + left: calc(50% + 5px); transform: translateX(-50%); width: 380px; height: auto; @@ -41,14 +41,23 @@ overflow-y: auto; position: fixed; z-index: 5; - padding: 2px; + padding: 2px 5px 5px 5px; font-size: var(--settings-modal-font-size); box-shadow: 0 0 5px rgba(0, 0, 0, 0.25); background-color: var(--settings-modal-background-color); - border-top: var(--settings-modal-border-top); - border-right: var(--settings-modal-border-right); - border-bottom: var(--settings-modal-border-bottom); - border-left: var(--settings-modal-border-left); +} + +.filtersProtipModal { + top: 50px; + width: 570px; + left: 50%; + padding: 2px 10px 5px 10px; +} + +@media (max-width: 614px) { + .filtersProtipModal { + width: 90vw; + } } @media (max-width: 420px) { @@ -82,6 +91,54 @@ top: 5px; image-rendering: pixelated; background-image: var(--settings-modal-close-button-background-image); + cursor: pointer; +} + +.openHelpButton { + right: 23px; + width: 18px; + height: 18px; + display: block; + background-size: 100%; + position: absolute; + top: 5px; + image-rendering: pixelated; + background-image: var(--open-help-button-background-image); + cursor: pointer; +} + +.filtersProtip { + text-transform: none; +} + +.filtersProtip code { + padding: 1px 5px 1px 5px; + background-color: #EEE; + color: #000; +} + +.filtersProtip li { + list-style: none; + margin: 5px 0px; +} + +.filtersProtip ul { + margin-bottom: 10px; +} + +.filtersProtip h4 { + font-size: 15px; + margin: 10px 0px 5px 0px; + padding-top: 10px; +} + +.filtersProtip h4:first-child { + padding-top: 0px; +} + +.filtersProtip h4::before { + content: "»"; + margin-right: 3px; } .filters { @@ -108,16 +165,12 @@ padding-bottom: 5px; } -.saveButton { - margin-left: 5px; -} - .filtersTable { width: 100%; border-spacing: 1px; margin-left: auto; margin-right: auto; - padding: 10px 2px 2px 2px; + padding: 0px 2px 2px 2px; } .filtersTable th { @@ -168,7 +221,8 @@ } .saveButton { - float: right; + position: absolute; + right: 5px; } .filterHits { diff --git a/src/components/catalog-filters/catalog-filters.tsx b/src/components/catalog-filters/catalog-filters.tsx index 257efded..5f662262 100644 --- a/src/components/catalog-filters/catalog-filters.tsx +++ b/src/components/catalog-filters/catalog-filters.tsx @@ -1,11 +1,13 @@ import { useState, useCallback, useRef, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import useCatalogFiltersStore from '../../stores/use-catalog-filters-store'; +import useFeedResetStore from '../../stores/use-feed-reset-store'; import styles from './catalog-filters.module.css'; const FiltersTable = ({ onSave }: { onSave: () => void }) => { const { t } = useTranslation(); const { filterItems, saveAndApplyFilters, currentSubplebbitAddress } = useCatalogFiltersStore(); + const resetFeed = useFeedResetStore((state) => state.reset); const [localFilterItems, setLocalFilterItems] = useState( filterItems.map((item) => ({ @@ -52,9 +54,17 @@ const FiltersTable = ({ onSave }: { onSave: () => void }) => { const handleSave = useCallback(() => { const nonEmptyFilters = localFilterItems.filter((item) => item.text.trim() !== ''); + saveAndApplyFilters(nonEmptyFilters); + + useCatalogFiltersStore.getState().resetCountsForCurrentSubplebbit(); + + if (resetFeed) { + resetFeed(); + } + onSave(); - }, [saveAndApplyFilters, localFilterItems, onSave]); + }, [saveAndApplyFilters, localFilterItems, onSave, resetFeed]); const handleKeyDown = useCallback( (e: KeyboardEvent) => { @@ -142,7 +152,7 @@ const FiltersTable = ({ onSave }: { onSave: () => void }) => { - {currentSubplebbitAddress && (item.subplebbitCounts?.get(currentSubplebbitAddress) ?? 0) > 0 && `x${item.subplebbitCounts?.get(currentSubplebbitAddress)}`} + {currentSubplebbitAddress && item.subplebbitFilteredCids?.has(currentSubplebbitAddress) && `x${item.subplebbitCounts?.get(currentSubplebbitAddress) ?? 0}`} ))} @@ -163,18 +173,107 @@ const FiltersTable = ({ onSave }: { onSave: () => void }) => { ); }; +const FiltersProtip = () => { + return ( +
+

Patterns

+ + + + + + + +

Controls

+ +
+ ); +}; + const FiltersModal = ({ closeModal }: { closeModal: () => void }) => { const { t } = useTranslation(); - + const [showHelp, setShowHelp] = useState(false); + const openHelp = () => setShowHelp(true); + const closeHelp = () => setShowHelp(false); return ( <> -
-
+
+
- {t('filters_and_highlights')} - + {showHelp ? t('filter_and_highlights_help') : t('filter_and_highlights')} + {!showHelp && } +
- + {showHelp ? : }
); diff --git a/src/lib/utils/pattern-utils.ts b/src/lib/utils/pattern-utils.ts new file mode 100644 index 00000000..81484791 --- /dev/null +++ b/src/lib/utils/pattern-utils.ts @@ -0,0 +1,110 @@ +import { Comment } from '@plebbit/plebbit-react-hooks'; + +/** + * Checks if a text matches a pattern according to various pattern matching rules: + * - Whole word matching: 'feel' matches 'feel' but not 'feeling' + * - AND operator: 'feel girlfriend' matches both words in any order + * - OR operator: 'feel|girlfriend' matches either word + * - Mixed operators: 'girlfriend|boyfriend feel' matches complex logic + * - Exact match: "that feel when" matches the exact phrase + * - Wildcards: 'feel*' matches 'feel', 'feels', 'feeling', etc. + * - Regex: /pattern/flags for regular expressions + * + * @param text The text to check against the pattern + * @param pattern The pattern to match + * @returns True if the text matches the pattern, false otherwise + */ +export const matchesPattern = (text: string, pattern: string): boolean => { + if (!pattern || !text) return false; + + const textLower = text.toLowerCase(); + + try { + // Check if it's a regex pattern (starts and ends with /) + if (pattern.startsWith('/') && pattern.length > 2 && /\/[gimsuy]*$/.test(pattern)) { + const lastSlashIndex = pattern.lastIndexOf('/'); + const regexPattern = pattern.substring(1, lastSlashIndex); + const flags = pattern.substring(lastSlashIndex + 1); + const regex = new RegExp(regexPattern, flags); + return regex.test(textLower); + } + // Check if it's an exact match pattern (surrounded by quotes) + else if (pattern.startsWith('"') && pattern.endsWith('"') && pattern.length > 2) { + const exactPattern = pattern.substring(1, pattern.length - 1).toLowerCase(); + return textLower.includes(exactPattern); + } + // Check for OR patterns (contains |) + else if (pattern.includes('|')) { + const orGroups = pattern.split(' ').filter(Boolean); + + // Process each group (which might contain OR operators) + return orGroups.every((group) => { + const orTerms = group.split('|').map((term) => term.toLowerCase()); + return orTerms.some((term) => { + // Handle wildcards in OR terms + if (term.includes('*')) { + const regexPattern = term.replace(/\*/g, '.*').toLowerCase(); + const regex = new RegExp(`\\b${regexPattern}\\b`, 'i'); + return regex.test(textLower); + } else { + // Match whole word only + const regex = new RegExp(`\\b${term}\\b`, 'i'); + return regex.test(textLower); + } + }); + }); + } + // Check for AND patterns (space-separated words) + else if (pattern.includes(' ')) { + const andTerms = pattern + .split(' ') + .filter(Boolean) + .map((term) => term.toLowerCase()); + return andTerms.every((term) => { + // Handle wildcards in AND terms + if (term.includes('*')) { + const regexPattern = term.replace(/\*/g, '.*').toLowerCase(); + const regex = new RegExp(`\\b${regexPattern}\\b`, 'i'); + return regex.test(textLower); + } else { + // Match whole word only + const regex = new RegExp(`\\b${term}\\b`, 'i'); + return regex.test(textLower); + } + }); + } + // Handle wildcard patterns + else if (pattern.includes('*')) { + const regexPattern = pattern.replace(/\*/g, '.*').toLowerCase(); + const regex = new RegExp(`\\b${regexPattern}\\b`, 'i'); + return regex.test(textLower); + } + // Simple whole word match + else { + const regex = new RegExp(`\\b${pattern.toLowerCase()}\\b`, 'i'); + return regex.test(textLower); + } + } catch (error) { + // If regex parsing fails, fall back to simple includes + console.error('Pattern matching error:', error); + const simplifiedPattern = pattern.toLowerCase(); + return textLower.includes(simplifiedPattern); + } +}; + +/** + * Checks if a comment matches a pattern in its title or content + * + * @param comment The comment to check + * @param pattern The pattern to match + * @returns True if the comment matches the pattern, false otherwise + */ +export const commentMatchesPattern = (comment: Comment, pattern: string): boolean => { + if (!pattern || !comment) return false; + + const titleLower = comment?.title?.toLowerCase() || ''; + const contentLower = comment?.content?.toLowerCase() || ''; + const textToMatch = titleLower + ' ' + contentLower; + + return matchesPattern(textToMatch, pattern); +}; diff --git a/src/stores/use-catalog-filters-store.ts b/src/stores/use-catalog-filters-store.ts index f4f106bc..c75072d7 100644 --- a/src/stores/use-catalog-filters-store.ts +++ b/src/stores/use-catalog-filters-store.ts @@ -1,6 +1,7 @@ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import { Comment } from '@plebbit/plebbit-react-hooks'; +import { commentMatchesPattern } from '../lib/utils/pattern-utils'; interface FilterItem { text: string; @@ -34,6 +35,7 @@ interface CatalogFiltersStore { searchText: string; setSearchFilter: (text: string) => void; clearSearchFilter: () => void; + resetCountsForCurrentSubplebbit: () => void; } const useCatalogFiltersStore = create( @@ -137,10 +139,39 @@ const useCatalogFiltersStore = create( hide: item.hide ?? true, top: item.top ?? false, })); + + // Compare new filter items with existing ones to detect pattern changes + const existingItems = get().filterItems; + const updatedItems = nonEmptyItems.map((newItem) => { + // Try to find matching existing item by index or text + const existingItem = existingItems.find((item) => item.text === newItem.text); + + // If we found a matching item, keep its counts + if (existingItem) { + return { + ...newItem, + count: existingItem.count, + filteredCids: existingItem.filteredCids, + subplebbitCounts: existingItem.subplebbitCounts, + subplebbitFilteredCids: existingItem.subplebbitFilteredCids, + }; + } + + // If pattern has changed or it's a new filter, reset all counts + return { + ...newItem, + count: 0, + filteredCids: new Set(), + subplebbitCounts: new Map(), + subplebbitFilteredCids: new Map>(), + }; + }); + set({ - filterItems: nonEmptyItems, + filterItems: updatedItems, filteredCids: new Set(), }); + get().recalcFilteredCount(); get().updateFilter(); }, @@ -154,11 +185,7 @@ const useCatalogFiltersStore = create( // Apply search filter if (state.searchText.trim() !== '') { - const searchPattern = state.searchText.toLowerCase(); - const title = comment?.title?.toLowerCase() || ''; - const content = comment?.content?.toLowerCase() || ''; - - if (!title.includes(searchPattern) && !content.includes(searchPattern)) { + if (!commentMatchesPattern(comment, state.searchText)) { return false; } } @@ -170,11 +197,7 @@ const useCatalogFiltersStore = create( for (let i = 0; i < filterItems.length; i++) { const item = filterItems[i]; if (item.enabled && item.text.trim() !== '') { - const pattern = item.text.toLowerCase(); - const title = comment?.title?.toLowerCase() || ''; - const content = comment?.content?.toLowerCase() || ''; - - if (title.includes(pattern) || content.includes(pattern)) { + if (commentMatchesPattern(comment, item.text)) { // If we have a current subplebbit and this is a match, increment the count if (currentSubplebbit && comment.subplebbitAddress === currentSubplebbit) { // We need to use a timeout to avoid modifying state during a state update @@ -272,6 +295,38 @@ const useCatalogFiltersStore = create( return filteredCount; }, + resetCountsForCurrentSubplebbit: () => { + const currentSubplebbit = get().currentSubplebbitAddress; + if (!currentSubplebbit) return; + + set((state) => { + const updatedFilterItems = state.filterItems.map((item) => { + const newItem = { ...item }; + + // Ensure maps are properly initialized + if (!newItem.subplebbitCounts || !(newItem.subplebbitCounts instanceof Map)) { + newItem.subplebbitCounts = new Map(); + } + if (!newItem.subplebbitFilteredCids || !(newItem.subplebbitFilteredCids instanceof Map)) { + newItem.subplebbitFilteredCids = new Map(); + } + + // Reset counts for current subplebbit + newItem.subplebbitCounts.set(currentSubplebbit, 0); + newItem.subplebbitFilteredCids.set(currentSubplebbit, new Set()); + + return newItem; + }); + + return { + filterItems: updatedFilterItems, + filteredCount: 0, + }; + }); + + // Trigger filter reapplication to start counting again + get().updateFilter(); + }, }), { name: 'catalog-filters-storage', diff --git a/src/themes.css b/src/themes.css index f25059a3..93e6b01c 100644 --- a/src/themes.css +++ b/src/themes.css @@ -50,6 +50,7 @@ --button-desktop-text-color: #00e; --button-desktop-text-color-hover: red; --close-button-background-image: url("/assets/buttons/cross-red.png"); + --open-help-button-background-image: url("/assets/buttons/help-red.png"); /* horizontal rule */ --hr-border: none; @@ -263,6 +264,7 @@ --button-desktop-text-color: #34345c; --button-desktop-text-color-hover: #d00; --close-button-background-image: url("/assets/buttons/cross-blue.png"); + --open-help-button-background-image: url("/assets/buttons/help-blue.png"); /* footer */ --footer-item-background-color-desktop: #eef2ff; @@ -479,6 +481,7 @@ --button-desktop-text-color-hover: red; --button-text-decoration: underline; --close-button-background-image: url("/assets/buttons/cross-red.png"); + --open-help-button-background-image: url("/assets/buttons/help-red.png"); /* footer */ --footer-item-background-color-desktop: #fed; @@ -657,7 +660,7 @@ --button-text-decoration: underline; --button-desktop-text-color-hover: #d00; --close-button-background-image: url("/assets/buttons/cross-blue.png"); - + --open-help-button-background-image: url("/assets/buttons/help-blue.png"); /* footer */ --footer-item-background-color-desktop: #eef2ff; @@ -848,7 +851,7 @@ --button-desktop-text-color: #81a2be; --button-desktop-text-color-hover: #5f89ac; --close-button-background-image: url("/assets/buttons/cross-dark.png"); - + --open-help-button-background-image: url("/assets/buttons/help-dark.png"); /* filter for inputs */ --filter80: brightness(85%); @@ -1067,7 +1070,7 @@ --button-desktop-text-color: #f60; --button-desktop-text-color-hover: #f30; --close-button-background-image: url("/assets/buttons/cross-photon.png"); - + --open-help-button-background-image: url("/assets/buttons/help-photon.png"); /* horizontal rule */ --hr-border: none; --hr-border-top: 1px solid #ddd; diff --git a/src/views/catalog/catalog.tsx b/src/views/catalog/catalog.tsx index 28a5d2f3..97ab0714 100644 --- a/src/views/catalog/catalog.tsx +++ b/src/views/catalog/catalog.tsx @@ -18,6 +18,7 @@ import useCatalogFiltersStore from '../../stores/use-catalog-filters-store'; import CatalogRow from '../../components/catalog-row'; import LoadingEllipsis from '../../components/loading-ellipsis'; import styles from './catalog.module.css'; +import { commentMatchesPattern } from '../../lib/utils/pattern-utils'; const lastVirtuosoStates: { [key: string]: StateSnapshot } = {}; @@ -39,15 +40,12 @@ const createContentFilter = ( 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(); + const pattern = item.text; - if (titleLower.includes(pattern) || contentLower.includes(pattern)) { + if (commentMatchesPattern(comment, pattern)) { // Find the original filter index to increment count const filterIndex = filterItems.findIndex((f) => f.text === item.text && f.enabled); if (filterIndex !== -1) { @@ -102,12 +100,7 @@ const createCombinedFilter = ( const searchFilter = { filter: (comment: Comment) => { if (!searchText.trim()) return true; - - const titleLower = comment?.title?.toLowerCase() || ''; - const contentLower = comment?.content?.toLowerCase() || ''; - const searchPattern = searchText.toLowerCase(); - - return titleLower.includes(searchPattern) || contentLower.includes(searchPattern); + return commentMatchesPattern(comment, searchText); }, key: searchText ? `search-filter-${searchText}` : 'no-search-filter', }; @@ -410,13 +403,9 @@ const Catalog = () => { 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)) { + if (commentMatchesPattern(comment, filter.text)) { isTop = true; break; }