Merge branch 'codex/fix/catalog-search-empty-state'

This commit is contained in:
Tommaso Casaburi
2026-05-19 15:38:52 +07:00
43 changed files with 229 additions and 110 deletions
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "افتح الملف الأصلي", "media_failed_to_load_open_source": "افتح الملف الأصلي",
"media_failed_to_load_inline": "فشلت الصورة الخارجية من {{host}}. جرّب VPN.", "media_failed_to_load_inline": "فشلت الصورة الخارجية من {{host}}. جرّب VPN.",
"media_failed_to_load_inline_unknown": "فشلت الصورة الخارجية. جرّب VPN.", "media_failed_to_load_inline_unknown": "فشلت الصورة الخارجية. جرّب VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "মূল ফাইল খুলুন", "media_failed_to_load_open_source": "মূল ফাইল খুলুন",
"media_failed_to_load_inline": "{{host}} থেকে বাইরের ছবি লোড হয়নি। VPN চেষ্টা করুন।", "media_failed_to_load_inline": "{{host}} থেকে বাইরের ছবি লোড হয়নি। VPN চেষ্টা করুন।",
"media_failed_to_load_inline_unknown": "বাইরের ছবি লোড হয়নি। VPN চেষ্টা করুন।", "media_failed_to_load_inline_unknown": "বাইরের ছবি লোড হয়নি। VPN চেষ্টা করুন।",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Otevřít původní soubor", "media_failed_to_load_open_source": "Otevřít původní soubor",
"media_failed_to_load_inline": "Externí obrázek z {{host}} selhal. Zkuste VPN.", "media_failed_to_load_inline": "Externí obrázek z {{host}} selhal. Zkuste VPN.",
"media_failed_to_load_inline_unknown": "Externí obrázek selhal. Zkuste VPN.", "media_failed_to_load_inline_unknown": "Externí obrázek selhal. Zkuste VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Åbn originalfil", "media_failed_to_load_open_source": "Åbn originalfil",
"media_failed_to_load_inline": "Eksternt billede fra {{host}} fejlede. Prøv VPN.", "media_failed_to_load_inline": "Eksternt billede fra {{host}} fejlede. Prøv VPN.",
"media_failed_to_load_inline_unknown": "Eksternt billede fejlede. Prøv VPN.", "media_failed_to_load_inline_unknown": "Eksternt billede fejlede. Prøv VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Originaldatei öffnen", "media_failed_to_load_open_source": "Originaldatei öffnen",
"media_failed_to_load_inline": "Externes Bild von {{host}} fehlgeschlagen. VPN versuchen.", "media_failed_to_load_inline": "Externes Bild von {{host}} fehlgeschlagen. VPN versuchen.",
"media_failed_to_load_inline_unknown": "Externes Bild fehlgeschlagen. VPN versuchen.", "media_failed_to_load_inline_unknown": "Externes Bild fehlgeschlagen. VPN versuchen.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Άνοιγμα αρχικού αρχείου", "media_failed_to_load_open_source": "Άνοιγμα αρχικού αρχείου",
"media_failed_to_load_inline": "Η εξωτερική εικόνα από {{host}} απέτυχε. Δοκιμάστε VPN.", "media_failed_to_load_inline": "Η εξωτερική εικόνα από {{host}} απέτυχε. Δοκιμάστε VPN.",
"media_failed_to_load_inline_unknown": "Η εξωτερική εικόνα απέτυχε. Δοκιμάστε VPN.", "media_failed_to_load_inline_unknown": "Η εξωτερική εικόνα απέτυχε. Δοκιμάστε VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+1
View File
@@ -140,6 +140,7 @@
"sort_by": "Sort by", "sort_by": "Sort by",
"posts_last_synced_info": "Posts last synced {{time}}, this board might be offline and publishing might fail.", "posts_last_synced_info": "Posts last synced {{time}}, this board might be offline and publishing might fail.",
"no_threads": "No threads", "no_threads": "No threads",
"nothing_found": "Nothing Found",
"hidden": "hidden", "hidden": "hidden",
"save_options": "Save Options", "save_options": "Save Options",
"save_advanced_settings": "Save Advanced Settings", "save_advanced_settings": "Save Advanced Settings",
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Abrir archivo original", "media_failed_to_load_open_source": "Abrir archivo original",
"media_failed_to_load_inline": "Falló la imagen externa de {{host}}. Prueba una VPN.", "media_failed_to_load_inline": "Falló la imagen externa de {{host}}. Prueba una VPN.",
"media_failed_to_load_inline_unknown": "Falló la imagen externa. Prueba una VPN.", "media_failed_to_load_inline_unknown": "Falló la imagen externa. Prueba una VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "باز کردن فایل اصلی", "media_failed_to_load_open_source": "باز کردن فایل اصلی",
"media_failed_to_load_inline": "تصویر خارجی از {{host}} بارگیری نشد. VPN را امتحان کنید.", "media_failed_to_load_inline": "تصویر خارجی از {{host}} بارگیری نشد. VPN را امتحان کنید.",
"media_failed_to_load_inline_unknown": "تصویر خارجی بارگیری نشد. VPN را امتحان کنید.", "media_failed_to_load_inline_unknown": "تصویر خارجی بارگیری نشد. VPN را امتحان کنید.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Avaa alkuperäinen tiedosto", "media_failed_to_load_open_source": "Avaa alkuperäinen tiedosto",
"media_failed_to_load_inline": "Ulkoinen kuva sivustolta {{host}} epäonnistui. Kokeile VPN:ää.", "media_failed_to_load_inline": "Ulkoinen kuva sivustolta {{host}} epäonnistui. Kokeile VPN:ää.",
"media_failed_to_load_inline_unknown": "Ulkoinen kuva epäonnistui. Kokeile VPN:ää.", "media_failed_to_load_inline_unknown": "Ulkoinen kuva epäonnistui. Kokeile VPN:ää.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Buksan ang orihinal na file", "media_failed_to_load_open_source": "Buksan ang orihinal na file",
"media_failed_to_load_inline": "Nabigo ang panlabas na larawan mula sa {{host}}. Subukan ang VPN.", "media_failed_to_load_inline": "Nabigo ang panlabas na larawan mula sa {{host}}. Subukan ang VPN.",
"media_failed_to_load_inline_unknown": "Nabigo ang panlabas na larawan. Subukan ang VPN.", "media_failed_to_load_inline_unknown": "Nabigo ang panlabas na larawan. Subukan ang VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Ouvrir le fichier original", "media_failed_to_load_open_source": "Ouvrir le fichier original",
"media_failed_to_load_inline": "L'image externe de {{host}} a échoué. Essayez un VPN.", "media_failed_to_load_inline": "L'image externe de {{host}} a échoué. Essayez un VPN.",
"media_failed_to_load_inline_unknown": "L'image externe a échoué. Essayez un VPN.", "media_failed_to_load_inline_unknown": "L'image externe a échoué. Essayez un VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "פתח את הקובץ המקורי", "media_failed_to_load_open_source": "פתח את הקובץ המקורי",
"media_failed_to_load_inline": "התמונה החיצונית מ-{{host}} נכשלה. נסה VPN.", "media_failed_to_load_inline": "התמונה החיצונית מ-{{host}} נכשלה. נסה VPN.",
"media_failed_to_load_inline_unknown": "התמונה החיצונית נכשלה. נסה VPN.", "media_failed_to_load_inline_unknown": "התמונה החיצונית נכשלה. נסה VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "मूल फ़ाइल खोलें", "media_failed_to_load_open_source": "मूल फ़ाइल खोलें",
"media_failed_to_load_inline": "{{host}} से बाहरी छवि लोड नहीं हुई। VPN आज़माएँ।", "media_failed_to_load_inline": "{{host}} से बाहरी छवि लोड नहीं हुई। VPN आज़माएँ।",
"media_failed_to_load_inline_unknown": "बाहरी छवि लोड नहीं हुई। VPN आज़माएँ।", "media_failed_to_load_inline_unknown": "बाहरी छवि लोड नहीं हुई। VPN आज़माएँ।",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Eredeti fájl megnyitása", "media_failed_to_load_open_source": "Eredeti fájl megnyitása",
"media_failed_to_load_inline": "A külső kép innen nem töltődött be: {{host}}. Próbálj VPN-t.", "media_failed_to_load_inline": "A külső kép innen nem töltődött be: {{host}}. Próbálj VPN-t.",
"media_failed_to_load_inline_unknown": "A külső kép nem töltődött be. Próbálj VPN-t.", "media_failed_to_load_inline_unknown": "A külső kép nem töltődött be. Próbálj VPN-t.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Buka file asli", "media_failed_to_load_open_source": "Buka file asli",
"media_failed_to_load_inline": "Gambar eksternal dari {{host}} gagal. Coba VPN.", "media_failed_to_load_inline": "Gambar eksternal dari {{host}} gagal. Coba VPN.",
"media_failed_to_load_inline_unknown": "Gambar eksternal gagal. Coba VPN.", "media_failed_to_load_inline_unknown": "Gambar eksternal gagal. Coba VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Apri file originale", "media_failed_to_load_open_source": "Apri file originale",
"media_failed_to_load_inline": "Immagine esterna da {{host}} non caricata. Prova una VPN.", "media_failed_to_load_inline": "Immagine esterna da {{host}} non caricata. Prova una VPN.",
"media_failed_to_load_inline_unknown": "Immagine esterna non caricata. Prova una VPN.", "media_failed_to_load_inline_unknown": "Immagine esterna non caricata. Prova una VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "元のファイルを開く", "media_failed_to_load_open_source": "元のファイルを開く",
"media_failed_to_load_inline": "{{host}} の外部画像を読み込めませんでした。VPN を試してください。", "media_failed_to_load_inline": "{{host}} の外部画像を読み込めませんでした。VPN を試してください。",
"media_failed_to_load_inline_unknown": "外部画像を読み込めませんでした。VPN を試してください。", "media_failed_to_load_inline_unknown": "外部画像を読み込めませんでした。VPN を試してください。",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "원본 파일 열기", "media_failed_to_load_open_source": "원본 파일 열기",
"media_failed_to_load_inline": "{{host}}의 외부 이미지를 불러오지 못했습니다. VPN을 시도하세요.", "media_failed_to_load_inline": "{{host}}의 외부 이미지를 불러오지 못했습니다. VPN을 시도하세요.",
"media_failed_to_load_inline_unknown": "외부 이미지를 불러오지 못했습니다. VPN을 시도하세요.", "media_failed_to_load_inline_unknown": "외부 이미지를 불러오지 못했습니다. VPN을 시도하세요.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "मूळ फाइल उघडा", "media_failed_to_load_open_source": "मूळ फाइल उघडा",
"media_failed_to_load_inline": "{{host}} वरील बाह्य प्रतिमा लोड झाली नाही. VPN वापरून पाहा.", "media_failed_to_load_inline": "{{host}} वरील बाह्य प्रतिमा लोड झाली नाही. VPN वापरून पाहा.",
"media_failed_to_load_inline_unknown": "बाह्य प्रतिमा लोड झाली नाही. VPN वापरून पाहा.", "media_failed_to_load_inline_unknown": "बाह्य प्रतिमा लोड झाली नाही. VPN वापरून पाहा.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Origineel bestand openen", "media_failed_to_load_open_source": "Origineel bestand openen",
"media_failed_to_load_inline": "Externe afbeelding van {{host}} mislukt. Probeer VPN.", "media_failed_to_load_inline": "Externe afbeelding van {{host}} mislukt. Probeer VPN.",
"media_failed_to_load_inline_unknown": "Externe afbeelding mislukt. Probeer VPN.", "media_failed_to_load_inline_unknown": "Externe afbeelding mislukt. Probeer VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Åpne originalfil", "media_failed_to_load_open_source": "Åpne originalfil",
"media_failed_to_load_inline": "Eksternt bilde fra {{host}} kunne ikke lastes. Prøv VPN.", "media_failed_to_load_inline": "Eksternt bilde fra {{host}} kunne ikke lastes. Prøv VPN.",
"media_failed_to_load_inline_unknown": "Eksternt bilde kunne ikke lastes. Prøv VPN.", "media_failed_to_load_inline_unknown": "Eksternt bilde kunne ikke lastes. Prøv VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Otwórz oryginalny plik", "media_failed_to_load_open_source": "Otwórz oryginalny plik",
"media_failed_to_load_inline": "Obraz zewnętrzny z {{host}} nie załadował się. Spróbuj VPN.", "media_failed_to_load_inline": "Obraz zewnętrzny z {{host}} nie załadował się. Spróbuj VPN.",
"media_failed_to_load_inline_unknown": "Obraz zewnętrzny nie załadował się. Spróbuj VPN.", "media_failed_to_load_inline_unknown": "Obraz zewnętrzny nie załadował się. Spróbuj VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Abrir arquivo original", "media_failed_to_load_open_source": "Abrir arquivo original",
"media_failed_to_load_inline": "Falha na imagem externa de {{host}}. Tente uma VPN.", "media_failed_to_load_inline": "Falha na imagem externa de {{host}}. Tente uma VPN.",
"media_failed_to_load_inline_unknown": "Falha na imagem externa. Tente uma VPN.", "media_failed_to_load_inline_unknown": "Falha na imagem externa. Tente uma VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Deschide fișierul original", "media_failed_to_load_open_source": "Deschide fișierul original",
"media_failed_to_load_inline": "Imaginea externă de la {{host}} a eșuat. Încearcă VPN.", "media_failed_to_load_inline": "Imaginea externă de la {{host}} a eșuat. Încearcă VPN.",
"media_failed_to_load_inline_unknown": "Imaginea externă a eșuat. Încearcă VPN.", "media_failed_to_load_inline_unknown": "Imaginea externă a eșuat. Încearcă VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Открыть исходный файл", "media_failed_to_load_open_source": "Открыть исходный файл",
"media_failed_to_load_inline": "Внешнее изображение с {{host}} не загрузилось. Попробуйте VPN.", "media_failed_to_load_inline": "Внешнее изображение с {{host}} не загрузилось. Попробуйте VPN.",
"media_failed_to_load_inline_unknown": "Внешнее изображение не загрузилось. Попробуйте VPN.", "media_failed_to_load_inline_unknown": "Внешнее изображение не загрузилось. Попробуйте VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Hap skedarin origjinal", "media_failed_to_load_open_source": "Hap skedarin origjinal",
"media_failed_to_load_inline": "Imazhi i jashtëm nga {{host}} dështoi. Provoni VPN.", "media_failed_to_load_inline": "Imazhi i jashtëm nga {{host}} dështoi. Provoni VPN.",
"media_failed_to_load_inline_unknown": "Imazhi i jashtëm dështoi. Provoni VPN.", "media_failed_to_load_inline_unknown": "Imazhi i jashtëm dështoi. Provoni VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Öppna originalfil", "media_failed_to_load_open_source": "Öppna originalfil",
"media_failed_to_load_inline": "Extern bild från {{host}} kunde inte läsas in. Prova VPN.", "media_failed_to_load_inline": "Extern bild från {{host}} kunde inte läsas in. Prova VPN.",
"media_failed_to_load_inline_unknown": "Extern bild kunde inte läsas in. Prova VPN.", "media_failed_to_load_inline_unknown": "Extern bild kunde inte läsas in. Prova VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "అసలు ఫైల్‌ను తెరవండి", "media_failed_to_load_open_source": "అసలు ఫైల్‌ను తెరవండి",
"media_failed_to_load_inline": "{{host}} నుండి బాహ్య చిత్రం లోడ్ కాలేదు. VPN ప్రయత్నించండి.", "media_failed_to_load_inline": "{{host}} నుండి బాహ్య చిత్రం లోడ్ కాలేదు. VPN ప్రయత్నించండి.",
"media_failed_to_load_inline_unknown": "బాహ్య చిత్రం లోడ్ కాలేదు. VPN ప్రయత్నించండి.", "media_failed_to_load_inline_unknown": "బాహ్య చిత్రం లోడ్ కాలేదు. VPN ప్రయత్నించండి.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "เปิดไฟล์ต้นฉบับ", "media_failed_to_load_open_source": "เปิดไฟล์ต้นฉบับ",
"media_failed_to_load_inline": "โหลดรูปภาพภายนอกจาก {{host}} ไม่สำเร็จ ลอง VPN", "media_failed_to_load_inline": "โหลดรูปภาพภายนอกจาก {{host}} ไม่สำเร็จ ลอง VPN",
"media_failed_to_load_inline_unknown": "โหลดรูปภาพภายนอกไม่สำเร็จ ลอง VPN", "media_failed_to_load_inline_unknown": "โหลดรูปภาพภายนอกไม่สำเร็จ ลอง VPN",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Orijinal dosyayı aç", "media_failed_to_load_open_source": "Orijinal dosyayı aç",
"media_failed_to_load_inline": "{{host}} harici görseli yüklenemedi. VPN deneyin.", "media_failed_to_load_inline": "{{host}} harici görseli yüklenemedi. VPN deneyin.",
"media_failed_to_load_inline_unknown": "Harici görsel yüklenemedi. VPN deneyin.", "media_failed_to_load_inline_unknown": "Harici görsel yüklenemedi. VPN deneyin.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Відкрити оригінальний файл", "media_failed_to_load_open_source": "Відкрити оригінальний файл",
"media_failed_to_load_inline": "Зовнішнє зображення з {{host}} не завантажилося. Спробуйте VPN.", "media_failed_to_load_inline": "Зовнішнє зображення з {{host}} не завантажилося. Спробуйте VPN.",
"media_failed_to_load_inline_unknown": "Зовнішнє зображення не завантажилося. Спробуйте VPN.", "media_failed_to_load_inline_unknown": "Зовнішнє зображення не завантажилося. Спробуйте VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "اصل فائل کھولیں", "media_failed_to_load_open_source": "اصل فائل کھولیں",
"media_failed_to_load_inline": "{{host}} سے بیرونی تصویر لوڈ نہیں ہوئی۔ VPN آزمائیں۔", "media_failed_to_load_inline": "{{host}} سے بیرونی تصویر لوڈ نہیں ہوئی۔ VPN آزمائیں۔",
"media_failed_to_load_inline_unknown": "بیرونی تصویر لوڈ نہیں ہوئی۔ VPN آزمائیں۔", "media_failed_to_load_inline_unknown": "بیرونی تصویر لوڈ نہیں ہوئی۔ VPN آزمائیں۔",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "Mở tệp gốc", "media_failed_to_load_open_source": "Mở tệp gốc",
"media_failed_to_load_inline": "Không tải được ảnh bên ngoài từ {{host}}. Thử VPN.", "media_failed_to_load_inline": "Không tải được ảnh bên ngoài từ {{host}}. Thử VPN.",
"media_failed_to_load_inline_unknown": "Không tải được ảnh bên ngoài. Thử VPN.", "media_failed_to_load_inline_unknown": "Không tải được ảnh bên ngoài. Thử VPN.",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+2 -1
View File
@@ -388,5 +388,6 @@
"media_failed_to_load_open_source": "打开原始文件", "media_failed_to_load_open_source": "打开原始文件",
"media_failed_to_load_inline": "来自 {{host}} 的外部图片加载失败。试试 VPN。", "media_failed_to_load_inline": "来自 {{host}} 的外部图片加载失败。试试 VPN。",
"media_failed_to_load_inline_unknown": "外部图片加载失败。试试 VPN。", "media_failed_to_load_inline_unknown": "外部图片加载失败。试试 VPN。",
"expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead." "expiring_media_link_alert": "Media from {{domain}} expires quickly. Use a stable media link instead.",
"nothing_found": "Nothing Found"
} }
+27 -29
View File
@@ -523,7 +523,6 @@ export const MobileBoardButtons = () => {
const resolvedAddress = useResolvedCommunityAddress(); const resolvedAddress = useResolvedCommunityAddress();
const communityAddress = resolvedAddress || accountComment?.communityAddress; const communityAddress = resolvedAddress || accountComment?.communityAddress;
const { filteredCount, searchText } = useCatalogFiltersStore();
const enableInfiniteScroll = useFeedViewSettingsStore((state) => state.enableInfiniteScroll); const enableInfiniteScroll = useFeedViewSettingsStore((state) => state.enableInfiniteScroll);
const isMultiboard = isInAllView || isInSubscriptionsView || isInModView; const isMultiboard = isInAllView || isInSubscriptionsView || isInModView;
const showTimeFilter = isMultiboard; const showTimeFilter = isMultiboard;
@@ -574,19 +573,7 @@ export const MobileBoardButtons = () => {
isInModView={isInModView} isInModView={isInModView}
isMobilePlacement={true} isMobilePlacement={true}
/> />
{searchText ? ( <CatalogSearchResultsLabel />
<span className={styles.filteredThreadsCount}>
{' '}
- {t('search_results_for')}: <strong>{searchText}</strong>
</span>
) : (
filteredCount > 0 && (
<span className={styles.filteredThreadsCount}>
{' '}
- {t('filtered_threads')}: <strong>{filteredCount}</strong>
</span>
)
)}
{(isInAllView || showTimeFilter || isInCatalogView) && ( {(isInAllView || showTimeFilter || isInCatalogView) && (
<> <>
<hr /> <hr />
@@ -683,6 +670,31 @@ export const PostPageStats = () => {
); );
}; };
export const CatalogSearchResultsLabel = () => {
const { t } = useTranslation();
const { filteredCount, searchText } = useCatalogFiltersStore();
if (searchText) {
return (
<span className={styles.filteredThreadsCount}>
{' '}
{t('search_results_for')}: <strong>{searchText}</strong>
</span>
);
}
if (filteredCount > 0) {
return (
<span className={styles.filteredThreadsCount}>
{' '}
{t('filtered_threads')}: <strong>{filteredCount}</strong>
</span>
);
}
return null;
};
export const DesktopBoardButtons = () => { export const DesktopBoardButtons = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const params = useParams(); const params = useParams();
@@ -698,7 +710,6 @@ export const DesktopBoardButtons = () => {
const isInModView = isModView(location.pathname); const isInModView = isModView(location.pathname);
const isInModQueueView = isModQueueView(location.pathname); const isInModQueueView = isModQueueView(location.pathname);
const { filteredCount, searchText } = useCatalogFiltersStore();
const enableInfiniteScroll = useFeedViewSettingsStore((state) => state.enableInfiniteScroll); const enableInfiniteScroll = useFeedViewSettingsStore((state) => state.enableInfiniteScroll);
const isMultiboard = isInAllView || isInSubscriptionsView || isInModView; const isMultiboard = isInAllView || isInSubscriptionsView || isInModView;
const showTimeFilter = isMultiboard; const showTimeFilter = isMultiboard;
@@ -794,20 +805,7 @@ export const DesktopBoardButtons = () => {
<ModQueueButton boardIdentifier={boardIdentifier} isMobile={false} /> <ModQueueButton boardIdentifier={boardIdentifier} isMobile={false} />
</> </>
)} )}
{isInCatalogView && searchText ? ( {isInCatalogView && <CatalogSearchResultsLabel />}
<span className={styles.filteredThreadsCount}>
{' '}
- {t('search_results_for')}: <strong>{searchText}</strong>
</span>
) : (
isInCatalogView &&
filteredCount > 0 && (
<span className={styles.filteredThreadsCount}>
{' '}
- {t('filtered_threads')}: <strong>{filteredCount}</strong>
</span>
)
)}
<span className={styles.rightSideButtons}> <span className={styles.rightSideButtons}>
{isInCatalogView && ( {isInCatalogView && (
<> <>
@@ -5,6 +5,7 @@ import { MemoryRouter } from 'react-router-dom';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { import {
CatalogFooterFirstRow, CatalogFooterFirstRow,
CatalogFooterStyleRow,
PageFooterDesktop, PageFooterDesktop,
PageFooterMobile, PageFooterMobile,
StyleOnlyFooterFirstRow, StyleOnlyFooterFirstRow,
@@ -51,6 +52,7 @@ vi.mock('../../style-selector/style-selector', () => ({
})); }));
vi.mock('../../board-buttons/board-buttons', () => ({ vi.mock('../../board-buttons/board-buttons', () => ({
CatalogSearchResultsLabel: () => null,
AutoButton: () => createElement('button', { type: 'button' }, 'auto-button'), AutoButton: () => createElement('button', { type: 'button' }, 'auto-button'),
CatalogButton: ({ CatalogButton: ({
address, address,
@@ -154,6 +156,7 @@ describe('footer', () => {
isInAllView: true, isInAllView: true,
communityAddress: 'music-posting.eth', communityAddress: 'music-posting.eth',
}), }),
createElement(CatalogFooterStyleRow),
createElement(ThreadFooterStyleRow), createElement(ThreadFooterStyleRow),
createElement(PageFooterMobile, { createElement(PageFooterMobile, {
children: createElement('div', { 'data-testid': 'mobile-child' }, 'mobile-child'), children: createElement('div', { 'data-testid': 'mobile-child' }, 'mobile-child'),
+4 -1
View File
@@ -4,6 +4,10 @@
padding-bottom: 24px; padding-bottom: 24px;
} }
.footerCatalog {
margin-top: 0;
}
.firstRow { .firstRow {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -76,7 +80,6 @@
} }
.styleLabel { .styleLabel {
font-size: 12px;
text-transform: capitalize; text-transform: capitalize;
} }
+33 -11
View File
@@ -4,7 +4,16 @@ import { useComment } from '@bitsocial/bitsocial-react-hooks';
import BoardsBar from '../boards-bar'; import BoardsBar from '../boards-bar';
import SiteLegalMeta from '../site-legal-meta'; import SiteLegalMeta from '../site-legal-meta';
import StyleSelector from '../style-selector/style-selector'; import StyleSelector from '../style-selector/style-selector';
import { ReturnButton, CatalogButton, TopButton, UpdateButton, AutoButton, PostPageStats, RefreshButton } from '../board-buttons/board-buttons'; import {
CatalogSearchResultsLabel,
ReturnButton,
CatalogButton,
TopButton,
UpdateButton,
AutoButton,
PostPageStats,
RefreshButton,
} from '../board-buttons/board-buttons';
import { isAllView, isSubscriptionsView, isModView } from '../../lib/utils/view-utils'; import { isAllView, isSubscriptionsView, isModView } from '../../lib/utils/view-utils';
import useReplyModalStore from '../../stores/use-reply-modal-store'; import useReplyModalStore from '../../stores/use-reply-modal-store';
import useThreadLiveUpdatesStore from '../../stores/use-thread-live-updates-store'; import useThreadLiveUpdatesStore from '../../stores/use-thread-live-updates-store';
@@ -22,15 +31,17 @@ import styles from './footer.module.css';
interface PageFooterDesktopProps { interface PageFooterDesktopProps {
/** Mode-specific first row content (e.g. board pagination or thread controls) */ /** Mode-specific first row content (e.g. board pagination or thread controls) */
firstRow: React.ReactNode; firstRow?: React.ReactNode;
/** Optional row between first row and BoardsBar (e.g. style selector on thread page) */ /** Optional row between first row and BoardsBar (e.g. style selector on thread page) */
styleRow?: React.ReactNode; styleRow?: React.ReactNode;
/** Catalog pages omit the default footer top margin */
variant?: 'catalog';
} }
export const PageFooterDesktop = ({ firstRow, styleRow }: PageFooterDesktopProps) => ( export const PageFooterDesktop = ({ firstRow, styleRow, variant }: PageFooterDesktopProps) => (
<footer className={styles.footer}> <footer className={variant === 'catalog' ? `${styles.footer} ${styles.footerCatalog}` : styles.footer}>
<hr /> <hr />
<div className={styles.firstRow}>{firstRow}</div> {firstRow != null ? <div className={styles.firstRow}>{firstRow}</div> : null}
{styleRow != null ? <div className={styles.styleRow}>{styleRow}</div> : null} {styleRow != null ? <div className={styles.styleRow}>{styleRow}</div> : null}
<div className={styles.boardsBarRow}> <div className={styles.boardsBarRow}>
<BoardsBar /> <BoardsBar />
@@ -60,7 +71,7 @@ export const StyleOnlyFooterFirstRow = () => {
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
* CatalogFooterFirstRow * CatalogFooterFirstRow
* Catalog footer first row: Return, Archive, Top, Refresh on left; Style selector on right. * Catalog footer nav row: Return, Catalog, Top, Refresh, plus search/filter label.
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
interface CatalogFooterFirstRowProps { interface CatalogFooterFirstRowProps {
@@ -71,7 +82,6 @@ interface CatalogFooterFirstRowProps {
} }
export const CatalogFooterFirstRow = ({ communityAddress, isInAllView = false, isInSubscriptionsView = false, isInModView = false }: CatalogFooterFirstRowProps) => { export const CatalogFooterFirstRow = ({ communityAddress, isInAllView = false, isInSubscriptionsView = false, isInModView = false }: CatalogFooterFirstRowProps) => {
const { t } = useTranslation();
return ( return (
<div className={styles.footerRow}> <div className={styles.footerRow}>
<div className={styles.footerLeft}> <div className={styles.footerLeft}>
@@ -87,15 +97,27 @@ export const CatalogFooterFirstRow = ({ communityAddress, isInAllView = false, i
<span> <span>
[<RefreshButton />] [<RefreshButton />]
</span> </span>
</div> <CatalogSearchResultsLabel />
<div className={styles.footerRight}>
<span className={styles.styleLabel}>{t('style')}:</span>
<StyleSelector />
</div> </div>
</div> </div>
); );
}; };
/* -----------------------------------------------------------------------------
* CatalogFooterStyleRow
* Catalog style selector on its own row (below nav, above BoardsBar).
* -------------------------------------------------------------------------- */
export const CatalogFooterStyleRow = () => {
const { t } = useTranslation();
return (
<span className={styles.styleRowContent}>
<span className={styles.styleLabel}>{t('style')}:</span>
<StyleSelector />
</span>
);
};
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
* ThreadFooterStyleRow * ThreadFooterStyleRow
* Style selector on its own row (thread page only, below first row, above BoardsBar). * Style selector on its own row (thread page only, below first row, above BoardsBar).
+1
View File
@@ -3,6 +3,7 @@ export {
PageFooterMobile, PageFooterMobile,
StyleOnlyFooterFirstRow, StyleOnlyFooterFirstRow,
CatalogFooterFirstRow, CatalogFooterFirstRow,
CatalogFooterStyleRow,
ThreadFooterFirstRow, ThreadFooterFirstRow,
ThreadFooterStyleRow, ThreadFooterStyleRow,
ThreadFooterMobile, ThreadFooterMobile,
+25 -2
View File
@@ -322,8 +322,15 @@ vi.mock('../../../components/catalog-row', () => ({
vi.mock('../../../components/footer', () => ({ vi.mock('../../../components/footer', () => ({
CatalogFooterFirstRow: ({ communityAddress }: { communityAddress?: string }) => CatalogFooterFirstRow: ({ communityAddress }: { communityAddress?: string }) =>
createElement('div', { 'data-testid': 'catalog-first-row' }, communityAddress || 'multi'), createElement(
PageFooterDesktop: ({ firstRow }: { firstRow: React.ReactNode }) => createElement('div', { 'data-testid': 'catalog-footer-desktop' }, firstRow), 'div',
{ 'data-testid': 'catalog-first-row' },
communityAddress || 'multi',
testState.searchText ? ` - search_results_for: ${testState.searchText}` : null,
),
CatalogFooterStyleRow: () => createElement('div', { 'data-testid': 'catalog-style-row' }, 'style'),
PageFooterDesktop: ({ firstRow, styleRow }: { firstRow?: React.ReactNode; styleRow?: React.ReactNode }) =>
createElement('div', { 'data-testid': 'catalog-footer-desktop' }, firstRow, styleRow),
PageFooterMobile: ({ children }: { children: React.ReactNode }) => createElement('div', { 'data-testid': 'catalog-footer-mobile' }, children), PageFooterMobile: ({ children }: { children: React.ReactNode }) => createElement('div', { 'data-testid': 'catalog-footer-mobile' }, children),
})); }));
@@ -902,6 +909,22 @@ describe('Catalog', () => {
expect(container.querySelector('[data-testid="catalog-first-row"]')?.textContent).toBe('music-posting.eth'); expect(container.querySelector('[data-testid="catalog-first-row"]')?.textContent).toBe('music-posting.eth');
}); });
it('shows centered nothing found when catalog search has no matches', async () => {
testState.feed = [{ cid: 'network-post', title: 'cats on stage', communityAddress: 'music-posting.eth' }];
testState.searchText = 'asddasd';
testState.hasMore = false;
await renderCatalog({ initialEntry: '/mu/catalog?q=asddasd', routePath: '/:boardIdentifier/catalog' });
expect(container.textContent).toContain('nothing_found');
expect(container.textContent).not.toContain('no_threads');
expect(container.querySelector('[role="status"]')?.className).toContain('searchNothingFound');
expect(container.querySelector('[role="status"]')?.textContent).toBe('nothing_found');
expect(container.querySelectorAll('[data-testid="catalog-row"]')).toHaveLength(0);
expect(container.textContent).toContain('search_results_for');
expect(container.textContent).toContain('asddasd');
});
it('queries scoped recent account posts and still applies local search filtering before injecting them', async () => { it('queries scoped recent account posts and still applies local search filtering before injecting them', async () => {
const currentTimestamp = Math.floor(Date.now() / 1000); const currentTimestamp = Math.floor(Date.now() / 1000);
testState.feed = [{ cid: 'network-post', title: 'cats on stage', communityAddress: 'music-posting.eth' }]; testState.feed = [{ cid: 'network-post', title: 'cats on stage', communityAddress: 'music-posting.eth' }];
+16
View File
@@ -3,6 +3,22 @@
color: var(--post-mobile-abbr-text-color); color: var(--post-mobile-abbr-text-color);
} }
.stateString {
padding: 15px 0 10px 5px;
color: var(--post-mobile-abbr-text-color);
}
.searchNothingFound {
color: red;
display: block;
margin: 0;
padding: 1em 0;
text-align: center;
font-size: x-large;
font-weight: bold;
line-height: normal;
}
.morePostsSuggestion { .morePostsSuggestion {
padding-bottom: 5px; padding-bottom: 5px;
} }
+51 -33
View File
@@ -25,7 +25,7 @@ import useSortingStore from '../../stores/use-sorting-store';
import useCatalogFiltersStore from '../../stores/use-catalog-filters-store'; import useCatalogFiltersStore from '../../stores/use-catalog-filters-store';
import { getCommunityAddress, isDirectoryBoard, normalizeMultiboardFeedPath } from '../../lib/utils/route-utils'; import { getCommunityAddress, isDirectoryBoard, normalizeMultiboardFeedPath } from '../../lib/utils/route-utils';
import CatalogRow from '../../components/catalog-row'; import CatalogRow from '../../components/catalog-row';
import { CatalogFooterFirstRow, PageFooterDesktop, PageFooterMobile } from '../../components/footer'; import { CatalogFooterFirstRow, CatalogFooterStyleRow, PageFooterDesktop, PageFooterMobile } from '../../components/footer';
import { ReturnButton, ArchiveButton, TopButton, RefreshButton } from '../../components/board-buttons/board-buttons'; import { ReturnButton, ArchiveButton, TopButton, RefreshButton } from '../../components/board-buttons/board-buttons';
import mobileFooterStyles from '../../components/footer/footer.module.css'; import mobileFooterStyles from '../../components/footer/footer.module.css';
import LoadingEllipsis from '../../components/loading-ellipsis'; import LoadingEllipsis from '../../components/loading-ellipsis';
@@ -131,6 +131,20 @@ const CatalogFooter = ({
// Separate component for the loading state when there's no feed // Separate component for the loading state when there's no feed
// This also calls useFeedStateString internally to isolate re-renders // This also calls useFeedStateString internally to isolate re-renders
interface CatalogSearchNothingFoundProps {
className?: string;
}
const CatalogSearchNothingFound = ({ className }: CatalogSearchNothingFoundProps) => {
const { t } = useTranslation();
return (
<div className={className ?? styles.searchNothingFound} role='status'>
{t('nothing_found')}
</div>
);
};
interface CatalogLoadingProps { interface CatalogLoadingProps {
communityAddresses: string[]; communityAddresses: string[];
hasMore: boolean; hasMore: boolean;
@@ -138,9 +152,10 @@ interface CatalogLoadingProps {
state: string | undefined; state: string | undefined;
subscriptionsLength: number; subscriptionsLength: number;
error: Error | undefined; error: Error | undefined;
hasActiveSearch: boolean;
} }
const CatalogLoading = ({ communityAddresses, hasMore, combinedFeedLength, state, subscriptionsLength, error }: CatalogLoadingProps) => { const CatalogLoading = ({ communityAddresses, hasMore, combinedFeedLength, state, subscriptionsLength, error, hasActiveSearch }: CatalogLoadingProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const rawFeedStateString = useFeedStateString(communityAddresses); const rawFeedStateString = useFeedStateString(communityAddresses);
@@ -153,7 +168,9 @@ const CatalogLoading = ({ communityAddresses, hasMore, combinedFeedLength, state
) : subscriptionsLength === 0 ? ( ) : subscriptionsLength === 0 ? (
<span className='red'>{t('not_subscribed_to_any_board')}</span> <span className='red'>{t('not_subscribed_to_any_board')}</span>
) : !hasMore && combinedFeedLength === 0 ? ( ) : !hasMore && combinedFeedLength === 0 ? (
t('no_threads') hasActiveSearch ? null : (
t('no_threads')
)
) : ( ) : (
hasMore && <LoadingEllipsis string={loadingStateString} /> hasMore && <LoadingEllipsis string={loadingStateString} />
)} )}
@@ -587,6 +604,11 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
const footerMoreThreadsSuggestion = showHiddenThreads ? null : moreThreadsSuggestion; const footerMoreThreadsSuggestion = showHiddenThreads ? null : moreThreadsSuggestion;
const footerShowLoadingEllipsis = showHiddenThreads ? isLoadingHiddenCatalogThreads : effectiveInfiniteScroll; const footerShowLoadingEllipsis = showHiddenThreads ? isLoadingHiddenCatalogThreads : effectiveInfiniteScroll;
const catalogFooterFirstRow = (
<CatalogFooterFirstRow communityAddress={communityAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />
);
const catalogFooterStyleRow = <CatalogFooterStyleRow />;
// Memoize footer component to preserve identity across renders (Virtuoso optimization) // Memoize footer component to preserve identity across renders (Virtuoso optimization)
// Note: useFeedStateString is called inside CatalogFooter to isolate re-renders from backend state changes // Note: useFeedStateString is called inside CatalogFooter to isolate re-renders from backend state changes
const footerComponents = useMemo( const footerComponents = useMemo(
@@ -604,16 +626,7 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
onExpandTimeWindow={expandSuggestionTimeWindow} onExpandTimeWindow={expandSuggestionTimeWindow}
showLoadingEllipsis={footerShowLoadingEllipsis} showLoadingEllipsis={footerShowLoadingEllipsis}
/> />
<PageFooterDesktop <PageFooterDesktop variant='catalog' firstRow={catalogFooterFirstRow} styleRow={catalogFooterStyleRow} />
firstRow={
<CatalogFooterFirstRow
communityAddress={communityAddress}
isInAllView={isInAllView}
isInSubscriptionsView={isInSubscriptionsView}
isInModView={isInModView}
/>
}
/>
<PageFooterMobile> <PageFooterMobile>
<div className={mobileFooterStyles.mobileFooterButtons}> <div className={mobileFooterStyles.mobileFooterButtons}>
<ReturnButton address={communityAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} /> <ReturnButton address={communityAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />
@@ -633,6 +646,8 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
footerMoreThreadsSuggestion, footerMoreThreadsSuggestion,
moreThreadsSuggestionPathname, moreThreadsSuggestionPathname,
expandSuggestionTimeWindow, expandSuggestionTimeWindow,
catalogFooterFirstRow,
catalogFooterStyleRow,
communityAddress, communityAddress,
isInAllView, isInAllView,
isInSubscriptionsView, isInSubscriptionsView,
@@ -655,16 +670,7 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
onExpandTimeWindow={expandSuggestionTimeWindow} onExpandTimeWindow={expandSuggestionTimeWindow}
showLoadingEllipsis={footerShowLoadingEllipsis} showLoadingEllipsis={footerShowLoadingEllipsis}
/> />
<PageFooterDesktop <PageFooterDesktop variant='catalog' firstRow={catalogFooterFirstRow} styleRow={catalogFooterStyleRow} />
firstRow={
<CatalogFooterFirstRow
communityAddress={communityAddress}
isInAllView={isInAllView}
isInSubscriptionsView={isInSubscriptionsView}
isInModView={isInModView}
/>
}
/>
<PageFooterMobile> <PageFooterMobile>
<div className={mobileFooterStyles.mobileFooterButtons}> <div className={mobileFooterStyles.mobileFooterButtons}>
<ReturnButton address={communityAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} /> <ReturnButton address={communityAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />
@@ -683,6 +689,8 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
footerMoreThreadsSuggestion, footerMoreThreadsSuggestion,
moreThreadsSuggestionPathname, moreThreadsSuggestionPathname,
expandSuggestionTimeWindow, expandSuggestionTimeWindow,
catalogFooterFirstRow,
catalogFooterStyleRow,
communityAddress, communityAddress,
isInAllView, isInAllView,
isInSubscriptionsView, isInSubscriptionsView,
@@ -692,6 +700,9 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
], ],
); );
const isFeedLoaded = feed.length > 0 || hiddenThreadsCount > 0 || state === 'failed'; const isFeedLoaded = feed.length > 0 || hiddenThreadsCount > 0 || state === 'failed';
const hasActiveSearch = searchText.trim().length > 0;
const showSearchNothingFound =
hasActiveSearch && !footerHasMore && footerCombinedFeedLength === 0 && state !== 'failed' && !(isInSubscriptionsView && (subscriptions?.length || 0) === 0);
// Process the feed to move "top" posts to the top (applied after display sort) // Process the feed to move "top" posts to the top (applied after display sort)
const processedFeed = useMemo(() => { const processedFeed = useMemo(() => {
@@ -896,6 +907,21 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
</> </>
)} )}
</> </>
) : showSearchNothingFound ? (
<>
<CatalogSearchNothingFound />
<hr />
{catalogFooterFirstRow}
<PageFooterDesktop variant='catalog' styleRow={catalogFooterStyleRow} />
<PageFooterMobile>
<div className={mobileFooterStyles.mobileFooterButtons}>
<ReturnButton address={communityAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />
<ArchiveButton address={communityAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />
<TopButton />
<RefreshButton />
</div>
</PageFooterMobile>
</>
) : ( ) : (
<> <>
<div className={styles.footer}> <div className={styles.footer}>
@@ -906,18 +932,10 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
state={state} state={state}
subscriptionsLength={isInSubscriptionsView ? subscriptions?.length || 0 : 1} subscriptionsLength={isInSubscriptionsView ? subscriptions?.length || 0 : 1}
error={error} error={error}
hasActiveSearch={hasActiveSearch}
/> />
</div> </div>
<PageFooterDesktop <PageFooterDesktop variant='catalog' firstRow={catalogFooterFirstRow} styleRow={catalogFooterStyleRow} />
firstRow={
<CatalogFooterFirstRow
communityAddress={communityAddress}
isInAllView={isInAllView}
isInSubscriptionsView={isInSubscriptionsView}
isInModView={isInModView}
/>
}
/>
<PageFooterMobile> <PageFooterMobile>
<div className={mobileFooterStyles.mobileFooterButtons}> <div className={mobileFooterStyles.mobileFooterButtons}>
<ReturnButton address={communityAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} /> <ReturnButton address={communityAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />