diff --git a/package.json b/package.json
index 4359dcaf..5ec5a6cf 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,7 @@
"@capawesome/capacitor-android-edge-to-edge-support": "7.2.2",
"@chenglou/pretext": "0.0.5",
"@floating-ui/react": "0.26.1",
- "@pkcprotocol/pkc-js": "0.0.35",
+ "@pkcprotocol/pkc-js": "0.0.37",
"@react-spring/web": "10.0.3",
"@types/node": "20.19.37",
"@types/react": "18.2.25",
@@ -183,7 +183,9 @@
"portless": "0.11.1"
},
"resolutions": {
+ "@pkcprotocol/pkc-js": "0.0.37",
"follow-redirects": "1.16.0",
+ "@libp2p/gossipsub": "15.0.23",
"@libp2p/kad-dht": "16.2.6",
"brace-expansion@npm:^5.0.2": "5.0.6",
"protobufjs": "7.5.8",
diff --git a/public/assets/icons/flags-2.png b/public/assets/icons/flags-2.png
new file mode 100644
index 00000000..882fd917
Binary files /dev/null and b/public/assets/icons/flags-2.png differ
diff --git a/public/assets/icons/flags-pony.png b/public/assets/icons/flags-pony.png
new file mode 100644
index 00000000..d78f63de
Binary files /dev/null and b/public/assets/icons/flags-pony.png differ
diff --git a/public/translations/ar/default.json b/public/translations/ar/default.json
index b7ffbe82..fb9fd630 100644
--- a/public/translations/ar/default.json
+++ b/public/translations/ar/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "لم يتم التحقق من عنوان هذه اللوحة. قد لا يكون مالك اللوحة مالكًا لهذا العنوان.",
"go_to_settings_to_import_mod_account": "انتقل إلى <1>الإعدادات1> لاستيراد حساب المشرف الخاص بك",
"post_form_rules_faq_prompt": "يُرجى قراءة القواعد والأسئلة الشائعة قبل النشر.",
- "pending_mod_approval_reason": "أرسل الإشراف بالذكاء الاصطناعي هذا المنشور إلى قائمة انتظار المشرفين. السبب: \"{{reason}}\"."
+ "pending_mod_approval_reason": "أرسل الإشراف بالذكاء الاصطناعي هذا المنشور إلى قائمة انتظار المشرفين. السبب: \"{{reason}}\".",
+ "flag": "علم"
}
diff --git a/public/translations/bn/default.json b/public/translations/bn/default.json
index dc3cfc31..fb1f8f11 100644
--- a/public/translations/bn/default.json
+++ b/public/translations/bn/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "এই বোর্ড ঠিকানাটি যাচাই করা হয়নি। বোর্ডের মালিক এই ঠিকানার মালিক নাও হতে পারেন।",
"go_to_settings_to_import_mod_account": "আপনার মড অ্যাকাউন্ট আমদানি করতে <1>সেটিংস1>-এ যান",
"post_form_rules_faq_prompt": "পোস্ট করার আগে নিয়ম এবং প্রশ্নোত্তর পড়ুন।",
- "pending_mod_approval_reason": "AI মডারেশন এই পোস্টটি মডারেটর কিউতে পাঠিয়েছে। কারণ: \"{{reason}}\"."
+ "pending_mod_approval_reason": "AI মডারেশন এই পোস্টটি মডারেটর কিউতে পাঠিয়েছে। কারণ: \"{{reason}}\".",
+ "flag": "পতাকা"
}
diff --git a/public/translations/cs/default.json b/public/translations/cs/default.json
index d846b9cd..ed5e0176 100644
--- a/public/translations/cs/default.json
+++ b/public/translations/cs/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Tato adresa nástěnky není ověřená. Vlastník nástěnky nemusí tuto adresu vlastnit.",
"go_to_settings_to_import_mod_account": "Přejděte do <1>nastavení1> a importujte svůj moderátorský účet",
"post_form_rules_faq_prompt": "Před odesláním si přečtěte Pravidla a FAQ.",
- "pending_mod_approval_reason": "AI moderace odeslala tento příspěvek do fronty moderátorů. Důvod: \"{{reason}}\"."
+ "pending_mod_approval_reason": "AI moderace odeslala tento příspěvek do fronty moderátorů. Důvod: \"{{reason}}\".",
+ "flag": "Vlajka"
}
diff --git a/public/translations/da/default.json b/public/translations/da/default.json
index 92461c45..39b15192 100644
--- a/public/translations/da/default.json
+++ b/public/translations/da/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Denne boardadresse er ikke verificeret. Boardets ejer ejer muligvis ikke denne adresse.",
"go_to_settings_to_import_mod_account": "Gå til <1>indstillinger1> for at importere din mod-konto",
"post_form_rules_faq_prompt": "Læs venligst Reglerne og FAQ, før du poster.",
- "pending_mod_approval_reason": "AI-moderering sendte dette opslag til moderatorkøen. Grund: \"{{reason}}\"."
+ "pending_mod_approval_reason": "AI-moderering sendte dette opslag til moderatorkøen. Grund: \"{{reason}}\".",
+ "flag": "Flag"
}
diff --git a/public/translations/de/default.json b/public/translations/de/default.json
index 6ccd1e29..2427f2cc 100644
--- a/public/translations/de/default.json
+++ b/public/translations/de/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Diese Board-Adresse ist nicht verifiziert. Der Board-Betreiber besitzt diese Adresse möglicherweise nicht.",
"go_to_settings_to_import_mod_account": "Gehe zu <1>Einstellungen1>, um dein Moderatorkonto zu importieren",
"post_form_rules_faq_prompt": "Bitte lesen Sie die Regeln und die FAQ, bevor Sie posten.",
- "pending_mod_approval_reason": "Die KI-Moderation hat diesen Beitrag in die Moderationswarteschlange verschoben. Grund: \"{{reason}}\"."
+ "pending_mod_approval_reason": "Die KI-Moderation hat diesen Beitrag in die Moderationswarteschlange verschoben. Grund: \"{{reason}}\".",
+ "flag": "Flagge"
}
diff --git a/public/translations/el/default.json b/public/translations/el/default.json
index b2c6ba14..b25136c9 100644
--- a/public/translations/el/default.json
+++ b/public/translations/el/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Αυτή η διεύθυνση board δεν έχει επαληθευτεί. Ο ιδιοκτήτης του board ενδέχεται να μην κατέχει αυτή τη διεύθυνση.",
"go_to_settings_to_import_mod_account": "Μεταβείτε στις <1>ρυθμίσεις1> για να εισαγάγετε τον λογαριασμό διαχειριστή σας",
"post_form_rules_faq_prompt": "Παρακαλώ διαβάστε τους Κανόνες και τις Συχνές ερωτήσεις πριν δημοσιεύσετε.",
- "pending_mod_approval_reason": "Η εποπτεία AI έστειλε αυτή την ανάρτηση στην ουρά των συντονιστών. Λόγος: \"{{reason}}\"."
+ "pending_mod_approval_reason": "Η εποπτεία AI έστειλε αυτή την ανάρτηση στην ουρά των συντονιστών. Λόγος: \"{{reason}}\".",
+ "flag": "Σημαία"
}
diff --git a/public/translations/en/default.json b/public/translations/en/default.json
index a6c27ade..0be4c552 100644
--- a/public/translations/en/default.json
+++ b/public/translations/en/default.json
@@ -411,5 +411,6 @@
"modQueue.dismiss": "Dismiss",
"board_address_unverified_warning": "This board address is not verified. The board owner might not own this address.",
"post_form_rules_faq_prompt": "Please read the Rules and FAQ before posting.",
- "pending_mod_approval_reason": "AI moderation sent this post to the mod queue. Reason: \"{{reason}}\"."
+ "pending_mod_approval_reason": "AI moderation sent this post to the mod queue. Reason: \"{{reason}}\".",
+ "flag": "Flag"
}
diff --git a/public/translations/es/default.json b/public/translations/es/default.json
index b0fc2e1b..d5ea20ac 100644
--- a/public/translations/es/default.json
+++ b/public/translations/es/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Esta dirección del tablón no está verificada. Es posible que el propietario del tablón no sea dueño de esta dirección.",
"go_to_settings_to_import_mod_account": "Ve a <1>configuración1> para importar tu cuenta de moderador",
"post_form_rules_faq_prompt": "Por favor, lee las Reglas y las FAQ antes de publicar.",
- "pending_mod_approval_reason": "La moderación con IA envió esta publicación a la cola de moderación. Motivo: \"{{reason}}\"."
+ "pending_mod_approval_reason": "La moderación con IA envió esta publicación a la cola de moderación. Motivo: \"{{reason}}\".",
+ "flag": "Bandera"
}
diff --git a/public/translations/fa/default.json b/public/translations/fa/default.json
index cedb1352..986f8f46 100644
--- a/public/translations/fa/default.json
+++ b/public/translations/fa/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "این نشانی برد تأیید نشده است. ممکن است مالک برد صاحب این نشانی نباشد.",
"go_to_settings_to_import_mod_account": "برای وارد کردن حساب مدیر خود به <1>تنظیمات1> بروید",
"post_form_rules_faq_prompt": "لطفاً قبل از ارسال قوانین و پرسشهای متداول را بخوانید.",
- "pending_mod_approval_reason": "مدیریت با هوش مصنوعی این پست را به صف مدیران فرستاد. دلیل: \"{{reason}}\"."
+ "pending_mod_approval_reason": "مدیریت با هوش مصنوعی این پست را به صف مدیران فرستاد. دلیل: \"{{reason}}\".",
+ "flag": "پرچم"
}
diff --git a/public/translations/fi/default.json b/public/translations/fi/default.json
index 4c44e77c..fc7688b3 100644
--- a/public/translations/fi/default.json
+++ b/public/translations/fi/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Tätä laudan osoitetta ei ole vahvistettu. Laudan omistaja ei välttämättä omista tätä osoitetta.",
"go_to_settings_to_import_mod_account": "Siirry <1>asetuksiin1> tuodaksesi moderaattoritilisi",
"post_form_rules_faq_prompt": "Lue Säännöt ja UKK ennen julkaisemista.",
- "pending_mod_approval_reason": "Tekoälymoderointi lähetti tämän viestin moderointijonoon. Syy: \"{{reason}}\"."
+ "pending_mod_approval_reason": "Tekoälymoderointi lähetti tämän viestin moderointijonoon. Syy: \"{{reason}}\".",
+ "flag": "Lippu"
}
diff --git a/public/translations/fil/default.json b/public/translations/fil/default.json
index 33524aaf..735dc7a1 100644
--- a/public/translations/fil/default.json
+++ b/public/translations/fil/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Hindi beripikado ang address ng board na ito. Maaaring hindi pag-aari ng may-ari ng board ang address na ito.",
"go_to_settings_to_import_mod_account": "Pumunta sa <1>mga setting1> para i-import ang iyong mod account",
"post_form_rules_faq_prompt": "Pakibasa ang Rules at FAQ bago mag-post.",
- "pending_mod_approval_reason": "Ipinadala ng AI moderation ang post na ito sa mod queue. Dahilan: \"{{reason}}\"."
+ "pending_mod_approval_reason": "Ipinadala ng AI moderation ang post na ito sa mod queue. Dahilan: \"{{reason}}\".",
+ "flag": "Bandila"
}
diff --git a/public/translations/fr/default.json b/public/translations/fr/default.json
index 63f6bf26..ed8bf97b 100644
--- a/public/translations/fr/default.json
+++ b/public/translations/fr/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Cette adresse de board n'est pas vérifiée. Le propriétaire du board pourrait ne pas posséder cette adresse.",
"go_to_settings_to_import_mod_account": "Allez dans les <1>paramètres1> pour importer votre compte de modération",
"post_form_rules_faq_prompt": "Veuillez lire le Règlement et la FAQ avant de publier.",
- "pending_mod_approval_reason": "La modération par IA a envoyé ce post dans la file de modération. Raison: \"{{reason}}\"."
+ "pending_mod_approval_reason": "La modération par IA a envoyé ce post dans la file de modération. Raison: \"{{reason}}\".",
+ "flag": "Drapeau"
}
diff --git a/public/translations/he/default.json b/public/translations/he/default.json
index 4cb8492f..39c82ec7 100644
--- a/public/translations/he/default.json
+++ b/public/translations/he/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "כתובת הלוח הזו אינה מאומתת. ייתכן שבעל הלוח אינו הבעלים של הכתובת הזו.",
"go_to_settings_to_import_mod_account": "עבור אל <1>הגדרות1> כדי לייבא את חשבון המוד שלך",
"post_form_rules_faq_prompt": "אנא קרא את הכללים ואת שאלות נפוצות לפני פרסום.",
- "pending_mod_approval_reason": "סינון ה-AI שלח את הפוסט הזה לתור המודרציה. סיבה: \"{{reason}}\"."
+ "pending_mod_approval_reason": "סינון ה-AI שלח את הפוסט הזה לתור המודרציה. סיבה: \"{{reason}}\".",
+ "flag": "דגל"
}
diff --git a/public/translations/hi/default.json b/public/translations/hi/default.json
index 838e1263..28945024 100644
--- a/public/translations/hi/default.json
+++ b/public/translations/hi/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "इस बोर्ड पते की पुष्टि नहीं हुई है। हो सकता है बोर्ड का मालिक इस पते का मालिक न हो।",
"go_to_settings_to_import_mod_account": "अपना मॉड खाता इम्पोर्ट करने के लिए <1>सेटिंग्स1> पर जाएं",
"post_form_rules_faq_prompt": "पोस्ट करने से पहले कृपया नियम और अक्सर पूछे जाने वाले सवाल पढ़ें।",
- "pending_mod_approval_reason": "AI मॉडरेशन ने इस पोस्ट को मॉड कतार में भेजा। कारण: \"{{reason}}\"."
+ "pending_mod_approval_reason": "AI मॉडरेशन ने इस पोस्ट को मॉड कतार में भेजा। कारण: \"{{reason}}\".",
+ "flag": "ध्वज"
}
diff --git a/public/translations/hu/default.json b/public/translations/hu/default.json
index 81914607..a28e015a 100644
--- a/public/translations/hu/default.json
+++ b/public/translations/hu/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Ez a board-cím nincs ellenőrizve. Lehet, hogy a board tulajdonosa nem birtokolja ezt a címet.",
"go_to_settings_to_import_mod_account": "Lépj a <1>beállítások1> menübe a moderátori fiókod importálásához",
"post_form_rules_faq_prompt": "Kérjük, olvasd el a Szabályokat és a GYIK-et posztolás előtt.",
- "pending_mod_approval_reason": "Az AI-moderáció ezt a bejegyzést a moderátori várólistára küldte. Ok: \"{{reason}}\"."
+ "pending_mod_approval_reason": "Az AI-moderáció ezt a bejegyzést a moderátori várólistára küldte. Ok: \"{{reason}}\".",
+ "flag": "Zászló"
}
diff --git a/public/translations/id/default.json b/public/translations/id/default.json
index cbea3aeb..b9a130b4 100644
--- a/public/translations/id/default.json
+++ b/public/translations/id/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Alamat board ini belum diverifikasi. Pemilik board mungkin tidak memiliki alamat ini.",
"go_to_settings_to_import_mod_account": "Buka <1>pengaturan1> untuk mengimpor akun mod Anda",
"post_form_rules_faq_prompt": "Silakan baca Aturan dan FAQ sebelum memposting.",
- "pending_mod_approval_reason": "Moderasi AI mengirim postingan ini ke antrean moderator. Alasan: \"{{reason}}\"."
+ "pending_mod_approval_reason": "Moderasi AI mengirim postingan ini ke antrean moderator. Alasan: \"{{reason}}\".",
+ "flag": "Bendera"
}
diff --git a/public/translations/it/default.json b/public/translations/it/default.json
index 955c44a3..28845597 100644
--- a/public/translations/it/default.json
+++ b/public/translations/it/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Questo indirizzo della board non è verificato. Il proprietario della board potrebbe non possedere questo indirizzo.",
"go_to_settings_to_import_mod_account": "Vai alle <1>impostazioni1> per importare il tuo account mod",
"post_form_rules_faq_prompt": "Leggi le Regole e le FAQ prima di pubblicare.",
- "pending_mod_approval_reason": "La moderazione AI ha inviato questo post alla coda di moderazione. Motivo: \"{{reason}}\"."
+ "pending_mod_approval_reason": "La moderazione AI ha inviato questo post alla coda di moderazione. Motivo: \"{{reason}}\".",
+ "flag": "Bandiera"
}
diff --git a/public/translations/ja/default.json b/public/translations/ja/default.json
index f2b4d50a..906fd3a3 100644
--- a/public/translations/ja/default.json
+++ b/public/translations/ja/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "このボードアドレスは検証されていません。ボード所有者がこのアドレスを所有していない可能性があります。",
"go_to_settings_to_import_mod_account": "モデレーターアカウントをインポートするには<1>設定1>に移動してください",
"post_form_rules_faq_prompt": "投稿する前にルールとFAQをお読みください。",
- "pending_mod_approval_reason": "AIモデレーションがこの投稿をモデレーターキューに送りました。理由: \"{{reason}}\"."
+ "pending_mod_approval_reason": "AIモデレーションがこの投稿をモデレーターキューに送りました。理由: \"{{reason}}\".",
+ "flag": "旗"
}
diff --git a/public/translations/ko/default.json b/public/translations/ko/default.json
index f0ef67ee..c841af09 100644
--- a/public/translations/ko/default.json
+++ b/public/translations/ko/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "이 보드 주소는 확인되지 않았습니다. 보드 소유자가 이 주소를 소유하지 않았을 수 있습니다.",
"go_to_settings_to_import_mod_account": "관리자 계정을 가져오려면 <1>설정1>으로 이동하세요",
"post_form_rules_faq_prompt": "게시하기 전에 규칙과 FAQ을 읽어 주세요.",
- "pending_mod_approval_reason": "AI 모더레이션이 이 게시물을 모더레이터 대기열로 보냈습니다. 이유: \"{{reason}}\"."
+ "pending_mod_approval_reason": "AI 모더레이션이 이 게시물을 모더레이터 대기열로 보냈습니다. 이유: \"{{reason}}\".",
+ "flag": "깃발"
}
diff --git a/public/translations/mr/default.json b/public/translations/mr/default.json
index 595fb124..ffefbfaa 100644
--- a/public/translations/mr/default.json
+++ b/public/translations/mr/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "हा बोर्ड पत्ता सत्यापित केलेला नाही. बोर्डचा मालक हा पत्ता मालक नसू शकतो.",
"go_to_settings_to_import_mod_account": "तुमचे मॉड खाते आयात करण्यासाठी <1>सेटिंग्ज1> वर जा",
"post_form_rules_faq_prompt": "पोस्ट करण्यापूर्वी नियम आणि वारंवार विचारले जाणारे प्रश्न वाचा.",
- "pending_mod_approval_reason": "AI मॉडरेशनने ही पोस्ट मॉड कतारमध्ये पाठवली. कारण: \"{{reason}}\"."
+ "pending_mod_approval_reason": "AI मॉडरेशनने ही पोस्ट मॉड कतारमध्ये पाठवली. कारण: \"{{reason}}\".",
+ "flag": "ध्वज"
}
diff --git a/public/translations/nl/default.json b/public/translations/nl/default.json
index 24462d5f..8483d981 100644
--- a/public/translations/nl/default.json
+++ b/public/translations/nl/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Dit boardadres is niet geverifieerd. De eigenaar van het board is mogelijk niet de eigenaar van dit adres.",
"go_to_settings_to_import_mod_account": "Ga naar <1>instellingen1> om je mod-account te importeren",
"post_form_rules_faq_prompt": "Lees de Regels en de FAQ voordat je plaatst.",
- "pending_mod_approval_reason": "AI-moderatie heeft dit bericht naar de moderatiewachtrij gestuurd. Reden: \"{{reason}}\"."
+ "pending_mod_approval_reason": "AI-moderatie heeft dit bericht naar de moderatiewachtrij gestuurd. Reden: \"{{reason}}\".",
+ "flag": "Vlag"
}
diff --git a/public/translations/no/default.json b/public/translations/no/default.json
index ab7f9c90..fe6ec26b 100644
--- a/public/translations/no/default.json
+++ b/public/translations/no/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Denne board-adressen er ikke bekreftet. Board-eieren eier kanskje ikke denne adressen.",
"go_to_settings_to_import_mod_account": "Gå til <1>innstillinger1> for å importere modkontoen din",
"post_form_rules_faq_prompt": "Vennligst les Reglene og FAQ før du poster.",
- "pending_mod_approval_reason": "AI-moderering sendte dette innlegget til moderatorkøen. Årsak: \"{{reason}}\"."
+ "pending_mod_approval_reason": "AI-moderering sendte dette innlegget til moderatorkøen. Årsak: \"{{reason}}\".",
+ "flag": "Flagg"
}
diff --git a/public/translations/pl/default.json b/public/translations/pl/default.json
index d3bca265..4932bb70 100644
--- a/public/translations/pl/default.json
+++ b/public/translations/pl/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Ten adres boarda nie jest zweryfikowany. Właściciel boarda może nie być właścicielem tego adresu.",
"go_to_settings_to_import_mod_account": "Przejdź do <1>ustawień1>, aby zaimportować swoje konto moderatora",
"post_form_rules_faq_prompt": "Przed opublikowaniem przeczytaj Zasady i FAQ.",
- "pending_mod_approval_reason": "Moderacja AI wysłała ten post do kolejki moderatorów. Powód: \"{{reason}}\"."
+ "pending_mod_approval_reason": "Moderacja AI wysłała ten post do kolejki moderatorów. Powód: \"{{reason}}\".",
+ "flag": "Flaga"
}
diff --git a/public/translations/pt/default.json b/public/translations/pt/default.json
index b2116e8d..91bde7cc 100644
--- a/public/translations/pt/default.json
+++ b/public/translations/pt/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Este endereço do board não foi verificado. O proprietário do board pode não ser dono deste endereço.",
"go_to_settings_to_import_mod_account": "Vá para <1>configurações1> para importar sua conta de mod",
"post_form_rules_faq_prompt": "Leia as Regras e as FAQ antes de publicar.",
- "pending_mod_approval_reason": "A moderação por IA enviou esta postagem para a fila de moderação. Razão: \"{{reason}}\"."
+ "pending_mod_approval_reason": "A moderação por IA enviou esta postagem para a fila de moderação. Razão: \"{{reason}}\".",
+ "flag": "Bandeira"
}
diff --git a/public/translations/ro/default.json b/public/translations/ro/default.json
index 420c8851..9fec568a 100644
--- a/public/translations/ro/default.json
+++ b/public/translations/ro/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Această adresă de board nu este verificată. Proprietarul boardului s-ar putea să nu dețină această adresă.",
"go_to_settings_to_import_mod_account": "Accesează <1>setările1> pentru a importa contul tău de moderator",
"post_form_rules_faq_prompt": "Citește Regulile și Întrebările frecvente înainte de a publica.",
- "pending_mod_approval_reason": "Moderarea AI a trimis această postare în coada moderatorilor. Motiv: \"{{reason}}\"."
+ "pending_mod_approval_reason": "Moderarea AI a trimis această postare în coada moderatorilor. Motiv: \"{{reason}}\".",
+ "flag": "Steag"
}
diff --git a/public/translations/ru/default.json b/public/translations/ru/default.json
index dbe9f655..a993ba38 100644
--- a/public/translations/ru/default.json
+++ b/public/translations/ru/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Этот адрес доски не проверен. Владелец доски может не владеть этим адресом.",
"go_to_settings_to_import_mod_account": "Перейдите в <1>настройки1>, чтобы импортировать свой аккаунт модератора",
"post_form_rules_faq_prompt": "Пожалуйста, прочитайте Правила и FAQ перед публикацией.",
- "pending_mod_approval_reason": "ИИ-модерация отправила этот пост в очередь модераторов. Причина: \"{{reason}}\"."
+ "pending_mod_approval_reason": "ИИ-модерация отправила этот пост в очередь модераторов. Причина: \"{{reason}}\".",
+ "flag": "Флаг"
}
diff --git a/public/translations/sq/default.json b/public/translations/sq/default.json
index 81a31be1..c85fab2b 100644
--- a/public/translations/sq/default.json
+++ b/public/translations/sq/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Kjo adresë e board-it nuk është verifikuar. Pronari i board-it mund të mos e zotërojë këtë adresë.",
"go_to_settings_to_import_mod_account": "Shkoni te <1>parametrat1> për të importuar llogarinë tuaj të modit",
"post_form_rules_faq_prompt": "Ju lutemi lexoni Rregullat dhe Pyetjet e shpeshta para se të postoni.",
- "pending_mod_approval_reason": "Moderimi me AI e dërgoi këtë postim në radhën e moderatorëve. Arsye: \"{{reason}}\"."
+ "pending_mod_approval_reason": "Moderimi me AI e dërgoi këtë postim në radhën e moderatorëve. Arsye: \"{{reason}}\".",
+ "flag": "Flamur"
}
diff --git a/public/translations/sv/default.json b/public/translations/sv/default.json
index 9034a439..df997b79 100644
--- a/public/translations/sv/default.json
+++ b/public/translations/sv/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Den här board-adressen är inte verifierad. Boardets ägare kanske inte äger den här adressen.",
"go_to_settings_to_import_mod_account": "Gå till <1>inställningar1> för att importera ditt modkonto",
"post_form_rules_faq_prompt": "Läs Reglerna och FAQ innan du postar.",
- "pending_mod_approval_reason": "AI-moderering skickade det här inlägget till moderatorkön. Anledning: \"{{reason}}\"."
+ "pending_mod_approval_reason": "AI-moderering skickade det här inlägget till moderatorkön. Anledning: \"{{reason}}\".",
+ "flag": "Flagga"
}
diff --git a/public/translations/te/default.json b/public/translations/te/default.json
index 34c54d64..615b32de 100644
--- a/public/translations/te/default.json
+++ b/public/translations/te/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "ఈ బోర్డ్ చిరునామా ధృవీకరించబడలేదు. బోర్డ్ యజమాని ఈ చిరునామాకు యజమాని కాకపోవచ్చు.",
"go_to_settings_to_import_mod_account": "మీ మోడ్ ఖాతాను దిగుమతి చేయడానికి <1>అమరికలు1>కి వెళ్లండి",
"post_form_rules_faq_prompt": "పోస్ట్ చేయడానికి ముందు నియమాలు మరియు తరచుగా అడిగే ప్రశ్నలు చదవండి.",
- "pending_mod_approval_reason": "AI మోడరేషన్ ఈ పోస్ట్ను మోడరేటర్ క్యూకు పంపింది. కారణం: \"{{reason}}\"."
+ "pending_mod_approval_reason": "AI మోడరేషన్ ఈ పోస్ట్ను మోడరేటర్ క్యూకు పంపింది. కారణం: \"{{reason}}\".",
+ "flag": "జెండా"
}
diff --git a/public/translations/th/default.json b/public/translations/th/default.json
index ce1c2dba..626e3b84 100644
--- a/public/translations/th/default.json
+++ b/public/translations/th/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "ที่อยู่บอร์ดนี้ยังไม่ได้รับการยืนยัน เจ้าของบอร์ดอาจไม่ได้เป็นเจ้าของที่อยู่นี้",
"go_to_settings_to_import_mod_account": "ไปที่ <1>การตั้งค่า1> เพื่อนำเข้าบัญชีผู้ดูแลของคุณ",
"post_form_rules_faq_prompt": "โปรดอ่านกฎและคำถามที่พบบ่อยก่อนโพสต์",
- "pending_mod_approval_reason": "การกลั่นกรองด้วย AI ส่งโพสต์นี้ไปยังคิวผู้ดูแล เหตุผล: \"{{reason}}\"."
+ "pending_mod_approval_reason": "การกลั่นกรองด้วย AI ส่งโพสต์นี้ไปยังคิวผู้ดูแล เหตุผล: \"{{reason}}\".",
+ "flag": "ธง"
}
diff --git a/public/translations/tr/default.json b/public/translations/tr/default.json
index 7a8bab4f..01ed819d 100644
--- a/public/translations/tr/default.json
+++ b/public/translations/tr/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Bu board adresi doğrulanmamış. Board sahibi bu adresin sahibi olmayabilir.",
"go_to_settings_to_import_mod_account": "Mod hesabınızı içe aktarmak için <1>ayarlar1>a gidin",
"post_form_rules_faq_prompt": "Lütfen göndermeden önce Kuralları ve SSS'yi okuyun.",
- "pending_mod_approval_reason": "AI moderasyonu bu gönderiyi moderatör kuyruğuna gönderdi. Neden: \"{{reason}}\"."
+ "pending_mod_approval_reason": "AI moderasyonu bu gönderiyi moderatör kuyruğuna gönderdi. Neden: \"{{reason}}\".",
+ "flag": "Bayrak"
}
diff --git a/public/translations/uk/default.json b/public/translations/uk/default.json
index 9585168d..ab06bbad 100644
--- a/public/translations/uk/default.json
+++ b/public/translations/uk/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Цю адресу дошки не перевірено. Власник дошки може не володіти цією адресою.",
"go_to_settings_to_import_mod_account": "Перейдіть до <1>налаштувань1>, щоб імпортувати свій обліковий запис модератора",
"post_form_rules_faq_prompt": "Будь ласка, прочитайте Правила і Часті запитання перед публікацією.",
- "pending_mod_approval_reason": "AI-модерація надіслала цей допис до черги модераторів. Причина: \"{{reason}}\"."
+ "pending_mod_approval_reason": "AI-модерація надіслала цей допис до черги модераторів. Причина: \"{{reason}}\".",
+ "flag": "Прапор"
}
diff --git a/public/translations/ur/default.json b/public/translations/ur/default.json
index 2b21e380..c7ed77e5 100644
--- a/public/translations/ur/default.json
+++ b/public/translations/ur/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "اس بورڈ کے پتے کی تصدیق نہیں ہوئی۔ ممکن ہے بورڈ کا مالک اس پتے کا مالک نہ ہو۔",
"go_to_settings_to_import_mod_account": "اپنا موڈ اکاؤنٹ درآمد کرنے کے لیے <1>ترتیبات1> پر جائیں",
"post_form_rules_faq_prompt": "براہ کرم پوسٹ کرنے سے پہلے اصول اور اکثر پوچھے گئے سوالات پڑھیں۔",
- "pending_mod_approval_reason": "AI موڈریشن نے اس پوسٹ کو موڈریٹر قطار میں بھیج دیا۔ وجہ: \"{{reason}}\"."
+ "pending_mod_approval_reason": "AI موڈریشن نے اس پوسٹ کو موڈریٹر قطار میں بھیج دیا۔ وجہ: \"{{reason}}\".",
+ "flag": "پرچم"
}
diff --git a/public/translations/vi/default.json b/public/translations/vi/default.json
index da9f8a8a..67425543 100644
--- a/public/translations/vi/default.json
+++ b/public/translations/vi/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "Địa chỉ bảng này chưa được xác minh. Chủ bảng có thể không sở hữu địa chỉ này.",
"go_to_settings_to_import_mod_account": "Vào <1>cài đặt1> để nhập tài khoản mod của bạn",
"post_form_rules_faq_prompt": "Vui lòng đọc Quy tắc và Câu hỏi thường gặp trước khi đăng.",
- "pending_mod_approval_reason": "Kiểm duyệt AI đã gửi bài viết này vào hàng đợi kiểm duyệt. Lý do: \"{{reason}}\"."
+ "pending_mod_approval_reason": "Kiểm duyệt AI đã gửi bài viết này vào hàng đợi kiểm duyệt. Lý do: \"{{reason}}\".",
+ "flag": "Cờ"
}
diff --git a/public/translations/zh/default.json b/public/translations/zh/default.json
index 5bbfd152..c38b6473 100644
--- a/public/translations/zh/default.json
+++ b/public/translations/zh/default.json
@@ -392,5 +392,6 @@
"board_address_unverified_warning": "此版面地址尚未验证。版主可能并不拥有此地址。",
"go_to_settings_to_import_mod_account": "前往<1>设置1>以导入您的版主账户",
"post_form_rules_faq_prompt": "发帖前请阅读规则和常见问题。",
- "pending_mod_approval_reason": "AI 审核已将此帖子发送到版主队列。原因:\"{{reason}}\"。"
+ "pending_mod_approval_reason": "AI 审核已将此帖子发送到版主队列。原因:\"{{reason}}\"。",
+ "flag": "旗帜"
}
diff --git a/src/components/challenge-modal/__tests__/challenge-modal.test.tsx b/src/components/challenge-modal/__tests__/challenge-modal.test.tsx
index 9fc3245e..d38b03bb 100644
--- a/src/components/challenge-modal/__tests__/challenge-modal.test.tsx
+++ b/src/components/challenge-modal/__tests__/challenge-modal.test.tsx
@@ -438,6 +438,63 @@ describe('ChallengeModal', () => {
expect(testState.removeChallengeMock).toHaveBeenCalledOnce();
});
+ it('advances through multiple iframe challenges before publishing answers', async () => {
+ const publication = createPublication();
+ testState.challenges = [
+ createStoredChallenge(
+ [
+ {
+ challenge: 'https://spamblocker.bitsocial.net/api/v1/iframe/session-123',
+ type: 'url/iframe',
+ },
+ {
+ challenge: 'https://flags.5chan.app/iframe/session-flag',
+ type: 'url/iframe',
+ },
+ ],
+ publication,
+ ),
+ ];
+
+ await renderModal();
+ await clickButton('Open');
+
+ await act(async () => {
+ window.dispatchEvent(
+ new MessageEvent('message', {
+ data: {
+ type: 'challengeAnswer',
+ challengeAnswers: [''],
+ sessionId: 'session-123',
+ },
+ origin: 'https://spamblocker.bitsocial.net',
+ }),
+ );
+ });
+
+ expect(publication.publishChallengeAnswers).not.toHaveBeenCalled();
+ expect(testState.removeChallengeMock).not.toHaveBeenCalled();
+ expect(container.textContent).toContain('mu wants to open flags.5chan.app.');
+
+ await clickButton('Open');
+
+ await act(async () => {
+ window.dispatchEvent(
+ new MessageEvent('message', {
+ data: {
+ type: 'challengeAnswer',
+ challengeAnswers: [''],
+ sessionId: 'session-flag',
+ },
+ origin: 'https://flags.5chan.app',
+ }),
+ );
+ });
+
+ expect(publication.publishChallengeAnswers).toHaveBeenCalledWith(['', '']);
+ expect(testState.removeChallengeMock).toHaveBeenCalledOnce();
+ });
+
it('ignores iframe completion messages with the wrong session id', async () => {
const publication = createPublication();
testState.challenges = [
diff --git a/src/components/challenge-modal/challenge-modal.tsx b/src/components/challenge-modal/challenge-modal.tsx
index a135991e..6ae626d6 100644
--- a/src/components/challenge-modal/challenge-modal.tsx
+++ b/src/components/challenge-modal/challenge-modal.tsx
@@ -373,19 +373,41 @@ const Challenge = ({ challenge, closeModal, abandonModal }: ChallengeProps) => {
closeModal();
};
+ const completeCurrentChallenge = useCallback(
+ (challengeAnswers: string[]) => {
+ if (!publication) return;
+ const resolvedAnswers = challengeAnswers.length ? challengeAnswers : [''];
+ const updatedAnswers = [...answers];
+ resolvedAnswers.forEach((answer, index) => {
+ updatedAnswers[currentChallengeIndex + index] = answer;
+ });
+
+ const nextChallengeIndex = currentChallengeIndex + resolvedAnswers.length;
+ if (challenges?.[nextChallengeIndex]) {
+ setAnswers(updatedAnswers);
+ setCurrentChallengeIndex(nextChallengeIndex);
+ setReadyIframeChallengeKey('');
+ return;
+ }
+
+ publication.publishChallengeAnswers(updatedAnswers);
+ setAnswers([]);
+ closeModal();
+ },
+ [answers, challenges, closeModal, currentChallengeIndex, publication],
+ );
+
const onIframeDone = useCallback(() => {
if (!publication) return;
- publication.publishChallengeAnswers(['']);
- closeModal();
- }, [closeModal, publication]);
+ completeCurrentChallenge(['']);
+ }, [completeCurrentChallenge, publication]);
const onIframeAutoComplete = useCallback(
(challengeAnswers: string[]) => {
if (!publication) return;
- publication.publishChallengeAnswers(challengeAnswers);
- closeModal();
+ completeCurrentChallenge(challengeAnswers);
},
- [closeModal, publication],
+ [completeCurrentChallenge, publication],
);
const onIframeReady = useCallback(() => {
diff --git a/src/components/post-author-flags.tsx b/src/components/post-author-flags.tsx
new file mode 100644
index 00000000..8f78442f
--- /dev/null
+++ b/src/components/post-author-flags.tsx
@@ -0,0 +1,39 @@
+import { getCommentFlagFlairs, getAuthorFlagViewModels } from '../lib/comment-flags';
+import styles from '../views/post/post.module.css';
+
+interface PostAuthorFlagsProps {
+ author: unknown;
+ comment: unknown;
+ enabled: boolean;
+}
+
+const getBackgroundPosition = (x: number, y: number) => `${x === 0 ? 0 : -x}px ${y === 0 ? 0 : -y}px`;
+
+const PostAuthorFlags = ({ author, comment, enabled }: PostAuthorFlagsProps) => {
+ if (!enabled) return null;
+
+ const flags = getAuthorFlagViewModels(getCommentFlagFlairs(comment, author));
+ if (flags.length === 0) return null;
+
+ return (
+
+ {flags.map((flag) => (
+
+ ))}
+
+ );
+};
+
+export default PostAuthorFlags;
diff --git a/src/components/post-desktop/post-desktop.tsx b/src/components/post-desktop/post-desktop.tsx
index e7dbcb8c..a884c2ca 100644
--- a/src/components/post-desktop/post-desktop.tsx
+++ b/src/components/post-desktop/post-desktop.tsx
@@ -31,6 +31,7 @@ import EditMenu from '../edit-menu/edit-menu';
import FailedPublishNotice from '../failed-publish-notice';
import { canEmbed } from '../embed';
import LoadingEllipsis from '../loading-ellipsis';
+import PostAuthorFlags from '../post-author-flags';
import PostMenuDesktop from './post-menu-desktop';
import ReplyQuotePreview from '../reply-quote-preview';
import Tooltip from '../tooltip';
@@ -239,7 +240,9 @@ const PostInfo = ({
const isReply = parentCid;
const { showOmittedReplies } = useShowOmittedReplies();
const directories = useDirectories();
+ const directoryEntry = communityAddress ? findDirectoryByAddress(directories, communityAddress) : undefined;
const boardPath = communityAddress ? getBoardPath(communityAddress, directories) : undefined;
+ const showAuthorFlags = directoryEntry?.features?.hasFlags === true && !(deleted || removed || purged);
const postMenuProps = selectPostMenuProps(post);
const params = useParams();
@@ -415,6 +418,7 @@ const PostInfo = ({
>
)}
+
{isInModQueueView && isOverThreshold ? (
<>
diff --git a/src/components/post-form/__tests__/post-form.test.tsx b/src/components/post-form/__tests__/post-form.test.tsx
index 864b35b0..6c14258b 100644
--- a/src/components/post-form/__tests__/post-form.test.tsx
+++ b/src/components/post-form/__tests__/post-form.test.tsx
@@ -20,7 +20,7 @@ const testState = vi.hoisted(() => ({
directories: [
{ address: 'music-posting.eth', features: {}, title: '/mu/ - Music' },
{ address: 'mod.eth', features: {}, title: '/mod/ - Moderation' },
- ] as Array<{ address: string; features?: Record; title?: string }>,
+ ] as Array<{ address: string; directoryCode?: string; features?: Record; title?: string }>,
editedComment: undefined as { commentModeration?: { archived?: boolean }; deleted?: boolean; locked?: boolean; postCid?: string; removed?: boolean } | undefined,
gifFrameStatus: 'idle' as 'idle' | 'ready',
handleUploadMock: vi.fn(),
@@ -426,6 +426,7 @@ describe('PostForm', () => {
testState.comments = {};
testState.directories = [
{ address: 'music-posting.eth', features: {}, title: '/mu/ - Music' },
+ { address: 'politically-incorrect.bso', directoryCode: 'pol', features: { hasFlags: true }, title: '/pol/ - Politically Incorrect' },
{ address: 'random-nsfw.bso', features: {}, title: '/b/ - Random' },
{ address: 'silly-stuff.bso', features: {}, title: '/s5s/ - Silly Stuff' },
{ address: 'traditional-games.bso', features: {}, title: '/tg/ - Traditional Games' },
@@ -606,6 +607,59 @@ describe('PostForm', () => {
expect(testState.publishedPostOptions?.content).toBeUndefined();
});
+ it('shows a 4chan-style flag field on flag boards and publishes the default geographic request', async () => {
+ testState.resolvedCommunityAddress = 'politically-incorrect.bso';
+
+ await renderPostForm('/pol');
+ await clickByText(container, 'start_new_thread');
+
+ const table = container.querySelector('table');
+ const flagSelect = table?.querySelector('select[aria-label="flag"]');
+ const textarea = table?.querySelector('textarea');
+
+ expect(flagSelect).toBeTruthy();
+ expect(flagSelect?.value).toBe('country:auto');
+ expect(
+ Array.from(flagSelect?.options || [])
+ .slice(0, 4)
+ .map((option) => option.textContent),
+ ).toEqual(['Geographic Location', 'Anarcho-Capitalist', 'Anarchist', 'Black Nationalist']);
+
+ await dispatchInput(textarea as HTMLTextAreaElement, 'flagged post');
+ await clickByText(table as HTMLTableElement, 'post');
+
+ expect(testState.publishPostMock).toHaveBeenCalledWith({
+ content: 'flagged post',
+ challengeRequest: {
+ challengeAnswers: ['bitsocial-flags:5chan:flag:country:auto'],
+ },
+ flairs: [{ type: 'country', code: 'auto', text: 'flag:country:auto' }],
+ });
+ });
+
+ it('publishes selected political flags from the post form', async () => {
+ testState.resolvedCommunityAddress = 'politically-incorrect.bso';
+
+ await renderPostForm('/pol');
+ await clickByText(container, 'start_new_thread');
+
+ const table = container.querySelector('table');
+ const flagSelect = table?.querySelector('select[aria-label="flag"]');
+ const textarea = table?.querySelector('textarea');
+
+ await dispatchChange(flagSelect as HTMLSelectElement, 'pol:AC');
+ await dispatchInput(textarea as HTMLTextAreaElement, 'memeflag post');
+ await clickByText(table as HTMLTableElement, 'post');
+
+ expect(testState.publishPostMock).toHaveBeenCalledWith({
+ content: 'memeflag post',
+ challengeRequest: {
+ challengeAnswers: ['bitsocial-flags:5chan:flag:pol:AC'],
+ },
+ flairs: [{ type: 'pol', code: 'AC', text: 'flag:pol:AC' }],
+ });
+ });
+
it('validates unsupported options and stores fortune output in post content', async () => {
const randomSpy = vi.spyOn(Math, 'random').mockReturnValue(0.25);
testState.resolvedCommunityAddress = 'random-nsfw.bso';
diff --git a/src/components/post-form/post-form.module.css b/src/components/post-form/post-form.module.css
index 64dea8e0..8bffca87 100644
--- a/src/components/post-form/post-form.module.css
+++ b/src/components/post-form/post-form.module.css
@@ -69,7 +69,7 @@
position: relative;
}
-.postFormTable input[type="text"], .postFormTable textarea {
+.postFormTable input[type="text"], .postFormTable textarea, .postFormTable select {
border: var(--post-form-field-input-border, revert);
font-family: var(--post-form-field-font-family, revert);
appearance: var(--post-form-field-input-appearance, revert);
@@ -80,7 +80,7 @@
font-size: 10pt;
}
-.postFormTable input[type="text"]:focus, .postFormTable textarea:focus {
+.postFormTable input[type="text"]:focus, .postFormTable textarea:focus, .postFormTable select:focus {
border: var(--post-form-field-input-focus-border, revert);
}
@@ -113,6 +113,10 @@
width: 190px !important;
}
+.flagSelect {
+ width: 254px;
+}
+
.spoilerButton input[type="checkbox"] {
margin: 0 3px;
vertical-align: middle;
diff --git a/src/components/post-form/post-form.tsx b/src/components/post-form/post-form.tsx
index bb05b769..d29f8231 100644
--- a/src/components/post-form/post-form.tsx
+++ b/src/components/post-form/post-form.tsx
@@ -24,6 +24,7 @@ import { getPublishURLFilename, isValidPublishURL, isValidURL } from '../../lib/
import { hasModQueueAccessRole } from '../../lib/utils/mod-access';
import { getBoardPath } from '../../lib/utils/route-utils';
import { isAllView, isCatalogView, isModQueueView, isModView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
+import { getCommentFlagOptionsForDirectory, getCommentFlagPublishOptionsFromSelection, type CommentFlagSelectOption } from '../../lib/comment-flag-selection';
import { useAccountCommunityAddresses } from '../../hooks/use-account-community-addresses';
import { useDirectories, useDirectoryByAddress } from '../../hooks/use-directories';
import { useCommunityField } from '../../hooks/use-stable-community';
@@ -126,6 +127,7 @@ interface PostFormFieldsProps {
postCid: string;
subjectRef: React.Ref;
optionsRef: React.RefObject;
+ flagRef: React.RefObject;
textRef: React.RefObject;
urlRef: React.Ref;
url: string;
@@ -150,6 +152,7 @@ interface PostFormFieldsProps {
communityAddress: string | undefined;
rulesPath: string;
requirePostLinkIsMedia: boolean;
+ flagOptions: CommentFlagSelectOption[];
showBbcodeToolbar: boolean;
onBbcodePreviewToggle: () => void;
onPublishReply: () => void;
@@ -168,6 +171,7 @@ const PostFormFields = ({
postCid,
subjectRef,
optionsRef,
+ flagRef,
textRef,
urlRef,
url,
@@ -192,6 +196,7 @@ const PostFormFields = ({
communityAddress,
rulesPath,
requirePostLinkIsMedia,
+ flagOptions,
showBbcodeToolbar,
onBbcodePreviewToggle,
onPublishReply,
@@ -294,6 +299,26 @@ const PostFormFields = ({
{lengthError &&