mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
Merge branch 'codex/fix/catalog-search-empty-state'
This commit is contained in:
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ export {
|
|||||||
PageFooterMobile,
|
PageFooterMobile,
|
||||||
StyleOnlyFooterFirstRow,
|
StyleOnlyFooterFirstRow,
|
||||||
CatalogFooterFirstRow,
|
CatalogFooterFirstRow,
|
||||||
|
CatalogFooterStyleRow,
|
||||||
ThreadFooterFirstRow,
|
ThreadFooterFirstRow,
|
||||||
ThreadFooterStyleRow,
|
ThreadFooterStyleRow,
|
||||||
ThreadFooterMobile,
|
ThreadFooterMobile,
|
||||||
|
|||||||
@@ -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' }];
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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} />
|
||||||
|
|||||||
Reference in New Issue
Block a user