From 6b2b85f51e3f11386a4f8a4f1ee7cd79ee82327c Mon Sep 17 00:00:00 2001 From: Tommaso Casaburi Date: Sun, 28 Jun 2026 15:44:36 +0700 Subject: [PATCH] fix(post-form): enforce required post link board rules Validate link and media requirements from community/directory features in post and reply forms, with shared helpers and translated alert messages. --- public/translations/ar/default.json | 4 +- public/translations/bn/default.json | 4 +- public/translations/cs/default.json | 4 +- public/translations/da/default.json | 4 +- public/translations/de/default.json | 4 +- public/translations/el/default.json | 4 +- public/translations/en/default.json | 4 +- public/translations/es/default.json | 4 +- public/translations/fa/default.json | 4 +- public/translations/fi/default.json | 4 +- public/translations/fil/default.json | 4 +- public/translations/fr/default.json | 4 +- public/translations/he/default.json | 4 +- public/translations/hi/default.json | 4 +- public/translations/hu/default.json | 4 +- public/translations/id/default.json | 4 +- public/translations/it/default.json | 4 +- public/translations/ja/default.json | 4 +- public/translations/ko/default.json | 4 +- public/translations/mr/default.json | 4 +- public/translations/nl/default.json | 4 +- public/translations/no/default.json | 4 +- public/translations/pl/default.json | 4 +- public/translations/pt/default.json | 4 +- public/translations/ro/default.json | 4 +- public/translations/ru/default.json | 4 +- public/translations/sq/default.json | 4 +- public/translations/sv/default.json | 4 +- public/translations/te/default.json | 4 +- public/translations/th/default.json | 4 +- public/translations/tr/default.json | 4 +- public/translations/uk/default.json | 4 +- public/translations/ur/default.json | 4 +- public/translations/vi/default.json | 4 +- public/translations/zh/default.json | 4 +- .../post-form/__tests__/post-form.test.tsx | 116 +++++++++++++++++- src/components/post-form/post-form.tsx | 45 +++---- .../__tests__/reply-modal.test.tsx | 96 ++++++++++++++- src/components/reply-modal/reply-modal.tsx | 24 ++-- .../publish-link-requirements.test.ts | 40 ++++++ src/lib/utils/publish-link-requirements.ts | 54 ++++++++ 41 files changed, 443 insertions(+), 72 deletions(-) create mode 100644 src/lib/utils/__tests__/publish-link-requirements.test.ts create mode 100644 src/lib/utils/publish-link-requirements.ts diff --git a/public/translations/ar/default.json b/public/translations/ar/default.json index eaa8d94e..bbe73c49 100644 --- a/public/translations/ar/default.json +++ b/public/translations/ar/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "ترقية الإعدادات متاحة", "settings_upgrade_review_button": "مراجعة", "settings_upgrade_never_show_again": "لا تظهر مرة أخرى", - "site_legal_meta_contributors_link": "المساهمون" + "site_legal_meta_contributors_link": "المساهمون", + "post_link_required_alert": "يجب أن تنشر رابطًا.", + "post_media_link_required_alert": "يجب أن تنشر رابط صورة أو فيديو." } diff --git a/public/translations/bn/default.json b/public/translations/bn/default.json index f4ee7a95..4b56df97 100644 --- a/public/translations/bn/default.json +++ b/public/translations/bn/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "সেটিংস আপগ্রেড উপলব্ধ", "settings_upgrade_review_button": "পর্যালোচনা", "settings_upgrade_never_show_again": "আর দেখাবেন না", - "site_legal_meta_contributors_link": "অবদানকারীরা" + "site_legal_meta_contributors_link": "অবদানকারীরা", + "post_link_required_alert": "আপনাকে একটি লিঙ্ক পোস্ট করতে হবে।", + "post_media_link_required_alert": "আপনাকে একটি ছবি বা ভিডিওর লিঙ্ক পোস্ট করতে হবে।" } diff --git a/public/translations/cs/default.json b/public/translations/cs/default.json index 604c699c..3148bae6 100644 --- a/public/translations/cs/default.json +++ b/public/translations/cs/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "upgrade nastavení k dispozici", "settings_upgrade_review_button": "zkontrolovat", "settings_upgrade_never_show_again": "už nezobrazovat", - "site_legal_meta_contributors_link": "Přispěvatelé" + "site_legal_meta_contributors_link": "Přispěvatelé", + "post_link_required_alert": "Musíte přidat odkaz.", + "post_media_link_required_alert": "Musíte přidat odkaz na obrázek nebo video." } diff --git a/public/translations/da/default.json b/public/translations/da/default.json index e8c3bc8a..d96c875e 100644 --- a/public/translations/da/default.json +++ b/public/translations/da/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "indstillingsopgradering tilgængelig", "settings_upgrade_review_button": "gennemgå", "settings_upgrade_never_show_again": "vis aldrig igen", - "site_legal_meta_contributors_link": "Bidragydere" + "site_legal_meta_contributors_link": "Bidragydere", + "post_link_required_alert": "Du skal poste et link.", + "post_media_link_required_alert": "Du skal poste et billed- eller videolink." } diff --git a/public/translations/de/default.json b/public/translations/de/default.json index 5b91e9ad..80b81040 100644 --- a/public/translations/de/default.json +++ b/public/translations/de/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "Einstellungs-Upgrade verfügbar", "settings_upgrade_review_button": "überprüfen", "settings_upgrade_never_show_again": "nie wieder anzeigen", - "site_legal_meta_contributors_link": "Mitwirkende" + "site_legal_meta_contributors_link": "Mitwirkende", + "post_link_required_alert": "Du musst einen Link posten.", + "post_media_link_required_alert": "Du musst einen Bild- oder Videolink posten." } diff --git a/public/translations/el/default.json b/public/translations/el/default.json index 33890542..925c77c1 100644 --- a/public/translations/el/default.json +++ b/public/translations/el/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "αναβάθμιση ρυθμίσεων διαθέσιμη", "settings_upgrade_review_button": "έλεγχος", "settings_upgrade_never_show_again": "να μην εμφανιστεί ξανά", - "site_legal_meta_contributors_link": "Συνεισφέροντες" + "site_legal_meta_contributors_link": "Συνεισφέροντες", + "post_link_required_alert": "Πρέπει να δημοσιεύσετε έναν σύνδεσμο.", + "post_media_link_required_alert": "Πρέπει να δημοσιεύσετε σύνδεσμο εικόνας ή βίντεο." } diff --git a/public/translations/en/default.json b/public/translations/en/default.json index ae533816..2074d94b 100644 --- a/public/translations/en/default.json +++ b/public/translations/en/default.json @@ -462,5 +462,7 @@ "settings_upgrade_review_notice": "settings upgrade available", "settings_upgrade_review_button": "review", "settings_upgrade_never_show_again": "never show again", - "site_legal_meta_contributors_link": "Contributors" + "site_legal_meta_contributors_link": "Contributors", + "post_link_required_alert": "You must post a link.", + "post_media_link_required_alert": "You must post an image or video link." } diff --git a/public/translations/es/default.json b/public/translations/es/default.json index 585ac877..b4f71237 100644 --- a/public/translations/es/default.json +++ b/public/translations/es/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "actualización de configuración disponible", "settings_upgrade_review_button": "revisar", "settings_upgrade_never_show_again": "no volver a mostrar", - "site_legal_meta_contributors_link": "Colaboradores" + "site_legal_meta_contributors_link": "Colaboradores", + "post_link_required_alert": "Debes publicar un enlace.", + "post_media_link_required_alert": "Debes publicar un enlace a una imagen o un video." } diff --git a/public/translations/fa/default.json b/public/translations/fa/default.json index 4ccd3ec3..195c6690 100644 --- a/public/translations/fa/default.json +++ b/public/translations/fa/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "ارتقای تنظیمات موجود است", "settings_upgrade_review_button": "بررسی", "settings_upgrade_never_show_again": "دیگر نشان نده", - "site_legal_meta_contributors_link": "مشارکت‌کنندگان" + "site_legal_meta_contributors_link": "مشارکت‌کنندگان", + "post_link_required_alert": "باید یک پیوند ارسال کنید.", + "post_media_link_required_alert": "باید پیوند یک تصویر یا ویدیو را ارسال کنید." } diff --git a/public/translations/fi/default.json b/public/translations/fi/default.json index d4913864..bc52f786 100644 --- a/public/translations/fi/default.json +++ b/public/translations/fi/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "asetuspäivitys saatavilla", "settings_upgrade_review_button": "tarkista", "settings_upgrade_never_show_again": "älä näytä uudelleen", - "site_legal_meta_contributors_link": "Avustajat" + "site_legal_meta_contributors_link": "Avustajat", + "post_link_required_alert": "Sinun on julkaistava linkki.", + "post_media_link_required_alert": "Sinun on julkaistava kuva- tai videolinkki." } diff --git a/public/translations/fil/default.json b/public/translations/fil/default.json index 524bb9ef..804e05a7 100644 --- a/public/translations/fil/default.json +++ b/public/translations/fil/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "may upgrade ng setting", "settings_upgrade_review_button": "suriin", "settings_upgrade_never_show_again": "huwag nang ipakita muli", - "site_legal_meta_contributors_link": "Mga Kontribyutor" + "site_legal_meta_contributors_link": "Mga Kontribyutor", + "post_link_required_alert": "Kailangan mong mag-post ng link.", + "post_media_link_required_alert": "Kailangan mong mag-post ng link ng larawan o video." } diff --git a/public/translations/fr/default.json b/public/translations/fr/default.json index 9d73b478..9b3eeb88 100644 --- a/public/translations/fr/default.json +++ b/public/translations/fr/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "mise à jour des paramètres disponible", "settings_upgrade_review_button": "examiner", "settings_upgrade_never_show_again": "ne plus afficher", - "site_legal_meta_contributors_link": "Contributeurs" + "site_legal_meta_contributors_link": "Contributeurs", + "post_link_required_alert": "Vous devez publier un lien.", + "post_media_link_required_alert": "Vous devez publier un lien vers une image ou une vidéo." } diff --git a/public/translations/he/default.json b/public/translations/he/default.json index d7376953..505d4c02 100644 --- a/public/translations/he/default.json +++ b/public/translations/he/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "שדרוג הגדרות זמין", "settings_upgrade_review_button": "סקור", "settings_upgrade_never_show_again": "לא להציג שוב", - "site_legal_meta_contributors_link": "תורמים" + "site_legal_meta_contributors_link": "תורמים", + "post_link_required_alert": "עליך לפרסם קישור.", + "post_media_link_required_alert": "עליך לפרסם קישור לתמונה או לסרטון." } diff --git a/public/translations/hi/default.json b/public/translations/hi/default.json index 6fa14d57..56d16d44 100644 --- a/public/translations/hi/default.json +++ b/public/translations/hi/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "सेटिंग्स अपग्रेड उपलब्ध", "settings_upgrade_review_button": "समीक्षा करें", "settings_upgrade_never_show_again": "फिर कभी न दिखाएँ", - "site_legal_meta_contributors_link": "योगदानकर्ता" + "site_legal_meta_contributors_link": "योगदानकर्ता", + "post_link_required_alert": "आपको एक लिंक पोस्ट करना होगा।", + "post_media_link_required_alert": "आपको एक छवि या वीडियो लिंक पोस्ट करना होगा।" } diff --git a/public/translations/hu/default.json b/public/translations/hu/default.json index 4d551746..afcbb8c9 100644 --- a/public/translations/hu/default.json +++ b/public/translations/hu/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "beállításfrissítés elérhető", "settings_upgrade_review_button": "áttekintés", "settings_upgrade_never_show_again": "ne jelenjen meg újra", - "site_legal_meta_contributors_link": "Közreműködők" + "site_legal_meta_contributors_link": "Közreműködők", + "post_link_required_alert": "Közzé kell tenned egy linket.", + "post_media_link_required_alert": "Közzé kell tenned egy kép- vagy videólinket." } diff --git a/public/translations/id/default.json b/public/translations/id/default.json index 64f0c0ab..2e7a0be5 100644 --- a/public/translations/id/default.json +++ b/public/translations/id/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "pembaruan pengaturan tersedia", "settings_upgrade_review_button": "tinjau", "settings_upgrade_never_show_again": "jangan tampilkan lagi", - "site_legal_meta_contributors_link": "Kontributor" + "site_legal_meta_contributors_link": "Kontributor", + "post_link_required_alert": "Anda harus memposting tautan.", + "post_media_link_required_alert": "Anda harus memposting tautan gambar atau video." } diff --git a/public/translations/it/default.json b/public/translations/it/default.json index c00a7f4c..3dd7689a 100644 --- a/public/translations/it/default.json +++ b/public/translations/it/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "aggiornamento impostazioni disponibile", "settings_upgrade_review_button": "rivedi", "settings_upgrade_never_show_again": "non mostrare più", - "site_legal_meta_contributors_link": "Collaboratori" + "site_legal_meta_contributors_link": "Collaboratori", + "post_link_required_alert": "Devi pubblicare un link.", + "post_media_link_required_alert": "Devi pubblicare un link a un'immagine o a un video." } diff --git a/public/translations/ja/default.json b/public/translations/ja/default.json index df48ef39..62683c87 100644 --- a/public/translations/ja/default.json +++ b/public/translations/ja/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "設定のアップグレードが利用可能", "settings_upgrade_review_button": "確認", "settings_upgrade_never_show_again": "今後表示しない", - "site_legal_meta_contributors_link": "貢献者" + "site_legal_meta_contributors_link": "貢献者", + "post_link_required_alert": "リンクを投稿する必要があります。", + "post_media_link_required_alert": "画像または動画のリンクを投稿する必要があります。" } diff --git a/public/translations/ko/default.json b/public/translations/ko/default.json index 41de1224..ed402c2e 100644 --- a/public/translations/ko/default.json +++ b/public/translations/ko/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "설정 업그레이드 사용 가능", "settings_upgrade_review_button": "검토", "settings_upgrade_never_show_again": "다시 표시하지 않기", - "site_legal_meta_contributors_link": "기여자" + "site_legal_meta_contributors_link": "기여자", + "post_link_required_alert": "링크를 게시해야 합니다.", + "post_media_link_required_alert": "이미지 또는 동영상 링크를 게시해야 합니다." } diff --git a/public/translations/mr/default.json b/public/translations/mr/default.json index 272794f4..759e6ec5 100644 --- a/public/translations/mr/default.json +++ b/public/translations/mr/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "सेटिंग्ज अपग्रेड उपलब्ध", "settings_upgrade_review_button": "पुनरावलोकन", "settings_upgrade_never_show_again": "पुन्हा दाखवू नका", - "site_legal_meta_contributors_link": "योगदानकर्ते" + "site_legal_meta_contributors_link": "योगदानकर्ते", + "post_link_required_alert": "तुम्हाला एक लिंक पोस्ट करावी लागेल.", + "post_media_link_required_alert": "तुम्हाला प्रतिमा किंवा व्हिडिओची लिंक पोस्ट करावी लागेल." } diff --git a/public/translations/nl/default.json b/public/translations/nl/default.json index 25913bef..02236c1a 100644 --- a/public/translations/nl/default.json +++ b/public/translations/nl/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "instellingenupgrade beschikbaar", "settings_upgrade_review_button": "bekijken", "settings_upgrade_never_show_again": "niet meer tonen", - "site_legal_meta_contributors_link": "Bijdragers" + "site_legal_meta_contributors_link": "Bijdragers", + "post_link_required_alert": "Je moet een link plaatsen.", + "post_media_link_required_alert": "Je moet een afbeelding- of videolink plaatsen." } diff --git a/public/translations/no/default.json b/public/translations/no/default.json index 5c78086c..cbe31ac7 100644 --- a/public/translations/no/default.json +++ b/public/translations/no/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "innstillingsoppgradering tilgjengelig", "settings_upgrade_review_button": "se gjennom", "settings_upgrade_never_show_again": "vis aldri igjen", - "site_legal_meta_contributors_link": "Bidragsytere" + "site_legal_meta_contributors_link": "Bidragsytere", + "post_link_required_alert": "Du må poste en lenke.", + "post_media_link_required_alert": "Du må poste en bilde- eller videolenke." } diff --git a/public/translations/pl/default.json b/public/translations/pl/default.json index 67e7c131..47093ebe 100644 --- a/public/translations/pl/default.json +++ b/public/translations/pl/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "aktualizacja ustawień dostępna", "settings_upgrade_review_button": "przejrzyj", "settings_upgrade_never_show_again": "nie pokazuj ponownie", - "site_legal_meta_contributors_link": "Współtwórcy" + "site_legal_meta_contributors_link": "Współtwórcy", + "post_link_required_alert": "Musisz opublikować link.", + "post_media_link_required_alert": "Musisz opublikować link do obrazu lub filmu." } diff --git a/public/translations/pt/default.json b/public/translations/pt/default.json index b03b75fa..fa8b8fda 100644 --- a/public/translations/pt/default.json +++ b/public/translations/pt/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "atualização de configurações disponível", "settings_upgrade_review_button": "revisar", "settings_upgrade_never_show_again": "não mostrar novamente", - "site_legal_meta_contributors_link": "Colaboradores" + "site_legal_meta_contributors_link": "Colaboradores", + "post_link_required_alert": "Você deve postar um link.", + "post_media_link_required_alert": "Você deve postar um link de imagem ou vídeo." } diff --git a/public/translations/ro/default.json b/public/translations/ro/default.json index 5fbe9d92..42619ee1 100644 --- a/public/translations/ro/default.json +++ b/public/translations/ro/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "actualizare setări disponibilă", "settings_upgrade_review_button": "revizuiește", "settings_upgrade_never_show_again": "nu mai afișa", - "site_legal_meta_contributors_link": "Contributori" + "site_legal_meta_contributors_link": "Contributori", + "post_link_required_alert": "Trebuie să postezi un link.", + "post_media_link_required_alert": "Trebuie să postezi un link către o imagine sau un videoclip." } diff --git a/public/translations/ru/default.json b/public/translations/ru/default.json index 1d26b182..35c51d04 100644 --- a/public/translations/ru/default.json +++ b/public/translations/ru/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "обновление настроек доступно", "settings_upgrade_review_button": "просмотреть", "settings_upgrade_never_show_again": "больше не показывать", - "site_legal_meta_contributors_link": "Участники" + "site_legal_meta_contributors_link": "Участники", + "post_link_required_alert": "Вы должны опубликовать ссылку.", + "post_media_link_required_alert": "Вы должны опубликовать ссылку на изображение или видео." } diff --git a/public/translations/sq/default.json b/public/translations/sq/default.json index 55bc3091..0edf695b 100644 --- a/public/translations/sq/default.json +++ b/public/translations/sq/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "përmirësim i cilësimeve i disponueshëm", "settings_upgrade_review_button": "shqyrto", "settings_upgrade_never_show_again": "mos e shfaq më", - "site_legal_meta_contributors_link": "Kontribuesit" + "site_legal_meta_contributors_link": "Kontribuesit", + "post_link_required_alert": "Duhet të postosh një lidhje.", + "post_media_link_required_alert": "Duhet të postosh një lidhje imazhi ose videoje." } diff --git a/public/translations/sv/default.json b/public/translations/sv/default.json index 75a1bcd4..11347a63 100644 --- a/public/translations/sv/default.json +++ b/public/translations/sv/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "inställningsuppgradering tillgänglig", "settings_upgrade_review_button": "granska", "settings_upgrade_never_show_again": "visa inte igen", - "site_legal_meta_contributors_link": "Bidragsgivare" + "site_legal_meta_contributors_link": "Bidragsgivare", + "post_link_required_alert": "Du måste posta en länk.", + "post_media_link_required_alert": "Du måste posta en bild- eller videolänk." } diff --git a/public/translations/te/default.json b/public/translations/te/default.json index ef62b695..9aa54cc3 100644 --- a/public/translations/te/default.json +++ b/public/translations/te/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "సెట్టింగ్‌ల అప్‌గ్రేడ్ అందుబాటులో ఉంది", "settings_upgrade_review_button": "సమీక్షించు", "settings_upgrade_never_show_again": "మళ్లీ చూపించవద్దు", - "site_legal_meta_contributors_link": "సహకారులు" + "site_legal_meta_contributors_link": "సహకారులు", + "post_link_required_alert": "మీరు ఒక లింక్ పోస్ట్ చేయాలి.", + "post_media_link_required_alert": "మీరు చిత్రం లేదా వీడియో లింక్ పోస్ట్ చేయాలి." } diff --git a/public/translations/th/default.json b/public/translations/th/default.json index 515066b9..930e0736 100644 --- a/public/translations/th/default.json +++ b/public/translations/th/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "มีการอัปเกรดการตั้งค่า", "settings_upgrade_review_button": "ตรวจสอบ", "settings_upgrade_never_show_again": "ไม่ต้องแสดงอีก", - "site_legal_meta_contributors_link": "ผู้มีส่วนร่วม" + "site_legal_meta_contributors_link": "ผู้มีส่วนร่วม", + "post_link_required_alert": "คุณต้องโพสต์ลิงก์", + "post_media_link_required_alert": "คุณต้องโพสต์ลิงก์รูปภาพหรือวิดีโอ" } diff --git a/public/translations/tr/default.json b/public/translations/tr/default.json index 585d9356..0f576efa 100644 --- a/public/translations/tr/default.json +++ b/public/translations/tr/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "ayar yükseltmesi mevcut", "settings_upgrade_review_button": "incele", "settings_upgrade_never_show_again": "bir daha gösterme", - "site_legal_meta_contributors_link": "Katkıda Bulunanlar" + "site_legal_meta_contributors_link": "Katkıda Bulunanlar", + "post_link_required_alert": "Bir bağlantı paylaşmalısınız.", + "post_media_link_required_alert": "Bir görsel veya video bağlantısı paylaşmalısınız." } diff --git a/public/translations/uk/default.json b/public/translations/uk/default.json index 710b0db6..30cac0ce 100644 --- a/public/translations/uk/default.json +++ b/public/translations/uk/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "оновлення налаштувань доступне", "settings_upgrade_review_button": "переглянути", "settings_upgrade_never_show_again": "більше не показувати", - "site_legal_meta_contributors_link": "Учасники" + "site_legal_meta_contributors_link": "Учасники", + "post_link_required_alert": "Потрібно опублікувати посилання.", + "post_media_link_required_alert": "Потрібно опублікувати посилання на зображення або відео." } diff --git a/public/translations/ur/default.json b/public/translations/ur/default.json index f32f7903..b5984274 100644 --- a/public/translations/ur/default.json +++ b/public/translations/ur/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "ترتیبات اپ گریڈ دستیاب", "settings_upgrade_review_button": "جائزہ لیں", "settings_upgrade_never_show_again": "دوبارہ نہ دکھائیں", - "site_legal_meta_contributors_link": "شراکت کنندگان" + "site_legal_meta_contributors_link": "شراکت کنندگان", + "post_link_required_alert": "آپ کو ایک لنک پوسٹ کرنا ہوگا۔", + "post_media_link_required_alert": "آپ کو تصویر یا ویڈیو کا لنک پوسٹ کرنا ہوگا۔" } diff --git a/public/translations/vi/default.json b/public/translations/vi/default.json index 23e63e6d..091923ab 100644 --- a/public/translations/vi/default.json +++ b/public/translations/vi/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "nâng cấp cài đặt có sẵn", "settings_upgrade_review_button": "xem lại", "settings_upgrade_never_show_again": "không hiển thị lại", - "site_legal_meta_contributors_link": "Người đóng góp" + "site_legal_meta_contributors_link": "Người đóng góp", + "post_link_required_alert": "Bạn phải đăng một liên kết.", + "post_media_link_required_alert": "Bạn phải đăng liên kết hình ảnh hoặc video." } diff --git a/public/translations/zh/default.json b/public/translations/zh/default.json index 2a356f98..7febfcc5 100644 --- a/public/translations/zh/default.json +++ b/public/translations/zh/default.json @@ -443,5 +443,7 @@ "settings_upgrade_review_notice": "设置升级可用", "settings_upgrade_review_button": "查看", "settings_upgrade_never_show_again": "不再显示", - "site_legal_meta_contributors_link": "贡献者" + "site_legal_meta_contributors_link": "贡献者", + "post_link_required_alert": "你必须发布一个链接。", + "post_media_link_required_alert": "你必须发布图片或视频链接。" } diff --git a/src/components/post-form/__tests__/post-form.test.tsx b/src/components/post-form/__tests__/post-form.test.tsx index 204352d1..4a6027bc 100644 --- a/src/components/post-form/__tests__/post-form.test.tsx +++ b/src/components/post-form/__tests__/post-form.test.tsx @@ -170,8 +170,17 @@ vi.mock('../../../hooks/use-resolved-community-address', () => ({ })); vi.mock('../../../hooks/use-stable-community', () => ({ - useCommunityField: (communityAddress: string | undefined, selector: (community?: { roles?: Record }) => T) => - selector(communityAddress ? { roles: testState.rolesByCommunity[communityAddress] } : undefined), + useCommunityField: (communityAddress: string | undefined, selector: (community?: Record & { roles?: Record }) => T) => { + const community = communityAddress ? (testState.communities[communityAddress] as Record | undefined) : undefined; + return selector( + communityAddress + ? ({ + ...community, + roles: testState.rolesByCommunity[communityAddress], + } as Record & { roles?: Record }) + : undefined, + ); + }, })); vi.mock('../../../hooks/use-fetch-gif-first-frame', () => ({ @@ -765,6 +774,109 @@ describe('PostForm', () => { expect(testState.publishedPostOptions?.content).toBe(youtubeLink); }); + it('requires a link when live community features require post links', async () => { + testState.resolvedCommunityAddress = 'music-posting.eth'; + testState.communities['music-posting.eth'] = { + address: 'music-posting.eth', + features: { requirePostLink: true }, + }; + + await renderPostForm('/mu'); + await clickByText(container, 'start_new_thread'); + + const table = container.querySelector('table') as HTMLTableElement; + const textarea = table.querySelector('textarea') as HTMLTextAreaElement; + const linkInput = table.querySelectorAll('input[type="text"]')[3]; + + await dispatchInput(textarea, 'Link required thread'); + await clickByText(table, 'post'); + + expect(container.textContent).toContain('error: post_link_required_alert'); + const linkRequiredError = Array.from(container.querySelectorAll('div')).find((element) => element.textContent === 'error: post_link_required_alert'); + expect(linkRequiredError?.className).toContain('error'); + expect(linkRequiredError?.className).toContain('formError'); + expect(linkRequiredError?.closest('tfoot')).toBeTruthy(); + expect(testState.publishPostMock).not.toHaveBeenCalled(); + + await dispatchInput(linkInput, 'https://example.com/thread'); + await clickByText(table, 'post'); + + expect(testState.publishPostMock).toHaveBeenCalledTimes(1); + expect(testState.publishedPostOptions?.link).toBe('https://example.com/thread'); + }); + + it('requires a media link when live community features require post links to be media', async () => { + testState.resolvedCommunityAddress = 'music-posting.eth'; + testState.communities['music-posting.eth'] = { + address: 'music-posting.eth', + features: { requirePostLink: true, requirePostLinkIsMedia: true }, + }; + + await renderPostForm('/mu'); + await clickByText(container, 'start_new_thread'); + + const table = container.querySelector('table') as HTMLTableElement; + const textarea = table.querySelector('textarea') as HTMLTextAreaElement; + const linkInput = table.querySelectorAll('input[type="text"]')[3]; + + await dispatchInput(textarea, 'Media required thread'); + await clickByText(table, 'post'); + + expect(container.textContent).toContain('error: post_media_link_required_alert'); + const mediaRequiredError = Array.from(container.querySelectorAll('div')).find((element) => element.textContent === 'error: post_media_link_required_alert'); + expect(mediaRequiredError?.className).toContain('error'); + expect(mediaRequiredError?.className).toContain('formError'); + expect(mediaRequiredError?.closest('tfoot')).toBeTruthy(); + expect(testState.publishPostMock).not.toHaveBeenCalled(); + + await dispatchInput(linkInput, 'https://example.com/page'); + await clickByText(table, 'post'); + + expect(container.textContent).toContain('error: link_not_image_or_video_alert'); + expect(testState.publishPostMock).not.toHaveBeenCalled(); + + await dispatchInput(linkInput, 'https://example.com/image.jpg'); + await clickByText(table, 'post'); + + expect(testState.publishPostMock).toHaveBeenCalledTimes(1); + expect(testState.publishedPostOptions?.link).toBe('https://example.com/image.jpg'); + }); + + it('does not require an empty link when only media links are constrained', async () => { + testState.resolvedCommunityAddress = 'current-news.bso'; + testState.directories.push({ + address: 'current-news.bso', + directoryCode: 'news', + features: { requirePostLink: false, requirePostLinkIsMedia: true }, + title: '/news/ - Current News', + }); + testState.communities['current-news.bso'] = { + address: 'current-news.bso', + features: { requirePostLink: false, requirePostLinkIsMedia: true }, + }; + + await renderPostForm('/news'); + await clickByText(container, 'start_new_thread'); + + const table = container.querySelector('table') as HTMLTableElement; + const textarea = table.querySelector('textarea') as HTMLTextAreaElement; + const linkInput = table.querySelectorAll('input[type="text"]')[3]; + + await dispatchInput(textarea, 'News body with source in text'); + await clickByText(table, 'post'); + + expect(container.textContent).not.toContain('post_media_link_required_alert'); + expect(testState.publishPostMock).toHaveBeenCalledTimes(1); + expect(testState.publishedPostOptions?.link).toBeUndefined(); + + testState.publishPostMock.mockClear(); + await dispatchInput(linkInput, 'https://example.com/article'); + await clickByText(table, 'post'); + + expect(container.textContent).toContain('error: link_not_image_or_video_alert'); + expect(testState.publishPostMock).not.toHaveBeenCalled(); + }); + it('ignores duplicate post clicks while youtube thumbnail resolution is pending', async () => { const youtubeLink = 'https://www.youtube.com/watch?v=slow123'; const thumbnailLink = 'https://img.youtube.com/vi/slow123/maxresdefault.jpg'; diff --git a/src/components/post-form/post-form.tsx b/src/components/post-form/post-form.tsx index 78236fc9..7738d305 100644 --- a/src/components/post-form/post-form.tsx +++ b/src/components/post-form/post-form.tsx @@ -6,13 +6,8 @@ import { Comment, setAccount, useAccount, useAccountComment, useEditedComment } import getShortAddress from '../../lib/get-short-address'; import { communitiesPagesStore as useCommunitiesPagesStore } from '../../lib/bitsocial-internals/stores'; import { getDisplayMediaInfoType, getLinkMediaInfo, getTwimgMediaFilePublishUrl } from '../../lib/utils/media-utils'; -import { - getExpiringMediaLinkAlert, - getPublishFileDisplayName, - getPublishLinkOptions, - isPublishFileMediaLink, - isPublishFileMediaType, -} from '../../lib/utils/media-link-validation-utils'; +import { getExpiringMediaLinkAlert, getPublishFileDisplayName, getPublishLinkOptions, isPublishFileMediaType } from '../../lib/utils/media-link-validation-utils'; +import { getEffectivePublishLinkFeatures, getPublishLinkValidationError, getRequirePostLink, getRequirePostLinkIsMedia } from '../../lib/utils/publish-link-requirements'; import { type DiceRoll, type FortuneEntry, @@ -27,7 +22,7 @@ import { isPostOptionsValidationError, } from '../../lib/utils/post-options-utils'; import { truncateWithEllipsisInMiddle } from '../../lib/utils/string-utils'; -import { isValidPublishURL, isValidURL } from '../../lib/utils/url-utils'; +import { isValidURL } from '../../lib/utils/url-utils'; import { getModerationPostingRoleLabel } from '../../lib/utils/author-display-utils'; import { hasModQueueAccessRole } from '../../lib/utils/mod-access'; import { getBoardPath, isDirectoryRoute } from '../../lib/utils/route-utils'; @@ -577,8 +572,10 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid: const showSpoilerForReply = directoryEntry?.features?.noSpoilerReplies !== true; const postOptionsDirectoryCode = getPostOptionsDirectoryCode(directoryEntry, location.pathname); const showOekakiControls = postOptionsDirectoryCode === 'i' || directoryEntry?.directoryCode === 'i'; - const requirePostLinkIsMediaFeature = directoryEntry?.features?.requirePostLinkIsMedia; - const requirePostLinkIsMedia = requirePostLinkIsMediaFeature === true || (requirePostLinkIsMediaFeature === undefined && (isInAllView || isInSubscriptionsView)); + const communityFeatures = useCommunityField(effectiveBoardAddress, (community) => community?.features); + const publishLinkFeatures = getEffectivePublishLinkFeatures(communityFeatures, directoryEntry?.features); + const requirePostLink = getRequirePostLink(publishLinkFeatures); + const requirePostLinkIsMedia = getRequirePostLinkIsMedia(publishLinkFeatures, isInAllView || isInSubscriptionsView); const flagOptions = getCommentFlagOptionsForDirectory(directoryEntry); const showFlashUploadPrompt = isFlashDirectoryCode(postOptionsDirectoryCode); const showFlashTagSelector = showFlashUploadPrompt && !isInPostView; @@ -680,12 +677,14 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid: setFormError(`${t('error')}: ${t('empty_comment_alert')}`); return; } - if (currentUrl && !isValidPublishURL(currentUrl)) { - setFormError(`${t('error')}: ${t('invalid_url_alert')}`); - return; - } - if (currentUrl && requirePostLinkIsMedia && !isPublishFileMediaLink(currentUrl)) { - setFormError(`${t('error')}: ${t('link_not_image_or_video_alert')}`); + const linkValidationError = getPublishLinkValidationError({ + link: currentUrl, + requireLink: requirePostLink, + requireMedia: requirePostLinkIsMedia, + t, + }); + if (linkValidationError) { + setFormError(linkValidationError); return; } const expiringMediaLinkAlert = currentUrl ? getExpiringMediaLinkAlert(currentUrl, t) : null; @@ -809,12 +808,14 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid: return; } - if (currentUrl && !isValidPublishURL(currentUrl)) { - setFormError(`${t('error')}: ${t('invalid_url_alert')}`); - return; - } - if (currentUrl && requirePostLinkIsMedia && !isPublishFileMediaLink(currentUrl)) { - setFormError(`${t('error')}: ${t('link_not_image_or_video_alert')}`); + const linkValidationError = getPublishLinkValidationError({ + link: currentUrl, + requireLink: requirePostLink, + requireMedia: requirePostLinkIsMedia, + t, + }); + if (linkValidationError) { + setFormError(linkValidationError); return; } const expiringMediaLinkAlert = currentUrl ? getExpiringMediaLinkAlert(currentUrl, t) : null; diff --git a/src/components/reply-modal/__tests__/reply-modal.test.tsx b/src/components/reply-modal/__tests__/reply-modal.test.tsx index 48fd3a03..89fa3608 100644 --- a/src/components/reply-modal/__tests__/reply-modal.test.tsx +++ b/src/components/reply-modal/__tests__/reply-modal.test.tsx @@ -61,7 +61,7 @@ const testState = vi.hoisted(() => ({ 'music-posting.eth': { address: 'music-posting.eth', }, - } as Record, + } as Record }>, fetchMock: vi.fn(), showUploadControls: true, uploadComplete: undefined as ((url: string) => void) | undefined, @@ -187,8 +187,18 @@ vi.mock('../../../hooks/use-resolved-community-address', () => ({ })); vi.mock('../../../hooks/use-stable-community', () => ({ - useCommunityField: (communityAddress: string | undefined, selector: (community?: { roles?: Record }) => T) => - selector(communityAddress ? { roles: testState.rolesByCommunity[communityAddress] } : undefined), + useCommunityField: ( + communityAddress: string | undefined, + selector: (community?: { features?: Record; roles?: Record }) => T, + ) => + selector( + communityAddress + ? { + ...testState.communities[communityAddress], + roles: testState.rolesByCommunity[communityAddress], + } + : undefined, + ), })); vi.mock('../../../hooks/use-publish-reply', async () => { @@ -755,6 +765,86 @@ describe('ReplyModal', () => { expect(testState.publishReplyMock).toHaveBeenCalledTimes(1); }); + it('requires a link when live community features require post links', async () => { + testState.openEmpty = true; + testState.selectedText = 'Reply body'; + testState.communities['music-posting.eth'] = { + address: 'music-posting.eth', + features: { requirePostLink: true }, + }; + + await renderReplyModal('/mu/thread/post-1'); + + await clickButtonByText('post'); + + expect(container.textContent).toContain('error: post_link_required_alert'); + const linkRequiredError = Array.from(container.querySelectorAll('div')).find((element) => element.textContent === 'error: post_link_required_alert'); + expect(linkRequiredError?.className).toContain('error'); + expect(testState.publishReplyMock).not.toHaveBeenCalled(); + + const linkInput = container.querySelectorAll('input[type="text"]')[2]; + await dispatchInput(linkInput, 'https://example.com/reply'); + await clickButtonByText('post'); + + expect(testState.setPublishReplyOptionsMock).toHaveBeenCalledWith({ link: 'https://example.com/reply' }); + expect(testState.publishReplyMock).toHaveBeenCalledTimes(1); + }); + + it('requires a media link when live community features require post links to be media', async () => { + testState.openEmpty = true; + testState.selectedText = 'Reply body'; + testState.communities['music-posting.eth'] = { + address: 'music-posting.eth', + features: { requirePostLink: true, requirePostLinkIsMedia: true }, + }; + + await renderReplyModal('/mu/thread/post-1'); + + await clickButtonByText('post'); + + expect(container.textContent).toContain('error: post_media_link_required_alert'); + const mediaRequiredError = Array.from(container.querySelectorAll('div')).find((element) => element.textContent === 'error: post_media_link_required_alert'); + expect(mediaRequiredError?.className).toContain('error'); + expect(testState.publishReplyMock).not.toHaveBeenCalled(); + + const linkInput = container.querySelectorAll('input[type="text"]')[2]; + await dispatchInput(linkInput, 'https://example.com/page'); + await clickButtonByText('post'); + + expect(container.textContent).toContain('error: link_not_image_or_video_alert'); + expect(testState.publishReplyMock).not.toHaveBeenCalled(); + + await dispatchInput(linkInput, 'https://example.com/reply.png'); + await clickButtonByText('post'); + + expect(testState.setPublishReplyOptionsMock).toHaveBeenCalledWith({ link: 'https://example.com/reply.png' }); + expect(testState.publishReplyMock).toHaveBeenCalledTimes(1); + }); + + it('does not require an empty link when only media links are constrained', async () => { + testState.openEmpty = true; + testState.selectedText = 'Reply body'; + testState.communities['music-posting.eth'] = { + address: 'music-posting.eth', + features: { requirePostLink: false, requirePostLinkIsMedia: true }, + }; + + await renderReplyModal('/mu/thread/post-1'); + + await clickButtonByText('post'); + + expect(container.textContent).not.toContain('post_media_link_required_alert'); + expect(testState.publishReplyMock).toHaveBeenCalledTimes(1); + + testState.publishReplyMock.mockClear(); + const linkInput = container.querySelectorAll('input[type="text"]')[2]; + await dispatchInput(linkInput, 'https://example.com/page'); + await clickButtonByText('post'); + + expect(container.textContent).toContain('error: link_not_image_or_video_alert'); + expect(testState.publishReplyMock).not.toHaveBeenCalled(); + }); + it('publishes twimg query-format reply links with a path extension', async () => { testState.openEmpty = true; testState.selectedText = 'reply body'; diff --git a/src/components/reply-modal/reply-modal.tsx b/src/components/reply-modal/reply-modal.tsx index c61d146c..c3ebcd44 100644 --- a/src/components/reply-modal/reply-modal.tsx +++ b/src/components/reply-modal/reply-modal.tsx @@ -3,8 +3,9 @@ import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import type { TFunction } from 'i18next'; import { setAccount, useAccount } from '@bitsocial/bitsocial-react-hooks'; -import { getExpiringMediaLinkAlert, getPublishFileDisplayName, getPublishLinkOptions, isPublishFileMediaLink } from '../../lib/utils/media-link-validation-utils'; +import { getExpiringMediaLinkAlert, getPublishFileDisplayName, getPublishLinkOptions } from '../../lib/utils/media-link-validation-utils'; import { getTwimgMediaFilePublishUrl } from '../../lib/utils/media-utils'; +import { getEffectivePublishLinkFeatures, getPublishLinkValidationError, getRequirePostLink, getRequirePostLinkIsMedia } from '../../lib/utils/publish-link-requirements'; import { getCommentFlagOptionsForDirectory, getCommentFlagPublishOptionsForDirectory } from '../../lib/comment-flag-selection'; import { type DiceRoll, @@ -18,7 +19,6 @@ import { hasNonokoOption, isPostOptionsValidationError, } from '../../lib/utils/post-options-utils'; -import { isValidPublishURL } from '../../lib/utils/url-utils'; import { isMathDirectoryCode } from '../../lib/math-tags'; import { hasModQueueAccessRole } from '../../lib/utils/mod-access'; import { getModerationPostingRoleLabel } from '../../lib/utils/author-display-utils'; @@ -78,8 +78,10 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa const postOptionsDirectoryCode = getPostOptionsDirectoryCode(directoryEntry, location.pathname); const showOekakiControls = postOptionsDirectoryCode === 'i' || directoryEntry?.directoryCode === 'i'; const showTexButton = isMathDirectoryCode(postOptionsDirectoryCode) || isMathDirectoryCode(directoryEntry?.directoryCode); - const requirePostLinkIsMediaFeature = directoryEntry?.features?.requirePostLinkIsMedia; - const requirePostLinkIsMedia = requirePostLinkIsMediaFeature === true || (requirePostLinkIsMediaFeature === undefined && (isInAllView || isInSubscriptionsView)); + const communityFeatures = useCommunityField(communityAddress, (community) => community?.features); + const publishLinkFeatures = getEffectivePublishLinkFeatures(communityFeatures, directoryEntry?.features); + const requirePostLink = getRequirePostLink(publishLinkFeatures); + const requirePostLinkIsMedia = getRequirePostLinkIsMedia(publishLinkFeatures, isInAllView || isInSubscriptionsView); const flagOptions = getCommentFlagOptionsForDirectory(directoryEntry); const { isResolvingExternalQuotes, publishReply, publishReplyError, publishReplyStateMessage, resetPublishReplyOptions, replyIndex, setPublishReplyOptions } = usePublishReply({ @@ -184,12 +186,14 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa return; } - if (currentUrl && !isValidPublishURL(currentUrl)) { - setError(t('error') + ': ' + t('invalid_url_alert')); - return; - } - if (currentUrl && requirePostLinkIsMedia && !isPublishFileMediaLink(currentUrl)) { - setError(t('error') + ': ' + t('link_not_image_or_video_alert')); + const linkValidationError = getPublishLinkValidationError({ + link: currentUrl, + requireLink: requirePostLink, + requireMedia: requirePostLinkIsMedia, + t, + }); + if (linkValidationError) { + setError(linkValidationError); return; } const expiringMediaLinkAlert = currentUrl ? getExpiringMediaLinkAlert(currentUrl, t) : null; diff --git a/src/lib/utils/__tests__/publish-link-requirements.test.ts b/src/lib/utils/__tests__/publish-link-requirements.test.ts new file mode 100644 index 00000000..e6426850 --- /dev/null +++ b/src/lib/utils/__tests__/publish-link-requirements.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from 'vitest'; +import type { TFunction } from 'i18next'; +import { getEffectivePublishLinkFeatures, getPublishLinkValidationError, getRequirePostLink, getRequirePostLinkIsMedia } from '../publish-link-requirements'; + +const t = ((key: string) => key) as TFunction; + +describe('getEffectivePublishLinkFeatures', () => { + it('uses live community feature values before directory defaults', () => { + expect(getEffectivePublishLinkFeatures({ requirePostLink: false }, { requirePostLink: true, requirePostLinkIsMedia: true })).toEqual({ + requirePostLink: false, + requirePostLinkIsMedia: true, + }); + }); + + it('falls back per feature when live community features are partial', () => { + expect(getEffectivePublishLinkFeatures({ requirePostLinkIsMedia: false }, { requirePostLink: true, requirePostLinkIsMedia: true })).toEqual({ + requirePostLink: true, + requirePostLinkIsMedia: false, + }); + }); +}); + +describe('getPublishLinkValidationError', () => { + it('does not require an empty link when only media links are enabled', () => { + expect(getPublishLinkValidationError({ link: '', requireLink: false, requireMedia: true, t })).toBeNull(); + }); + + it('requires a media link when both link and media requirements are enabled', () => { + expect(getPublishLinkValidationError({ link: '', requireLink: true, requireMedia: true, t })).toBe('error: post_media_link_required_alert'); + }); +}); + +describe('publish link feature helpers', () => { + it('does not treat media-only validation as a required link', () => { + const features = getEffectivePublishLinkFeatures({ requirePostLink: false, requirePostLinkIsMedia: true }, undefined); + + expect(getRequirePostLink(features)).toBe(false); + expect(getRequirePostLinkIsMedia(features, false)).toBe(true); + }); +}); diff --git a/src/lib/utils/publish-link-requirements.ts b/src/lib/utils/publish-link-requirements.ts new file mode 100644 index 00000000..d7c98b07 --- /dev/null +++ b/src/lib/utils/publish-link-requirements.ts @@ -0,0 +1,54 @@ +import type { TFunction } from 'i18next'; +import { isPublishFileMediaLink } from './media-link-validation-utils'; +import { isValidPublishURL } from './url-utils'; + +interface PublishLinkFeatures { + requirePostLink?: unknown; + requirePostLinkIsMedia?: unknown; +} + +export const getEffectivePublishLinkFeatures = ( + communityFeatures: PublishLinkFeatures | undefined, + directoryFeatures: PublishLinkFeatures | undefined, +): PublishLinkFeatures | undefined => { + const requirePostLink = communityFeatures?.requirePostLink ?? directoryFeatures?.requirePostLink; + const requirePostLinkIsMedia = communityFeatures?.requirePostLinkIsMedia ?? directoryFeatures?.requirePostLinkIsMedia; + + if (requirePostLink === undefined && requirePostLinkIsMedia === undefined) { + return undefined; + } + + return { + requirePostLink, + requirePostLinkIsMedia, + }; +}; + +export const getRequirePostLink = (features: PublishLinkFeatures | undefined): boolean => Boolean(features?.requirePostLink); + +export const getRequirePostLinkIsMedia = (features: PublishLinkFeatures | undefined, useMediaDefault: boolean): boolean => + Boolean(features?.requirePostLinkIsMedia) || (features?.requirePostLinkIsMedia === undefined && useMediaDefault); + +export const getPublishLinkValidationError = ({ + link, + requireLink, + requireMedia, + t, +}: { + link: string; + requireLink: boolean; + requireMedia: boolean; + t: TFunction; +}): string | null => { + if (!link && requireLink) { + return `${t('error')}: ${t(requireMedia ? 'post_media_link_required_alert' : 'post_link_required_alert')}`; + } + if (link && !isValidPublishURL(link)) { + return `${t('error')}: ${t('invalid_url_alert')}`; + } + if (link && requireMedia && !isPublishFileMediaLink(link)) { + return `${t('error')}: ${t('link_not_image_or_video_alert')}`; + } + + return null; +};