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.
This commit is contained in:
Tommaso Casaburi
2026-06-28 15:44:36 +07:00
parent b974be7b2f
commit 6b2b85f51e
41 changed files with 443 additions and 72 deletions
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "ترقية الإعدادات متاحة", "settings_upgrade_review_notice": "ترقية الإعدادات متاحة",
"settings_upgrade_review_button": "مراجعة", "settings_upgrade_review_button": "مراجعة",
"settings_upgrade_never_show_again": "لا تظهر مرة أخرى", "settings_upgrade_never_show_again": "لا تظهر مرة أخرى",
"site_legal_meta_contributors_link": "المساهمون" "site_legal_meta_contributors_link": "المساهمون",
"post_link_required_alert": "يجب أن تنشر رابطًا.",
"post_media_link_required_alert": "يجب أن تنشر رابط صورة أو فيديو."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "সেটিংস আপগ্রেড উপলব্ধ", "settings_upgrade_review_notice": "সেটিংস আপগ্রেড উপলব্ধ",
"settings_upgrade_review_button": "পর্যালোচনা", "settings_upgrade_review_button": "পর্যালোচনা",
"settings_upgrade_never_show_again": "আর দেখাবেন না", "settings_upgrade_never_show_again": "আর দেখাবেন না",
"site_legal_meta_contributors_link": "অবদানকারীরা" "site_legal_meta_contributors_link": "অবদানকারীরা",
"post_link_required_alert": "আপনাকে একটি লিঙ্ক পোস্ট করতে হবে।",
"post_media_link_required_alert": "আপনাকে একটি ছবি বা ভিডিওর লিঙ্ক পোস্ট করতে হবে।"
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "upgrade nastavení k dispozici", "settings_upgrade_review_notice": "upgrade nastavení k dispozici",
"settings_upgrade_review_button": "zkontrolovat", "settings_upgrade_review_button": "zkontrolovat",
"settings_upgrade_never_show_again": "už nezobrazovat", "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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "indstillingsopgradering tilgængelig", "settings_upgrade_review_notice": "indstillingsopgradering tilgængelig",
"settings_upgrade_review_button": "gennemgå", "settings_upgrade_review_button": "gennemgå",
"settings_upgrade_never_show_again": "vis aldrig igen", "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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "Einstellungs-Upgrade verfügbar", "settings_upgrade_review_notice": "Einstellungs-Upgrade verfügbar",
"settings_upgrade_review_button": "überprüfen", "settings_upgrade_review_button": "überprüfen",
"settings_upgrade_never_show_again": "nie wieder anzeigen", "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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "αναβάθμιση ρυθμίσεων διαθέσιμη", "settings_upgrade_review_notice": "αναβάθμιση ρυθμίσεων διαθέσιμη",
"settings_upgrade_review_button": "έλεγχος", "settings_upgrade_review_button": "έλεγχος",
"settings_upgrade_never_show_again": "να μην εμφανιστεί ξανά", "settings_upgrade_never_show_again": "να μην εμφανιστεί ξανά",
"site_legal_meta_contributors_link": "Συνεισφέροντες" "site_legal_meta_contributors_link": "Συνεισφέροντες",
"post_link_required_alert": "Πρέπει να δημοσιεύσετε έναν σύνδεσμο.",
"post_media_link_required_alert": "Πρέπει να δημοσιεύσετε σύνδεσμο εικόνας ή βίντεο."
} }
+3 -1
View File
@@ -462,5 +462,7 @@
"settings_upgrade_review_notice": "settings upgrade available", "settings_upgrade_review_notice": "settings upgrade available",
"settings_upgrade_review_button": "review", "settings_upgrade_review_button": "review",
"settings_upgrade_never_show_again": "never show again", "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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "actualización de configuración disponible", "settings_upgrade_review_notice": "actualización de configuración disponible",
"settings_upgrade_review_button": "revisar", "settings_upgrade_review_button": "revisar",
"settings_upgrade_never_show_again": "no volver a mostrar", "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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "ارتقای تنظیمات موجود است", "settings_upgrade_review_notice": "ارتقای تنظیمات موجود است",
"settings_upgrade_review_button": "بررسی", "settings_upgrade_review_button": "بررسی",
"settings_upgrade_never_show_again": "دیگر نشان نده", "settings_upgrade_never_show_again": "دیگر نشان نده",
"site_legal_meta_contributors_link": "مشارکت‌کنندگان" "site_legal_meta_contributors_link": "مشارکت‌کنندگان",
"post_link_required_alert": "باید یک پیوند ارسال کنید.",
"post_media_link_required_alert": "باید پیوند یک تصویر یا ویدیو را ارسال کنید."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "asetuspäivitys saatavilla", "settings_upgrade_review_notice": "asetuspäivitys saatavilla",
"settings_upgrade_review_button": "tarkista", "settings_upgrade_review_button": "tarkista",
"settings_upgrade_never_show_again": "älä näytä uudelleen", "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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "may upgrade ng setting", "settings_upgrade_review_notice": "may upgrade ng setting",
"settings_upgrade_review_button": "suriin", "settings_upgrade_review_button": "suriin",
"settings_upgrade_never_show_again": "huwag nang ipakita muli", "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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "mise à jour des paramètres disponible", "settings_upgrade_review_notice": "mise à jour des paramètres disponible",
"settings_upgrade_review_button": "examiner", "settings_upgrade_review_button": "examiner",
"settings_upgrade_never_show_again": "ne plus afficher", "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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "שדרוג הגדרות זמין", "settings_upgrade_review_notice": "שדרוג הגדרות זמין",
"settings_upgrade_review_button": "סקור", "settings_upgrade_review_button": "סקור",
"settings_upgrade_never_show_again": "לא להציג שוב", "settings_upgrade_never_show_again": "לא להציג שוב",
"site_legal_meta_contributors_link": "תורמים" "site_legal_meta_contributors_link": "תורמים",
"post_link_required_alert": "עליך לפרסם קישור.",
"post_media_link_required_alert": "עליך לפרסם קישור לתמונה או לסרטון."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "सेटिंग्स अपग्रेड उपलब्ध", "settings_upgrade_review_notice": "सेटिंग्स अपग्रेड उपलब्ध",
"settings_upgrade_review_button": "समीक्षा करें", "settings_upgrade_review_button": "समीक्षा करें",
"settings_upgrade_never_show_again": "फिर कभी न दिखाएँ", "settings_upgrade_never_show_again": "फिर कभी न दिखाएँ",
"site_legal_meta_contributors_link": "योगदानकर्ता" "site_legal_meta_contributors_link": "योगदानकर्ता",
"post_link_required_alert": "आपको एक लिंक पोस्ट करना होगा।",
"post_media_link_required_alert": "आपको एक छवि या वीडियो लिंक पोस्ट करना होगा।"
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "beállításfrissítés elérhető", "settings_upgrade_review_notice": "beállításfrissítés elérhető",
"settings_upgrade_review_button": "áttekintés", "settings_upgrade_review_button": "áttekintés",
"settings_upgrade_never_show_again": "ne jelenjen meg újra", "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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "pembaruan pengaturan tersedia", "settings_upgrade_review_notice": "pembaruan pengaturan tersedia",
"settings_upgrade_review_button": "tinjau", "settings_upgrade_review_button": "tinjau",
"settings_upgrade_never_show_again": "jangan tampilkan lagi", "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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "aggiornamento impostazioni disponibile", "settings_upgrade_review_notice": "aggiornamento impostazioni disponibile",
"settings_upgrade_review_button": "rivedi", "settings_upgrade_review_button": "rivedi",
"settings_upgrade_never_show_again": "non mostrare più", "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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "設定のアップグレードが利用可能", "settings_upgrade_review_notice": "設定のアップグレードが利用可能",
"settings_upgrade_review_button": "確認", "settings_upgrade_review_button": "確認",
"settings_upgrade_never_show_again": "今後表示しない", "settings_upgrade_never_show_again": "今後表示しない",
"site_legal_meta_contributors_link": "貢献者" "site_legal_meta_contributors_link": "貢献者",
"post_link_required_alert": "リンクを投稿する必要があります。",
"post_media_link_required_alert": "画像または動画のリンクを投稿する必要があります。"
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "설정 업그레이드 사용 가능", "settings_upgrade_review_notice": "설정 업그레이드 사용 가능",
"settings_upgrade_review_button": "검토", "settings_upgrade_review_button": "검토",
"settings_upgrade_never_show_again": "다시 표시하지 않기", "settings_upgrade_never_show_again": "다시 표시하지 않기",
"site_legal_meta_contributors_link": "기여자" "site_legal_meta_contributors_link": "기여자",
"post_link_required_alert": "링크를 게시해야 합니다.",
"post_media_link_required_alert": "이미지 또는 동영상 링크를 게시해야 합니다."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "सेटिंग्ज अपग्रेड उपलब्ध", "settings_upgrade_review_notice": "सेटिंग्ज अपग्रेड उपलब्ध",
"settings_upgrade_review_button": "पुनरावलोकन", "settings_upgrade_review_button": "पुनरावलोकन",
"settings_upgrade_never_show_again": "पुन्हा दाखवू नका", "settings_upgrade_never_show_again": "पुन्हा दाखवू नका",
"site_legal_meta_contributors_link": "योगदानकर्ते" "site_legal_meta_contributors_link": "योगदानकर्ते",
"post_link_required_alert": "तुम्हाला एक लिंक पोस्ट करावी लागेल.",
"post_media_link_required_alert": "तुम्हाला प्रतिमा किंवा व्हिडिओची लिंक पोस्ट करावी लागेल."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "instellingenupgrade beschikbaar", "settings_upgrade_review_notice": "instellingenupgrade beschikbaar",
"settings_upgrade_review_button": "bekijken", "settings_upgrade_review_button": "bekijken",
"settings_upgrade_never_show_again": "niet meer tonen", "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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "innstillingsoppgradering tilgjengelig", "settings_upgrade_review_notice": "innstillingsoppgradering tilgjengelig",
"settings_upgrade_review_button": "se gjennom", "settings_upgrade_review_button": "se gjennom",
"settings_upgrade_never_show_again": "vis aldri igjen", "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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "aktualizacja ustawień dostępna", "settings_upgrade_review_notice": "aktualizacja ustawień dostępna",
"settings_upgrade_review_button": "przejrzyj", "settings_upgrade_review_button": "przejrzyj",
"settings_upgrade_never_show_again": "nie pokazuj ponownie", "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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "atualização de configurações disponível", "settings_upgrade_review_notice": "atualização de configurações disponível",
"settings_upgrade_review_button": "revisar", "settings_upgrade_review_button": "revisar",
"settings_upgrade_never_show_again": "não mostrar novamente", "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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "actualizare setări disponibilă", "settings_upgrade_review_notice": "actualizare setări disponibilă",
"settings_upgrade_review_button": "revizuiește", "settings_upgrade_review_button": "revizuiește",
"settings_upgrade_never_show_again": "nu mai afișa", "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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "обновление настроек доступно", "settings_upgrade_review_notice": "обновление настроек доступно",
"settings_upgrade_review_button": "просмотреть", "settings_upgrade_review_button": "просмотреть",
"settings_upgrade_never_show_again": "больше не показывать", "settings_upgrade_never_show_again": "больше не показывать",
"site_legal_meta_contributors_link": "Участники" "site_legal_meta_contributors_link": "Участники",
"post_link_required_alert": "Вы должны опубликовать ссылку.",
"post_media_link_required_alert": "Вы должны опубликовать ссылку на изображение или видео."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "përmirësim i cilësimeve i disponueshëm", "settings_upgrade_review_notice": "përmirësim i cilësimeve i disponueshëm",
"settings_upgrade_review_button": "shqyrto", "settings_upgrade_review_button": "shqyrto",
"settings_upgrade_never_show_again": "mos e shfaq më", "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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "inställningsuppgradering tillgänglig", "settings_upgrade_review_notice": "inställningsuppgradering tillgänglig",
"settings_upgrade_review_button": "granska", "settings_upgrade_review_button": "granska",
"settings_upgrade_never_show_again": "visa inte igen", "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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "సెట్టింగ్‌ల అప్‌గ్రేడ్ అందుబాటులో ఉంది", "settings_upgrade_review_notice": "సెట్టింగ్‌ల అప్‌గ్రేడ్ అందుబాటులో ఉంది",
"settings_upgrade_review_button": "సమీక్షించు", "settings_upgrade_review_button": "సమీక్షించు",
"settings_upgrade_never_show_again": "మళ్లీ చూపించవద్దు", "settings_upgrade_never_show_again": "మళ్లీ చూపించవద్దు",
"site_legal_meta_contributors_link": "సహకారులు" "site_legal_meta_contributors_link": "సహకారులు",
"post_link_required_alert": "మీరు ఒక లింక్ పోస్ట్ చేయాలి.",
"post_media_link_required_alert": "మీరు చిత్రం లేదా వీడియో లింక్ పోస్ట్ చేయాలి."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "มีการอัปเกรดการตั้งค่า", "settings_upgrade_review_notice": "มีการอัปเกรดการตั้งค่า",
"settings_upgrade_review_button": "ตรวจสอบ", "settings_upgrade_review_button": "ตรวจสอบ",
"settings_upgrade_never_show_again": "ไม่ต้องแสดงอีก", "settings_upgrade_never_show_again": "ไม่ต้องแสดงอีก",
"site_legal_meta_contributors_link": "ผู้มีส่วนร่วม" "site_legal_meta_contributors_link": "ผู้มีส่วนร่วม",
"post_link_required_alert": "คุณต้องโพสต์ลิงก์",
"post_media_link_required_alert": "คุณต้องโพสต์ลิงก์รูปภาพหรือวิดีโอ"
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "ayar yükseltmesi mevcut", "settings_upgrade_review_notice": "ayar yükseltmesi mevcut",
"settings_upgrade_review_button": "incele", "settings_upgrade_review_button": "incele",
"settings_upgrade_never_show_again": "bir daha gösterme", "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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "оновлення налаштувань доступне", "settings_upgrade_review_notice": "оновлення налаштувань доступне",
"settings_upgrade_review_button": "переглянути", "settings_upgrade_review_button": "переглянути",
"settings_upgrade_never_show_again": "більше не показувати", "settings_upgrade_never_show_again": "більше не показувати",
"site_legal_meta_contributors_link": "Учасники" "site_legal_meta_contributors_link": "Учасники",
"post_link_required_alert": "Потрібно опублікувати посилання.",
"post_media_link_required_alert": "Потрібно опублікувати посилання на зображення або відео."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "ترتیبات اپ گریڈ دستیاب", "settings_upgrade_review_notice": "ترتیبات اپ گریڈ دستیاب",
"settings_upgrade_review_button": "جائزہ لیں", "settings_upgrade_review_button": "جائزہ لیں",
"settings_upgrade_never_show_again": "دوبارہ نہ دکھائیں", "settings_upgrade_never_show_again": "دوبارہ نہ دکھائیں",
"site_legal_meta_contributors_link": "شراکت کنندگان" "site_legal_meta_contributors_link": "شراکت کنندگان",
"post_link_required_alert": "آپ کو ایک لنک پوسٹ کرنا ہوگا۔",
"post_media_link_required_alert": "آپ کو تصویر یا ویڈیو کا لنک پوسٹ کرنا ہوگا۔"
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "nâng cấp cài đặt có sẵn", "settings_upgrade_review_notice": "nâng cấp cài đặt có sẵn",
"settings_upgrade_review_button": "xem lại", "settings_upgrade_review_button": "xem lại",
"settings_upgrade_never_show_again": "không hiển thị 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."
} }
+3 -1
View File
@@ -443,5 +443,7 @@
"settings_upgrade_review_notice": "设置升级可用", "settings_upgrade_review_notice": "设置升级可用",
"settings_upgrade_review_button": "查看", "settings_upgrade_review_button": "查看",
"settings_upgrade_never_show_again": "不再显示", "settings_upgrade_never_show_again": "不再显示",
"site_legal_meta_contributors_link": "贡献者" "site_legal_meta_contributors_link": "贡献者",
"post_link_required_alert": "你必须发布一个链接。",
"post_media_link_required_alert": "你必须发布图片或视频链接。"
} }
@@ -170,8 +170,17 @@ vi.mock('../../../hooks/use-resolved-community-address', () => ({
})); }));
vi.mock('../../../hooks/use-stable-community', () => ({ vi.mock('../../../hooks/use-stable-community', () => ({
useCommunityField: <T,>(communityAddress: string | undefined, selector: (community?: { roles?: Record<string, { role?: string }> }) => T) => useCommunityField: <T,>(communityAddress: string | undefined, selector: (community?: Record<string, unknown> & { roles?: Record<string, { role?: string }> }) => T) => {
selector(communityAddress ? { roles: testState.rolesByCommunity[communityAddress] } : undefined), const community = communityAddress ? (testState.communities[communityAddress] as Record<string, unknown> | undefined) : undefined;
return selector(
communityAddress
? ({
...community,
roles: testState.rolesByCommunity[communityAddress],
} as Record<string, unknown> & { roles?: Record<string, { role?: string }> })
: undefined,
);
},
})); }));
vi.mock('../../../hooks/use-fetch-gif-first-frame', () => ({ vi.mock('../../../hooks/use-fetch-gif-first-frame', () => ({
@@ -765,6 +774,109 @@ describe('PostForm', () => {
expect(testState.publishedPostOptions?.content).toBe(youtubeLink); 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<HTMLInputElement>('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<HTMLInputElement>('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<HTMLInputElement>('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 () => { it('ignores duplicate post clicks while youtube thumbnail resolution is pending', async () => {
const youtubeLink = 'https://www.youtube.com/watch?v=slow123'; const youtubeLink = 'https://www.youtube.com/watch?v=slow123';
const thumbnailLink = 'https://img.youtube.com/vi/slow123/maxresdefault.jpg'; const thumbnailLink = 'https://img.youtube.com/vi/slow123/maxresdefault.jpg';
+23 -22
View File
@@ -6,13 +6,8 @@ import { Comment, setAccount, useAccount, useAccountComment, useEditedComment }
import getShortAddress from '../../lib/get-short-address'; import getShortAddress from '../../lib/get-short-address';
import { communitiesPagesStore as useCommunitiesPagesStore } from '../../lib/bitsocial-internals/stores'; import { communitiesPagesStore as useCommunitiesPagesStore } from '../../lib/bitsocial-internals/stores';
import { getDisplayMediaInfoType, getLinkMediaInfo, getTwimgMediaFilePublishUrl } from '../../lib/utils/media-utils'; import { getDisplayMediaInfoType, getLinkMediaInfo, getTwimgMediaFilePublishUrl } from '../../lib/utils/media-utils';
import { import { getExpiringMediaLinkAlert, getPublishFileDisplayName, getPublishLinkOptions, isPublishFileMediaType } from '../../lib/utils/media-link-validation-utils';
getExpiringMediaLinkAlert, import { getEffectivePublishLinkFeatures, getPublishLinkValidationError, getRequirePostLink, getRequirePostLinkIsMedia } from '../../lib/utils/publish-link-requirements';
getPublishFileDisplayName,
getPublishLinkOptions,
isPublishFileMediaLink,
isPublishFileMediaType,
} from '../../lib/utils/media-link-validation-utils';
import { import {
type DiceRoll, type DiceRoll,
type FortuneEntry, type FortuneEntry,
@@ -27,7 +22,7 @@ import {
isPostOptionsValidationError, isPostOptionsValidationError,
} from '../../lib/utils/post-options-utils'; } from '../../lib/utils/post-options-utils';
import { truncateWithEllipsisInMiddle } from '../../lib/utils/string-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 { getModerationPostingRoleLabel } from '../../lib/utils/author-display-utils';
import { hasModQueueAccessRole } from '../../lib/utils/mod-access'; import { hasModQueueAccessRole } from '../../lib/utils/mod-access';
import { getBoardPath, isDirectoryRoute } from '../../lib/utils/route-utils'; 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 showSpoilerForReply = directoryEntry?.features?.noSpoilerReplies !== true;
const postOptionsDirectoryCode = getPostOptionsDirectoryCode(directoryEntry, location.pathname); const postOptionsDirectoryCode = getPostOptionsDirectoryCode(directoryEntry, location.pathname);
const showOekakiControls = postOptionsDirectoryCode === 'i' || directoryEntry?.directoryCode === 'i'; const showOekakiControls = postOptionsDirectoryCode === 'i' || directoryEntry?.directoryCode === 'i';
const requirePostLinkIsMediaFeature = directoryEntry?.features?.requirePostLinkIsMedia; const communityFeatures = useCommunityField(effectiveBoardAddress, (community) => community?.features);
const requirePostLinkIsMedia = requirePostLinkIsMediaFeature === true || (requirePostLinkIsMediaFeature === undefined && (isInAllView || isInSubscriptionsView)); const publishLinkFeatures = getEffectivePublishLinkFeatures(communityFeatures, directoryEntry?.features);
const requirePostLink = getRequirePostLink(publishLinkFeatures);
const requirePostLinkIsMedia = getRequirePostLinkIsMedia(publishLinkFeatures, isInAllView || isInSubscriptionsView);
const flagOptions = getCommentFlagOptionsForDirectory(directoryEntry); const flagOptions = getCommentFlagOptionsForDirectory(directoryEntry);
const showFlashUploadPrompt = isFlashDirectoryCode(postOptionsDirectoryCode); const showFlashUploadPrompt = isFlashDirectoryCode(postOptionsDirectoryCode);
const showFlashTagSelector = showFlashUploadPrompt && !isInPostView; const showFlashTagSelector = showFlashUploadPrompt && !isInPostView;
@@ -680,12 +677,14 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
setFormError(`${t('error')}: ${t('empty_comment_alert')}`); setFormError(`${t('error')}: ${t('empty_comment_alert')}`);
return; return;
} }
if (currentUrl && !isValidPublishURL(currentUrl)) { const linkValidationError = getPublishLinkValidationError({
setFormError(`${t('error')}: ${t('invalid_url_alert')}`); link: currentUrl,
return; requireLink: requirePostLink,
} requireMedia: requirePostLinkIsMedia,
if (currentUrl && requirePostLinkIsMedia && !isPublishFileMediaLink(currentUrl)) { t,
setFormError(`${t('error')}: ${t('link_not_image_or_video_alert')}`); });
if (linkValidationError) {
setFormError(linkValidationError);
return; return;
} }
const expiringMediaLinkAlert = currentUrl ? getExpiringMediaLinkAlert(currentUrl, t) : null; const expiringMediaLinkAlert = currentUrl ? getExpiringMediaLinkAlert(currentUrl, t) : null;
@@ -809,12 +808,14 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
return; return;
} }
if (currentUrl && !isValidPublishURL(currentUrl)) { const linkValidationError = getPublishLinkValidationError({
setFormError(`${t('error')}: ${t('invalid_url_alert')}`); link: currentUrl,
return; requireLink: requirePostLink,
} requireMedia: requirePostLinkIsMedia,
if (currentUrl && requirePostLinkIsMedia && !isPublishFileMediaLink(currentUrl)) { t,
setFormError(`${t('error')}: ${t('link_not_image_or_video_alert')}`); });
if (linkValidationError) {
setFormError(linkValidationError);
return; return;
} }
const expiringMediaLinkAlert = currentUrl ? getExpiringMediaLinkAlert(currentUrl, t) : null; const expiringMediaLinkAlert = currentUrl ? getExpiringMediaLinkAlert(currentUrl, t) : null;
@@ -61,7 +61,7 @@ const testState = vi.hoisted(() => ({
'music-posting.eth': { 'music-posting.eth': {
address: 'music-posting.eth', address: 'music-posting.eth',
}, },
} as Record<string, { address: string }>, } as Record<string, { address: string; features?: Record<string, unknown> }>,
fetchMock: vi.fn(), fetchMock: vi.fn(),
showUploadControls: true, showUploadControls: true,
uploadComplete: undefined as ((url: string) => void) | undefined, uploadComplete: undefined as ((url: string) => void) | undefined,
@@ -187,8 +187,18 @@ vi.mock('../../../hooks/use-resolved-community-address', () => ({
})); }));
vi.mock('../../../hooks/use-stable-community', () => ({ vi.mock('../../../hooks/use-stable-community', () => ({
useCommunityField: <T,>(communityAddress: string | undefined, selector: (community?: { roles?: Record<string, { role?: string }> }) => T) => useCommunityField: <T,>(
selector(communityAddress ? { roles: testState.rolesByCommunity[communityAddress] } : undefined), communityAddress: string | undefined,
selector: (community?: { features?: Record<string, unknown>; roles?: Record<string, { role?: string }> }) => T,
) =>
selector(
communityAddress
? {
...testState.communities[communityAddress],
roles: testState.rolesByCommunity[communityAddress],
}
: undefined,
),
})); }));
vi.mock('../../../hooks/use-publish-reply', async () => { vi.mock('../../../hooks/use-publish-reply', async () => {
@@ -755,6 +765,86 @@ describe('ReplyModal', () => {
expect(testState.publishReplyMock).toHaveBeenCalledTimes(1); 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<HTMLInputElement>('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<HTMLInputElement>('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<HTMLInputElement>('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 () => { it('publishes twimg query-format reply links with a path extension', async () => {
testState.openEmpty = true; testState.openEmpty = true;
testState.selectedText = 'reply body'; testState.selectedText = 'reply body';
+14 -10
View File
@@ -3,8 +3,9 @@ import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { TFunction } from 'i18next'; import type { TFunction } from 'i18next';
import { setAccount, useAccount } from '@bitsocial/bitsocial-react-hooks'; 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 { 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 { getCommentFlagOptionsForDirectory, getCommentFlagPublishOptionsForDirectory } from '../../lib/comment-flag-selection';
import { import {
type DiceRoll, type DiceRoll,
@@ -18,7 +19,6 @@ import {
hasNonokoOption, hasNonokoOption,
isPostOptionsValidationError, isPostOptionsValidationError,
} from '../../lib/utils/post-options-utils'; } from '../../lib/utils/post-options-utils';
import { isValidPublishURL } from '../../lib/utils/url-utils';
import { isMathDirectoryCode } from '../../lib/math-tags'; import { isMathDirectoryCode } from '../../lib/math-tags';
import { hasModQueueAccessRole } from '../../lib/utils/mod-access'; import { hasModQueueAccessRole } from '../../lib/utils/mod-access';
import { getModerationPostingRoleLabel } from '../../lib/utils/author-display-utils'; 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 postOptionsDirectoryCode = getPostOptionsDirectoryCode(directoryEntry, location.pathname);
const showOekakiControls = postOptionsDirectoryCode === 'i' || directoryEntry?.directoryCode === 'i'; const showOekakiControls = postOptionsDirectoryCode === 'i' || directoryEntry?.directoryCode === 'i';
const showTexButton = isMathDirectoryCode(postOptionsDirectoryCode) || isMathDirectoryCode(directoryEntry?.directoryCode); const showTexButton = isMathDirectoryCode(postOptionsDirectoryCode) || isMathDirectoryCode(directoryEntry?.directoryCode);
const requirePostLinkIsMediaFeature = directoryEntry?.features?.requirePostLinkIsMedia; const communityFeatures = useCommunityField(communityAddress, (community) => community?.features);
const requirePostLinkIsMedia = requirePostLinkIsMediaFeature === true || (requirePostLinkIsMediaFeature === undefined && (isInAllView || isInSubscriptionsView)); const publishLinkFeatures = getEffectivePublishLinkFeatures(communityFeatures, directoryEntry?.features);
const requirePostLink = getRequirePostLink(publishLinkFeatures);
const requirePostLinkIsMedia = getRequirePostLinkIsMedia(publishLinkFeatures, isInAllView || isInSubscriptionsView);
const flagOptions = getCommentFlagOptionsForDirectory(directoryEntry); const flagOptions = getCommentFlagOptionsForDirectory(directoryEntry);
const { isResolvingExternalQuotes, publishReply, publishReplyError, publishReplyStateMessage, resetPublishReplyOptions, replyIndex, setPublishReplyOptions } = const { isResolvingExternalQuotes, publishReply, publishReplyError, publishReplyStateMessage, resetPublishReplyOptions, replyIndex, setPublishReplyOptions } =
usePublishReply({ usePublishReply({
@@ -184,12 +186,14 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
return; return;
} }
if (currentUrl && !isValidPublishURL(currentUrl)) { const linkValidationError = getPublishLinkValidationError({
setError(t('error') + ': ' + t('invalid_url_alert')); link: currentUrl,
return; requireLink: requirePostLink,
} requireMedia: requirePostLinkIsMedia,
if (currentUrl && requirePostLinkIsMedia && !isPublishFileMediaLink(currentUrl)) { t,
setError(t('error') + ': ' + t('link_not_image_or_video_alert')); });
if (linkValidationError) {
setError(linkValidationError);
return; return;
} }
const expiringMediaLinkAlert = currentUrl ? getExpiringMediaLinkAlert(currentUrl, t) : null; const expiringMediaLinkAlert = currentUrl ? getExpiringMediaLinkAlert(currentUrl, t) : null;
@@ -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);
});
});
@@ -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;
};