mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
Review settings upgrades before applying defaults
Adds a review modal for settings upgrades, keeps the settings banner reviewable, and preserves explicit router settings during account import.
This commit is contained in:
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "وضع متصفح P2P خالص",
|
"advanced_pure_p2p_browser_aria": "وضع متصفح P2P خالص",
|
||||||
"advanced_options_saved_reloading": "تم حفظ الخيارات، جارٍ إعادة التحميل...",
|
"advanced_options_saved_reloading": "تم حفظ الخيارات، جارٍ إعادة التحميل...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "خطأ في حفظ الخيارات: {{message}}"
|
"advanced_error_saving_options": "خطأ في حفظ الخيارات: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "ترقية الإعدادات متاحة",
|
||||||
|
"advanced_settings_upgrade_tip": "هذه إعدادات 5chan الافتراضية الجديدة المفقودة من هذا الحساب. قم بإلغاء تحديد أي شيء لا تريده.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "إضافة موجهات HTTP جديدة",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "إضافة {{router}} إلى موجهات HTTP",
|
||||||
|
"settings_upgrade_keep_current": "الإبقاء على الإعدادات الحالية",
|
||||||
|
"settings_upgrade_apply_selected": "تطبيق التغييرات المحددة",
|
||||||
|
"settings_upgrade_error_saving": "تعذر حفظ ترقية الإعدادات.",
|
||||||
|
"settings_upgrade_review_notice": "ترقية الإعدادات متاحة",
|
||||||
|
"settings_upgrade_review_button": "مراجعة",
|
||||||
|
"settings_upgrade_never_show_again": "لا تظهر مرة أخرى"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "বিশুদ্ধ P2P ব্রাউজার মোড",
|
"advanced_pure_p2p_browser_aria": "বিশুদ্ধ P2P ব্রাউজার মোড",
|
||||||
"advanced_options_saved_reloading": "বিকল্পগুলো সংরক্ষণ করা হয়েছে, পুনরায় লোড করা হচ্ছে...",
|
"advanced_options_saved_reloading": "বিকল্পগুলো সংরক্ষণ করা হয়েছে, পুনরায় লোড করা হচ্ছে...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "বিকল্প সংরক্ষণে ত্রুটি: {{message}}"
|
"advanced_error_saving_options": "বিকল্প সংরক্ষণে ত্রুটি: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "সেটিংস আপগ্রেড উপলব্ধ",
|
||||||
|
"advanced_settings_upgrade_tip": "এগুলো এই অ্যাকাউন্টে অনুপস্থিত নতুন 5chan ডিফল্ট। যা চান না, তা আনচেক করুন।",
|
||||||
|
"advanced_settings_upgrade_http_routers": "নতুন HTTP রাউটার যোগ করুন",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "HTTP রাউটারে {{router}} যোগ করুন",
|
||||||
|
"settings_upgrade_keep_current": "বর্তমান সেটিংস রাখুন",
|
||||||
|
"settings_upgrade_apply_selected": "নির্বাচিত পরিবর্তনগুলি প্রয়োগ করুন",
|
||||||
|
"settings_upgrade_error_saving": "সেটিংস আপগ্রেড সংরক্ষণ করা যায়নি।",
|
||||||
|
"settings_upgrade_review_notice": "সেটিংস আপগ্রেড উপলব্ধ",
|
||||||
|
"settings_upgrade_review_button": "পর্যালোচনা",
|
||||||
|
"settings_upgrade_never_show_again": "আর দেখাবেন না"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Režim čistého P2P prohlížeče",
|
"advanced_pure_p2p_browser_aria": "Režim čistého P2P prohlížeče",
|
||||||
"advanced_options_saved_reloading": "Možnosti uloženy, načítání...",
|
"advanced_options_saved_reloading": "Možnosti uloženy, načítání...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Chyba při ukládání možností: {{message}}"
|
"advanced_error_saving_options": "Chyba při ukládání možností: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "upgrade nastavení k dispozici",
|
||||||
|
"advanced_settings_upgrade_tip": "Toto jsou nová výchozí nastavení 5chan, která v tomto účtu chybí. Zrušte zaškrtnutí u všeho, co nechcete.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "přidat nové HTTP směrovače",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "Přidat {{router}} do HTTP směrovačů",
|
||||||
|
"settings_upgrade_keep_current": "ponechat stávající nastavení",
|
||||||
|
"settings_upgrade_apply_selected": "použít vybrané změny",
|
||||||
|
"settings_upgrade_error_saving": "Nepodařilo se uložit upgrade nastavení.",
|
||||||
|
"settings_upgrade_review_notice": "upgrade nastavení k dispozici",
|
||||||
|
"settings_upgrade_review_button": "zkontrolovat",
|
||||||
|
"settings_upgrade_never_show_again": "už nezobrazovat"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Ren P2P-browsertilstand",
|
"advanced_pure_p2p_browser_aria": "Ren P2P-browsertilstand",
|
||||||
"advanced_options_saved_reloading": "Indstillinger gemt, genindlæser...",
|
"advanced_options_saved_reloading": "Indstillinger gemt, genindlæser...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Fejl ved lagring af indstillinger: {{message}}"
|
"advanced_error_saving_options": "Fejl ved lagring af indstillinger: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "indstillingsopgradering tilgængelig",
|
||||||
|
"advanced_settings_upgrade_tip": "Dette er nye 5chan-standarder, der mangler på denne konto. Fjern markeringen for alt, du ikke ønsker.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "tilføj nye HTTP-routere",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "Tilføj {{router}} til HTTP-routere",
|
||||||
|
"settings_upgrade_keep_current": "behold nuværende indstillinger",
|
||||||
|
"settings_upgrade_apply_selected": "anvend valgte ændringer",
|
||||||
|
"settings_upgrade_error_saving": "Kunne ikke gemme indstillingsopgraderingen.",
|
||||||
|
"settings_upgrade_review_notice": "indstillingsopgradering tilgængelig",
|
||||||
|
"settings_upgrade_review_button": "gennemgå",
|
||||||
|
"settings_upgrade_never_show_again": "vis aldrig igen"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Reiner P2P-Browsermodus",
|
"advanced_pure_p2p_browser_aria": "Reiner P2P-Browsermodus",
|
||||||
"advanced_options_saved_reloading": "Optionen gespeichert, wird neu geladen...",
|
"advanced_options_saved_reloading": "Optionen gespeichert, wird neu geladen...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Fehler beim Speichern der Optionen: {{message}}"
|
"advanced_error_saving_options": "Fehler beim Speichern der Optionen: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "Einstellungs-Upgrade verfügbar",
|
||||||
|
"advanced_settings_upgrade_tip": "Dies sind neue 5chan-Standardeinstellungen, die in diesem Konto fehlen. Deaktivieren Sie alles, was Sie nicht möchten.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "neue HTTP-Router hinzufügen",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "{{router}} zu HTTP-Routern hinzufügen",
|
||||||
|
"settings_upgrade_keep_current": "aktuelle Einstellungen beibehalten",
|
||||||
|
"settings_upgrade_apply_selected": "ausgewählte Änderungen anwenden",
|
||||||
|
"settings_upgrade_error_saving": "Die Einstellungsaktualisierung konnte nicht gespeichert werden.",
|
||||||
|
"settings_upgrade_review_notice": "Einstellungs-Upgrade verfügbar",
|
||||||
|
"settings_upgrade_review_button": "überprüfen",
|
||||||
|
"settings_upgrade_never_show_again": "nie wieder anzeigen"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Λειτουργία καθαρού P2P προγράμματος περιήγησης",
|
"advanced_pure_p2p_browser_aria": "Λειτουργία καθαρού P2P προγράμματος περιήγησης",
|
||||||
"advanced_options_saved_reloading": "Οι επιλογές αποθηκεύτηκαν, επαναφόρτωση...",
|
"advanced_options_saved_reloading": "Οι επιλογές αποθηκεύτηκαν, επαναφόρτωση...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Σφάλμα αποθήκευσης ρυθμίσεων: {{message}}"
|
"advanced_error_saving_options": "Σφάλμα αποθήκευσης ρυθμίσεων: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "αναβάθμιση ρυθμίσεων διαθέσιμη",
|
||||||
|
"advanced_settings_upgrade_tip": "Αυτές είναι νέες προεπιλογές 5chan που λείπουν από αυτόν τον λογαριασμό. Αποεπιλέξτε ό,τι δεν θέλετε.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "προσθήκη νέων δρομολογητών HTTP",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "Προσθήκη {{router}} σε δρομολογητές HTTP",
|
||||||
|
"settings_upgrade_keep_current": "διατήρηση τρεχουσών ρυθμίσεων",
|
||||||
|
"settings_upgrade_apply_selected": "εφαρμογή επιλεγμένων αλλαγών",
|
||||||
|
"settings_upgrade_error_saving": "Δεν ήταν δυνατή η αποθήκευση της αναβάθμισης ρυθμίσεων.",
|
||||||
|
"settings_upgrade_review_notice": "αναβάθμιση ρυθμίσεων διαθέσιμη",
|
||||||
|
"settings_upgrade_review_button": "έλεγχος",
|
||||||
|
"settings_upgrade_never_show_again": "να μην εμφανιστεί ξανά"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -451,5 +451,15 @@
|
|||||||
"tex_preview_title": "<tex>TeX</tex> Preview",
|
"tex_preview_title": "<tex>TeX</tex> Preview",
|
||||||
"tex_preview_protip": "Use [math][/math] tags for inline, and [eqn][/eqn] tags for block equations.",
|
"tex_preview_protip": "Use [math][/math] tags for inline, and [eqn][/eqn] tags for block equations.",
|
||||||
"tex_preview_input_label": "TeX source",
|
"tex_preview_input_label": "TeX source",
|
||||||
"preview_tex_equations": "Preview TeX equations"
|
"preview_tex_equations": "Preview TeX equations",
|
||||||
|
"advanced_settings_upgrade_available": "settings upgrade available",
|
||||||
|
"advanced_settings_upgrade_tip": "These are new 5chan defaults missing from this account. Uncheck anything you do not want.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "add new HTTP routers",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "Add {{router}} to HTTP routers",
|
||||||
|
"settings_upgrade_keep_current": "keep current settings",
|
||||||
|
"settings_upgrade_apply_selected": "apply selected changes",
|
||||||
|
"settings_upgrade_error_saving": "Could not save the settings upgrade.",
|
||||||
|
"settings_upgrade_review_notice": "settings upgrade available",
|
||||||
|
"settings_upgrade_review_button": "review",
|
||||||
|
"settings_upgrade_never_show_again": "never show again"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Modo de navegador P2P puro",
|
"advanced_pure_p2p_browser_aria": "Modo de navegador P2P puro",
|
||||||
"advanced_options_saved_reloading": "Opciones guardadas, recargando...",
|
"advanced_options_saved_reloading": "Opciones guardadas, recargando...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Error al guardar las opciones: {{message}}"
|
"advanced_error_saving_options": "Error al guardar las opciones: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "actualización de configuración disponible",
|
||||||
|
"advanced_settings_upgrade_tip": "Estos son nuevos valores predeterminados de 5chan que faltan en esta cuenta. Desmarca lo que no quieras.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "añadir nuevos enrutadores HTTP",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "Agregar {{router}} a los enrutadores HTTP",
|
||||||
|
"settings_upgrade_keep_current": "mantener la configuración actual",
|
||||||
|
"settings_upgrade_apply_selected": "aplicar cambios seleccionados",
|
||||||
|
"settings_upgrade_error_saving": "No se pudo guardar la actualización de configuración.",
|
||||||
|
"settings_upgrade_review_notice": "actualización de configuración disponible",
|
||||||
|
"settings_upgrade_review_button": "revisar",
|
||||||
|
"settings_upgrade_never_show_again": "no volver a mostrar"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "حالت مرورگر P2P خالص",
|
"advanced_pure_p2p_browser_aria": "حالت مرورگر P2P خالص",
|
||||||
"advanced_options_saved_reloading": "گزینهها ذخیره شد، در حال بارگذاری مجدد...",
|
"advanced_options_saved_reloading": "گزینهها ذخیره شد، در حال بارگذاری مجدد...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "خطا در ذخیره گزینهها: {{message}}"
|
"advanced_error_saving_options": "خطا در ذخیره گزینهها: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "ارتقای تنظیمات موجود است",
|
||||||
|
"advanced_settings_upgrade_tip": "اینها تنظیمات پیشفرض جدید 5chan هستند که در این حساب وجود ندارند. هر موردی را که نمیخواهید، از حالت انتخاب خارج کنید.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "افزودن مسیریابهای HTTP جدید",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "افزودن {{router}} به مسیریابهای HTTP",
|
||||||
|
"settings_upgrade_keep_current": "حفظ تنظیمات فعلی",
|
||||||
|
"settings_upgrade_apply_selected": "اعمال تغییرات انتخابشده",
|
||||||
|
"settings_upgrade_error_saving": "امکان ذخیرهٔ ارتقای تنظیمات وجود نداشت.",
|
||||||
|
"settings_upgrade_review_notice": "ارتقای تنظیمات موجود است",
|
||||||
|
"settings_upgrade_review_button": "بررسی",
|
||||||
|
"settings_upgrade_never_show_again": "دیگر نشان نده"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Puhdas P2P-selaimen tila",
|
"advanced_pure_p2p_browser_aria": "Puhdas P2P-selaimen tila",
|
||||||
"advanced_options_saved_reloading": "Asetukset tallennettu, ladataan uudelleen...",
|
"advanced_options_saved_reloading": "Asetukset tallennettu, ladataan uudelleen...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Virhe asetusten tallennuksessa: {{message}}"
|
"advanced_error_saving_options": "Virhe asetusten tallennuksessa: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "asetuspäivitys saatavilla",
|
||||||
|
"advanced_settings_upgrade_tip": "Nämä ovat uusia 5chan-oletusasetuksia, jotka puuttuvat tältä tililtä. Poista valinta kaikista, joita et halua.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "lisää uudet HTTP-reitittimet",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "Lisää {{router}} HTTP-reitittimiin",
|
||||||
|
"settings_upgrade_keep_current": "säilytä nykyiset asetukset",
|
||||||
|
"settings_upgrade_apply_selected": "käytä valitut muutokset",
|
||||||
|
"settings_upgrade_error_saving": "Asetusten päivitystä ei voitu tallentaa.",
|
||||||
|
"settings_upgrade_review_notice": "asetuspäivitys saatavilla",
|
||||||
|
"settings_upgrade_review_button": "tarkista",
|
||||||
|
"settings_upgrade_never_show_again": "älä näytä uudelleen"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Purong P2P browser mode",
|
"advanced_pure_p2p_browser_aria": "Purong P2P browser mode",
|
||||||
"advanced_options_saved_reloading": "Na-save ang mga opsyon, nagre-reload...",
|
"advanced_options_saved_reloading": "Na-save ang mga opsyon, nagre-reload...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Error sa pag-save ng mga opsyon: {{message}}"
|
"advanced_error_saving_options": "Error sa pag-save ng mga opsyon: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "may upgrade ng setting",
|
||||||
|
"advanced_settings_upgrade_tip": "Ito ang mga bagong default ng 5chan na wala sa account na ito. Alisin ang check sa anumang ayaw mo.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "magdagdag ng mga bagong HTTP routers",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "Idagdag ang {{router}} sa HTTP routers",
|
||||||
|
"settings_upgrade_keep_current": "panatilihin ang kasalukuyang mga setting",
|
||||||
|
"settings_upgrade_apply_selected": "ilapat ang mga napiling pagbabago",
|
||||||
|
"settings_upgrade_error_saving": "Hindi ma-save ang upgrade ng mga setting.",
|
||||||
|
"settings_upgrade_review_notice": "may upgrade ng setting",
|
||||||
|
"settings_upgrade_review_button": "suriin",
|
||||||
|
"settings_upgrade_never_show_again": "huwag nang ipakita muli"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Mode navigateur P2P pur",
|
"advanced_pure_p2p_browser_aria": "Mode navigateur P2P pur",
|
||||||
"advanced_options_saved_reloading": "Options enregistrées, rechargement...",
|
"advanced_options_saved_reloading": "Options enregistrées, rechargement...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Erreur lors de l'enregistrement des options : {{message}}"
|
"advanced_error_saving_options": "Erreur lors de l'enregistrement des options : {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "mise à jour des paramètres disponible",
|
||||||
|
"advanced_settings_upgrade_tip": "Ce sont de nouvelles valeurs par défaut 5chan absentes de ce compte. Décochez tout ce que vous ne voulez pas.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "ajouter de nouveaux routeurs HTTP",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "Ajouter {{router}} aux routeurs HTTP",
|
||||||
|
"settings_upgrade_keep_current": "conserver les paramètres actuels",
|
||||||
|
"settings_upgrade_apply_selected": "appliquer les modifications sélectionnées",
|
||||||
|
"settings_upgrade_error_saving": "Impossible d'enregistrer la mise à niveau des paramètres.",
|
||||||
|
"settings_upgrade_review_notice": "mise à jour des paramètres disponible",
|
||||||
|
"settings_upgrade_review_button": "examiner",
|
||||||
|
"settings_upgrade_never_show_again": "ne plus afficher"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "מצב דפדפן P2P טהור",
|
"advanced_pure_p2p_browser_aria": "מצב דפדפן P2P טהור",
|
||||||
"advanced_options_saved_reloading": "ההגדרות נשמרו, טוען מחדש...",
|
"advanced_options_saved_reloading": "ההגדרות נשמרו, טוען מחדש...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "שגיאה בשמירת האפשרויות: {{message}}"
|
"advanced_error_saving_options": "שגיאה בשמירת האפשרויות: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "שדרוג הגדרות זמין",
|
||||||
|
"advanced_settings_upgrade_tip": "אלה ברירות המחדל החדשות של 5chan שחסרות בחשבון זה. בטל סימון של כל מה שאינך רוצה.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "הוסף נתבי HTTP חדשים",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "הוסף את {{router}} לנתבי HTTP",
|
||||||
|
"settings_upgrade_keep_current": "שמור על ההגדרות הנוכחיות",
|
||||||
|
"settings_upgrade_apply_selected": "החל שינויים שנבחרו",
|
||||||
|
"settings_upgrade_error_saving": "לא ניתן לשמור את שדרוג ההגדרות.",
|
||||||
|
"settings_upgrade_review_notice": "שדרוג הגדרות זמין",
|
||||||
|
"settings_upgrade_review_button": "סקור",
|
||||||
|
"settings_upgrade_never_show_again": "לא להציג שוב"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "शुद्ध P2P ब्राउज़र मोड",
|
"advanced_pure_p2p_browser_aria": "शुद्ध P2P ब्राउज़र मोड",
|
||||||
"advanced_options_saved_reloading": "विकल्प सहेजे गए, पुनः लोड हो रहा है...",
|
"advanced_options_saved_reloading": "विकल्प सहेजे गए, पुनः लोड हो रहा है...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "विकल्प सहेजने में त्रुटि: {{message}}"
|
"advanced_error_saving_options": "विकल्प सहेजने में त्रुटि: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "सेटिंग्स अपग्रेड उपलब्ध",
|
||||||
|
"advanced_settings_upgrade_tip": "ये नए 5chan डिफ़ॉल्ट हैं जो इस खाते में नहीं हैं। जो आप नहीं चाहते, उसे अनचेक करें।",
|
||||||
|
"advanced_settings_upgrade_http_routers": "नए HTTP राउटर जोड़ें",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "HTTP राउटर में {{router}} जोड़ें",
|
||||||
|
"settings_upgrade_keep_current": "वर्तमान सेटिंग्स रखें",
|
||||||
|
"settings_upgrade_apply_selected": "चयनित परिवर्तन लागू करें",
|
||||||
|
"settings_upgrade_error_saving": "सेटिंग्स अपग्रेड सहेज नहीं सके।",
|
||||||
|
"settings_upgrade_review_notice": "सेटिंग्स अपग्रेड उपलब्ध",
|
||||||
|
"settings_upgrade_review_button": "समीक्षा करें",
|
||||||
|
"settings_upgrade_never_show_again": "फिर कभी न दिखाएँ"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Tiszta P2P böngésző mód",
|
"advanced_pure_p2p_browser_aria": "Tiszta P2P böngésző mód",
|
||||||
"advanced_options_saved_reloading": "Beállítások mentve, újratöltés...",
|
"advanced_options_saved_reloading": "Beállítások mentve, újratöltés...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Hiba a beállítások mentésekor: {{message}}"
|
"advanced_error_saving_options": "Hiba a beállítások mentésekor: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "beállításfrissítés elérhető",
|
||||||
|
"advanced_settings_upgrade_tip": "Ezek olyan új 5chan-alapértelmezések, amelyek hiányoznak ebből a fiókból. Vegye ki a pipát mindenből, amit nem szeretne.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "új HTTP routerek hozzáadása",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "{{router}} hozzáadása HTTP routerekhez",
|
||||||
|
"settings_upgrade_keep_current": "jelenlegi beállítások megtartása",
|
||||||
|
"settings_upgrade_apply_selected": "kiválasztott módosítások alkalmazása",
|
||||||
|
"settings_upgrade_error_saving": "A beállítások frissítése nem menthető el.",
|
||||||
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Mode browser P2P murni",
|
"advanced_pure_p2p_browser_aria": "Mode browser P2P murni",
|
||||||
"advanced_options_saved_reloading": "Opsi disimpan, memuat ulang...",
|
"advanced_options_saved_reloading": "Opsi disimpan, memuat ulang...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Kesalahan saat menyimpan opsi: {{message}}"
|
"advanced_error_saving_options": "Kesalahan saat menyimpan opsi: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "pembaruan pengaturan tersedia",
|
||||||
|
"advanced_settings_upgrade_tip": "Ini adalah default 5chan baru yang belum ada di akun ini. Hapus centang pada apa pun yang tidak Anda inginkan.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "tambahkan router HTTP baru",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "Tambahkan {{router}} ke router HTTP",
|
||||||
|
"settings_upgrade_keep_current": "pertahankan pengaturan saat ini",
|
||||||
|
"settings_upgrade_apply_selected": "terapkan perubahan yang dipilih",
|
||||||
|
"settings_upgrade_error_saving": "Pembaruan pengaturan tidak dapat disimpan.",
|
||||||
|
"settings_upgrade_review_notice": "pembaruan pengaturan tersedia",
|
||||||
|
"settings_upgrade_review_button": "tinjau",
|
||||||
|
"settings_upgrade_never_show_again": "jangan tampilkan lagi"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Modalità browser P2P puro",
|
"advanced_pure_p2p_browser_aria": "Modalità browser P2P puro",
|
||||||
"advanced_options_saved_reloading": "Opzioni salvate, ricaricamento...",
|
"advanced_options_saved_reloading": "Opzioni salvate, ricaricamento...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Errore nel salvataggio delle opzioni: {{message}}"
|
"advanced_error_saving_options": "Errore nel salvataggio delle opzioni: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "aggiornamento impostazioni disponibile",
|
||||||
|
"advanced_settings_upgrade_tip": "Queste sono nuove impostazioni predefinite di 5chan assenti in questo account. Deseleziona ciò che non desideri.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "aggiungi nuovi router HTTP",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "Aggiungi {{router}} ai router HTTP",
|
||||||
|
"settings_upgrade_keep_current": "mantieni le impostazioni attuali",
|
||||||
|
"settings_upgrade_apply_selected": "applica modifiche selezionate",
|
||||||
|
"settings_upgrade_error_saving": "Impossibile salvare l'aggiornamento delle impostazioni.",
|
||||||
|
"settings_upgrade_review_notice": "aggiornamento impostazioni disponibile",
|
||||||
|
"settings_upgrade_review_button": "rivedi",
|
||||||
|
"settings_upgrade_never_show_again": "non mostrare più"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "純粋なP2Pブラウザモード",
|
"advanced_pure_p2p_browser_aria": "純粋なP2Pブラウザモード",
|
||||||
"advanced_options_saved_reloading": "オプションを保存しました。再読み込み中...",
|
"advanced_options_saved_reloading": "オプションを保存しました。再読み込み中...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "オプションの保存中にエラーが発生しました: {{message}}"
|
"advanced_error_saving_options": "オプションの保存中にエラーが発生しました: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "設定のアップグレードが利用可能",
|
||||||
|
"advanced_settings_upgrade_tip": "これはこのアカウントに不足している新しい5chanのデフォルト設定です。不要なものはチェックを外してください。",
|
||||||
|
"advanced_settings_upgrade_http_routers": "新しいHTTPルーターを追加",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "{{router}} を HTTPルーターに追加",
|
||||||
|
"settings_upgrade_keep_current": "現在の設定を維持",
|
||||||
|
"settings_upgrade_apply_selected": "選択した変更を適用",
|
||||||
|
"settings_upgrade_error_saving": "設定のアップグレードを保存できませんでした。",
|
||||||
|
"settings_upgrade_review_notice": "設定のアップグレードが利用可能",
|
||||||
|
"settings_upgrade_review_button": "確認",
|
||||||
|
"settings_upgrade_never_show_again": "今後表示しない"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "순수 P2P 브라우저 모드",
|
"advanced_pure_p2p_browser_aria": "순수 P2P 브라우저 모드",
|
||||||
"advanced_options_saved_reloading": "옵션이 저장되었습니다. 다시 불러오는 중...",
|
"advanced_options_saved_reloading": "옵션이 저장되었습니다. 다시 불러오는 중...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "옵션 저장 오류: {{message}}"
|
"advanced_error_saving_options": "옵션 저장 오류: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "설정 업그레이드 사용 가능",
|
||||||
|
"advanced_settings_upgrade_tip": "이 계정에 없는 새로운 5chan 기본값입니다. 원하지 않는 항목은 선택을 해제하세요.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "새 HTTP 라우터 추가",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "{{router}}를 HTTP 라우터에 추가",
|
||||||
|
"settings_upgrade_keep_current": "현재 설정 유지",
|
||||||
|
"settings_upgrade_apply_selected": "선택한 변경 사항 적용",
|
||||||
|
"settings_upgrade_error_saving": "설정 업그레이드를 저장할 수 없습니다.",
|
||||||
|
"settings_upgrade_review_notice": "설정 업그레이드 사용 가능",
|
||||||
|
"settings_upgrade_review_button": "검토",
|
||||||
|
"settings_upgrade_never_show_again": "다시 표시하지 않기"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "शुद्ध P2P ब्राउझर मोड",
|
"advanced_pure_p2p_browser_aria": "शुद्ध P2P ब्राउझर मोड",
|
||||||
"advanced_options_saved_reloading": "पर्याय जतन केले, पुन्हा लोड होत आहे...",
|
"advanced_options_saved_reloading": "पर्याय जतन केले, पुन्हा लोड होत आहे...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "पर्याय जतन करताना त्रुटी: {{message}}"
|
"advanced_error_saving_options": "पर्याय जतन करताना त्रुटी: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "सेटिंग्ज अपग्रेड उपलब्ध",
|
||||||
|
"advanced_settings_upgrade_tip": "हे या खात्यात नसलेले नवीन 5chan डिफॉल्ट आहेत. जे तुम्हाला नको आहे ते अनचेक करा.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "नवीन HTTP राऊटर जोडा",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "HTTP राऊटरमध्ये {{router}} जोडा",
|
||||||
|
"settings_upgrade_keep_current": "सध्याच्या सेटिंग्ज ठेवा",
|
||||||
|
"settings_upgrade_apply_selected": "निवडलेले बदल लागू करा",
|
||||||
|
"settings_upgrade_error_saving": "सेटिंग्ज अपग्रेड जतन करू शकले नाही.",
|
||||||
|
"settings_upgrade_review_notice": "सेटिंग्ज अपग्रेड उपलब्ध",
|
||||||
|
"settings_upgrade_review_button": "पुनरावलोकन",
|
||||||
|
"settings_upgrade_never_show_again": "पुन्हा दाखवू नका"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Pure P2P-browsermodus",
|
"advanced_pure_p2p_browser_aria": "Pure P2P-browsermodus",
|
||||||
"advanced_options_saved_reloading": "Opties opgeslagen, opnieuw laden...",
|
"advanced_options_saved_reloading": "Opties opgeslagen, opnieuw laden...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Fout bij het opslaan van opties: {{message}}"
|
"advanced_error_saving_options": "Fout bij het opslaan van opties: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "instellingenupgrade beschikbaar",
|
||||||
|
"advanced_settings_upgrade_tip": "Dit zijn nieuwe 5chan-standaardinstellingen die in dit account ontbreken. Vink alles uit wat je niet wilt.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "nieuwe HTTP-routers toevoegen",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "{{router}} toevoegen aan HTTP-routers",
|
||||||
|
"settings_upgrade_keep_current": "huidige instellingen behouden",
|
||||||
|
"settings_upgrade_apply_selected": "geselecteerde wijzigingen toepassen",
|
||||||
|
"settings_upgrade_error_saving": "De instellingenupgrade kon niet worden opgeslagen.",
|
||||||
|
"settings_upgrade_review_notice": "instellingenupgrade beschikbaar",
|
||||||
|
"settings_upgrade_review_button": "bekijken",
|
||||||
|
"settings_upgrade_never_show_again": "niet meer tonen"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Ren P2P-nettlesermodus",
|
"advanced_pure_p2p_browser_aria": "Ren P2P-nettlesermodus",
|
||||||
"advanced_options_saved_reloading": "Innstillinger lagret, laster på nytt...",
|
"advanced_options_saved_reloading": "Innstillinger lagret, laster på nytt...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Feil ved lagring av alternativer: {{message}}"
|
"advanced_error_saving_options": "Feil ved lagring av alternativer: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "innstillingsoppgradering tilgjengelig",
|
||||||
|
"advanced_settings_upgrade_tip": "Dette er nye 5chan-standarder som mangler på denne kontoen. Fjern merkingen for alt du ikke ønsker.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "legg til nye HTTP-rutere",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "Legg til {{router}} i HTTP-rutere",
|
||||||
|
"settings_upgrade_keep_current": "behold gjeldende innstillinger",
|
||||||
|
"settings_upgrade_apply_selected": "bruk valgte endringer",
|
||||||
|
"settings_upgrade_error_saving": "Kunne ikke lagre innstillingsoppgraderingen.",
|
||||||
|
"settings_upgrade_review_notice": "innstillingsoppgradering tilgjengelig",
|
||||||
|
"settings_upgrade_review_button": "se gjennom",
|
||||||
|
"settings_upgrade_never_show_again": "vis aldri igjen"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Tryb przeglądarki czystego P2P",
|
"advanced_pure_p2p_browser_aria": "Tryb przeglądarki czystego P2P",
|
||||||
"advanced_options_saved_reloading": "Opcje zapisane, ładowanie ponowne...",
|
"advanced_options_saved_reloading": "Opcje zapisane, ładowanie ponowne...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Błąd podczas zapisywania opcji: {{message}}"
|
"advanced_error_saving_options": "Błąd podczas zapisywania opcji: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "aktualizacja ustawień dostępna",
|
||||||
|
"advanced_settings_upgrade_tip": "To nowe domyślne ustawienia 5chan, których brakuje na tym koncie. Odznacz wszystko, czego nie chcesz.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "dodaj nowe routery HTTP",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "Dodaj {{router}} do routerów HTTP",
|
||||||
|
"settings_upgrade_keep_current": "zachowaj obecne ustawienia",
|
||||||
|
"settings_upgrade_apply_selected": "zastosuj wybrane zmiany",
|
||||||
|
"settings_upgrade_error_saving": "Nie udało się zapisać aktualizacji ustawień.",
|
||||||
|
"settings_upgrade_review_notice": "aktualizacja ustawień dostępna",
|
||||||
|
"settings_upgrade_review_button": "przejrzyj",
|
||||||
|
"settings_upgrade_never_show_again": "nie pokazuj ponownie"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Modo de navegador P2P puro",
|
"advanced_pure_p2p_browser_aria": "Modo de navegador P2P puro",
|
||||||
"advanced_options_saved_reloading": "Opções salvas, recarregando...",
|
"advanced_options_saved_reloading": "Opções salvas, recarregando...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Erro ao salvar as opções: {{message}}"
|
"advanced_error_saving_options": "Erro ao salvar as opções: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "atualização de configurações disponível",
|
||||||
|
"advanced_settings_upgrade_tip": "Estas são novas predefinições do 5chan ausentes nesta conta. Desmarque o que não quiser.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "adicionar novos roteadores HTTP",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "Adicionar {{router}} aos roteadores HTTP",
|
||||||
|
"settings_upgrade_keep_current": "manter configurações atuais",
|
||||||
|
"settings_upgrade_apply_selected": "aplicar alterações selecionadas",
|
||||||
|
"settings_upgrade_error_saving": "Não foi possível salvar a atualização das configurações.",
|
||||||
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Mod browser P2P pur",
|
"advanced_pure_p2p_browser_aria": "Mod browser P2P pur",
|
||||||
"advanced_options_saved_reloading": "Opțiuni salvate, se reîncarcă...",
|
"advanced_options_saved_reloading": "Opțiuni salvate, se reîncarcă...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Eroare la salvarea opțiunilor: {{message}}"
|
"advanced_error_saving_options": "Eroare la salvarea opțiunilor: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "actualizare setări disponibilă",
|
||||||
|
"advanced_settings_upgrade_tip": "Acestea sunt noi setări implicite 5chan lipsă din acest cont. Debifați tot ce nu doriți.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "adaugă noi routere HTTP",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "Adaugă {{router}} la routerele HTTP",
|
||||||
|
"settings_upgrade_keep_current": "păstrează setările actuale",
|
||||||
|
"settings_upgrade_apply_selected": "aplică modificările selectate",
|
||||||
|
"settings_upgrade_error_saving": "Nu s-a putut salva actualizarea setărilor.",
|
||||||
|
"settings_upgrade_review_notice": "actualizare setări disponibilă",
|
||||||
|
"settings_upgrade_review_button": "revizuiește",
|
||||||
|
"settings_upgrade_never_show_again": "nu mai afișa"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Режим чистого P2P в браузере",
|
"advanced_pure_p2p_browser_aria": "Режим чистого P2P в браузере",
|
||||||
"advanced_options_saved_reloading": "Настройки сохранены, перезагрузка...",
|
"advanced_options_saved_reloading": "Настройки сохранены, перезагрузка...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Ошибка при сохранении параметров: {{message}}"
|
"advanced_error_saving_options": "Ошибка при сохранении параметров: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "обновление настроек доступно",
|
||||||
|
"advanced_settings_upgrade_tip": "Это новые настройки 5chan по умолчанию, отсутствующие в этом аккаунте. Снимите галочку с того, что вам не нужно.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "добавить новые HTTP-маршрутизаторы",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "Добавить {{router}} в HTTP-маршрутизаторы",
|
||||||
|
"settings_upgrade_keep_current": "сохранить текущие настройки",
|
||||||
|
"settings_upgrade_apply_selected": "применить выбранные изменения",
|
||||||
|
"settings_upgrade_error_saving": "Не удалось сохранить обновление настроек.",
|
||||||
|
"settings_upgrade_review_notice": "обновление настроек доступно",
|
||||||
|
"settings_upgrade_review_button": "просмотреть",
|
||||||
|
"settings_upgrade_never_show_again": "больше не показывать"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Modalitet shfletuesi P2P i pastër",
|
"advanced_pure_p2p_browser_aria": "Modalitet shfletuesi P2P i pastër",
|
||||||
"advanced_options_saved_reloading": "Opsionet u ruajtën, po ringarkohet...",
|
"advanced_options_saved_reloading": "Opsionet u ruajtën, po ringarkohet...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Gabim gjatë ruajtjes së opsioneve: {{message}}"
|
"advanced_error_saving_options": "Gabim gjatë ruajtjes së opsioneve: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "përmirësim i cilësimeve i disponueshëm",
|
||||||
|
"advanced_settings_upgrade_tip": "Këto janë parazgjedhje të reja 5chan që mungojnë në këtë llogari. Hiqni shenjën nga çdo gjë që nuk dëshironi.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "shto routerë HTTP të rinj",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "Shto {{router}} te routerët HTTP",
|
||||||
|
"settings_upgrade_keep_current": "mbaj cilësimet aktuale",
|
||||||
|
"settings_upgrade_apply_selected": "apliko ndryshimet e zgjedhura",
|
||||||
|
"settings_upgrade_error_saving": "Nuk u ruajt përmirësimi i cilësimeve.",
|
||||||
|
"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ë"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Rent P2P-webbläsarläge",
|
"advanced_pure_p2p_browser_aria": "Rent P2P-webbläsarläge",
|
||||||
"advanced_options_saved_reloading": "Alternativ sparade, laddar om...",
|
"advanced_options_saved_reloading": "Alternativ sparade, laddar om...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Fel vid sparande av alternativ: {{message}}"
|
"advanced_error_saving_options": "Fel vid sparande av alternativ: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "inställningsuppgradering tillgänglig",
|
||||||
|
"advanced_settings_upgrade_tip": "Det här är nya 5chan-standardinställningar som saknas i detta konto. Avmarkera allt du inte vill ha.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "lägg till nya HTTP-routrar",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "Lägg till {{router}} i HTTP-routrar",
|
||||||
|
"settings_upgrade_keep_current": "behåll nuvarande inställningar",
|
||||||
|
"settings_upgrade_apply_selected": "tillämpa valda ändringar",
|
||||||
|
"settings_upgrade_error_saving": "Kunde inte spara inställningsuppgraderingen.",
|
||||||
|
"settings_upgrade_review_notice": "inställningsuppgradering tillgänglig",
|
||||||
|
"settings_upgrade_review_button": "granska",
|
||||||
|
"settings_upgrade_never_show_again": "visa inte igen"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "శుద్ధ P2P బ్రౌజర్ మోడ్",
|
"advanced_pure_p2p_browser_aria": "శుద్ధ P2P బ్రౌజర్ మోడ్",
|
||||||
"advanced_options_saved_reloading": "ఎంపికలు సేవ్ చేయబడ్డాయి, మళ్లీ లోడ్ అవుతోంది...",
|
"advanced_options_saved_reloading": "ఎంపికలు సేవ్ చేయబడ్డాయి, మళ్లీ లోడ్ అవుతోంది...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "ఎంపికలు సేవ్ చేయడంలో లోపం: {{message}}"
|
"advanced_error_saving_options": "ఎంపికలు సేవ్ చేయడంలో లోపం: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "సెట్టింగ్ల అప్గ్రేడ్ అందుబాటులో ఉంది",
|
||||||
|
"advanced_settings_upgrade_tip": "ఇవి ఈ ఖాతాలో లేని కొత్త 5chan డిఫాల్ట్లు. మీకు కావనివి అన్చెక్ చేయండి.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "నూతన HTTP రౌటర్లను జోడించండి",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "HTTP రౌటర్లకు {{router}} జోడించండి",
|
||||||
|
"settings_upgrade_keep_current": "ప్రస్తుత అమరికలను ఉంచండి",
|
||||||
|
"settings_upgrade_apply_selected": "ఎంచుకున్న మార్పులను వర్తింపజేయండి",
|
||||||
|
"settings_upgrade_error_saving": "సెట్టింగ్ల అప్గ్రేడ్ను సేవ్ చేయలేకపోయింది.",
|
||||||
|
"settings_upgrade_review_notice": "సెట్టింగ్ల అప్గ్రేడ్ అందుబాటులో ఉంది",
|
||||||
|
"settings_upgrade_review_button": "సమీక్షించు",
|
||||||
|
"settings_upgrade_never_show_again": "మళ్లీ చూపించవద్దు"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "โหมดเบราว์เซอร์ P2P แบบบริสุทธิ์",
|
"advanced_pure_p2p_browser_aria": "โหมดเบราว์เซอร์ P2P แบบบริสุทธิ์",
|
||||||
"advanced_options_saved_reloading": "บันทึกตัวเลือกแล้ว กำลังโหลดใหม่...",
|
"advanced_options_saved_reloading": "บันทึกตัวเลือกแล้ว กำลังโหลดใหม่...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "เกิดข้อผิดพลาดในการบันทึกตัวเลือก: {{message}}"
|
"advanced_error_saving_options": "เกิดข้อผิดพลาดในการบันทึกตัวเลือก: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "มีการอัปเกรดการตั้งค่า",
|
||||||
|
"advanced_settings_upgrade_tip": "นี่คือค่าเริ่มต้น 5chan ใหม่ที่บัญชีนี้ยังไม่มี ให้ยกเลิกการเลือกสิ่งที่คุณไม่ต้องการ",
|
||||||
|
"advanced_settings_upgrade_http_routers": "เพิ่มเราเตอร์ HTTP ใหม่",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "เพิ่ม {{router}} ในเราเตอร์ HTTP",
|
||||||
|
"settings_upgrade_keep_current": "คงการตั้งค่าปัจจุบัน",
|
||||||
|
"settings_upgrade_apply_selected": "ใช้การเปลี่ยนแปลงที่เลือก",
|
||||||
|
"settings_upgrade_error_saving": "ไม่สามารถบันทึกการอัปเกรดการตั้งค่าได้",
|
||||||
|
"settings_upgrade_review_notice": "มีการอัปเกรดการตั้งค่า",
|
||||||
|
"settings_upgrade_review_button": "ตรวจสอบ",
|
||||||
|
"settings_upgrade_never_show_again": "ไม่ต้องแสดงอีก"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Saf P2P tarayıcı modu",
|
"advanced_pure_p2p_browser_aria": "Saf P2P tarayıcı modu",
|
||||||
"advanced_options_saved_reloading": "Seçenekler kaydedildi, yeniden yükleniyor...",
|
"advanced_options_saved_reloading": "Seçenekler kaydedildi, yeniden yükleniyor...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Seçenekler kaydedilirken hata oluştu: {{message}}"
|
"advanced_error_saving_options": "Seçenekler kaydedilirken hata oluştu: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "ayar yükseltmesi mevcut",
|
||||||
|
"advanced_settings_upgrade_tip": "Bunlar bu hesapta eksik olan yeni 5chan varsayılanlarıdır. İstemediğiniz her şeyin işaretini kaldırın.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "yeni HTTP yönlendiricileri ekle",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "{{router}} adresini HTTP yönlendiricilerine ekle",
|
||||||
|
"settings_upgrade_keep_current": "mevcut ayarları koru",
|
||||||
|
"settings_upgrade_apply_selected": "seçili değişiklikleri uygula",
|
||||||
|
"settings_upgrade_error_saving": "Ayar yükseltmesi kaydedilemedi.",
|
||||||
|
"settings_upgrade_review_notice": "ayar yükseltmesi mevcut",
|
||||||
|
"settings_upgrade_review_button": "incele",
|
||||||
|
"settings_upgrade_never_show_again": "bir daha gösterme"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Режим чистого P2P у браузері",
|
"advanced_pure_p2p_browser_aria": "Режим чистого P2P у браузері",
|
||||||
"advanced_options_saved_reloading": "Параметри збережено, перезавантаження...",
|
"advanced_options_saved_reloading": "Параметри збережено, перезавантаження...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Помилка збереження параметрів: {{message}}"
|
"advanced_error_saving_options": "Помилка збереження параметрів: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "оновлення налаштувань доступне",
|
||||||
|
"advanced_settings_upgrade_tip": "Це нові типові налаштування 5chan, яких немає в цьому обліковому записі. Зніміть позначку з того, чого не хочете.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "додати нові HTTP-маршрутизатори",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "Додати {{router}} до HTTP-маршрутизаторів",
|
||||||
|
"settings_upgrade_keep_current": "зберегти поточні налаштування",
|
||||||
|
"settings_upgrade_apply_selected": "застосувати вибрані зміни",
|
||||||
|
"settings_upgrade_error_saving": "Не вдалося зберегти оновлення налаштувань.",
|
||||||
|
"settings_upgrade_review_notice": "оновлення налаштувань доступне",
|
||||||
|
"settings_upgrade_review_button": "переглянути",
|
||||||
|
"settings_upgrade_never_show_again": "більше не показувати"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "خالص P2P براؤزر موڈ",
|
"advanced_pure_p2p_browser_aria": "خالص P2P براؤزر موڈ",
|
||||||
"advanced_options_saved_reloading": "اختیارات محفوظ ہو گئیں، دوبارہ لوڈ ہو رہا ہے...",
|
"advanced_options_saved_reloading": "اختیارات محفوظ ہو گئیں، دوبارہ لوڈ ہو رہا ہے...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "اختیارات محفوظ کرنے میں خرابی: {{message}}"
|
"advanced_error_saving_options": "اختیارات محفوظ کرنے میں خرابی: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "ترتیبات اپ گریڈ دستیاب",
|
||||||
|
"advanced_settings_upgrade_tip": "یہ نئے 5chan ڈیفالٹ ہیں جو اس اکاؤنٹ میں موجود نہیں۔ جو آپ نہیں چاہتے، ان کا نشان ہٹا دیں۔",
|
||||||
|
"advanced_settings_upgrade_http_routers": "نئے HTTP روٹرز شامل کریں",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "HTTP روٹرز میں {{router}} شامل کریں",
|
||||||
|
"settings_upgrade_keep_current": "موجودہ ترتیبات برقرار رکھیں",
|
||||||
|
"settings_upgrade_apply_selected": "منتخب تبدیلیاں لاگو کریں",
|
||||||
|
"settings_upgrade_error_saving": "ترتیبات اپ گریڈ محفوظ نہیں ہو سکی۔",
|
||||||
|
"settings_upgrade_review_notice": "ترتیبات اپ گریڈ دستیاب",
|
||||||
|
"settings_upgrade_review_button": "جائزہ لیں",
|
||||||
|
"settings_upgrade_never_show_again": "دوبارہ نہ دکھائیں"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "Chế độ trình duyệt P2P thuần túy",
|
"advanced_pure_p2p_browser_aria": "Chế độ trình duyệt P2P thuần túy",
|
||||||
"advanced_options_saved_reloading": "Đã lưu tùy chọn, đang tải lại...",
|
"advanced_options_saved_reloading": "Đã lưu tùy chọn, đang tải lại...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "Lỗi khi lưu tùy chọn: {{message}}"
|
"advanced_error_saving_options": "Lỗi khi lưu tùy chọn: {{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "nâng cấp cài đặt có sẵn",
|
||||||
|
"advanced_settings_upgrade_tip": "Đây là các giá trị mặc định mới của 5chan còn thiếu trong tài khoản này. Bỏ chọn những mục bạn không muốn.",
|
||||||
|
"advanced_settings_upgrade_http_routers": "thêm bộ định tuyến HTTP mới",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "Thêm {{router}} vào bộ định tuyến HTTP",
|
||||||
|
"settings_upgrade_keep_current": "giữ cài đặt hiện tại",
|
||||||
|
"settings_upgrade_apply_selected": "áp dụng các thay đổi đã chọn",
|
||||||
|
"settings_upgrade_error_saving": "Không thể lưu bản nâng cấp cài đặt.",
|
||||||
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,5 +432,15 @@
|
|||||||
"advanced_pure_p2p_browser_aria": "纯 P2P 浏览器模式",
|
"advanced_pure_p2p_browser_aria": "纯 P2P 浏览器模式",
|
||||||
"advanced_options_saved_reloading": "选项已保存,正在重新加载...",
|
"advanced_options_saved_reloading": "选项已保存,正在重新加载...",
|
||||||
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
"advanced_p2p_rpc_placeholder": "ws://<IP>:<port>/<secretAuthKey>",
|
||||||
"advanced_error_saving_options": "保存选项时出错:{{message}}"
|
"advanced_error_saving_options": "保存选项时出错:{{message}}",
|
||||||
|
"advanced_settings_upgrade_available": "设置升级可用",
|
||||||
|
"advanced_settings_upgrade_tip": "这些是当前账户缺少的新 5chan 默认设置。取消勾选您不需要的项目。",
|
||||||
|
"advanced_settings_upgrade_http_routers": "添加新的 HTTP 路由器",
|
||||||
|
"advanced_settings_upgrade_http_router_aria": "将 {{router}} 添加到 HTTP 路由器",
|
||||||
|
"settings_upgrade_keep_current": "保留当前设置",
|
||||||
|
"settings_upgrade_apply_selected": "应用所选更改",
|
||||||
|
"settings_upgrade_error_saving": "无法保存设置升级。",
|
||||||
|
"settings_upgrade_review_notice": "设置升级可用",
|
||||||
|
"settings_upgrade_review_button": "查看",
|
||||||
|
"settings_upgrade_never_show_again": "不再显示"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -270,6 +270,10 @@ vi.mock('../components/settings-modal', () => ({
|
|||||||
default: MockSettingsModal,
|
default: MockSettingsModal,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('../components/settings-upgrade-modal', () => ({
|
||||||
|
default: makeNamedComponent('settings-upgrade-modal'),
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock('../components/reply-modal', () => ({
|
vi.mock('../components/reply-modal', () => ({
|
||||||
default: ({ parentCid, postCid }: { parentCid: string; postCid: string }) => createElement('div', { 'data-testid': 'reply-modal' }, `${parentCid}:${postCid}`),
|
default: ({ parentCid, postCid }: { parentCid: string; postCid: string }) => createElement('div', { 'data-testid': 'reply-modal' }, `${parentCid}:${postCid}`),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ const DirectoryModal = lazy(() => import('./components/directory-modal'));
|
|||||||
const DisclaimerModal = lazy(() => import('./components/disclaimer-modal'));
|
const DisclaimerModal = lazy(() => import('./components/disclaimer-modal'));
|
||||||
const ReplyModal = lazy(() => import('./components/reply-modal'));
|
const ReplyModal = lazy(() => import('./components/reply-modal'));
|
||||||
const SettingsModal = lazy(() => import('./components/settings-modal'));
|
const SettingsModal = lazy(() => import('./components/settings-modal'));
|
||||||
|
const SettingsUpgradeModal = lazy(() => import('./components/settings-upgrade-modal'));
|
||||||
|
|
||||||
// Preload all theme assets (buttons, backgrounds) immediately on app load
|
// Preload all theme assets (buttons, backgrounds) immediately on app load
|
||||||
// to prevent visible loading delays when switching themes
|
// to prevent visible loading delays when switching themes
|
||||||
@@ -225,6 +226,9 @@ const GlobalLayout = () => {
|
|||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<ChallengeModal />
|
<ChallengeModal />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<SettingsUpgradeModal />
|
||||||
|
</Suspense>
|
||||||
{activeCid && threadCid && activeCommunityAddress && (
|
{activeCid && threadCid && activeCommunityAddress && (
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<ReplyModal
|
<ReplyModal
|
||||||
|
|||||||
@@ -4,20 +4,26 @@ import { createRoot, Root } from 'react-dom/client';
|
|||||||
import { MemoryRouter, useLocation } from 'react-router-dom';
|
import { MemoryRouter, useLocation } from 'react-router-dom';
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import SettingsModal from '../settings-modal';
|
import SettingsModal from '../settings-modal';
|
||||||
|
import useSettingsUpgradeReviewStore from '../../../stores/use-settings-upgrade-review-store';
|
||||||
|
|
||||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||||
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
|
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
|
||||||
|
|
||||||
const testState = vi.hoisted(() => ({
|
const testState = vi.hoisted(() => ({
|
||||||
account: {
|
account: {
|
||||||
|
id: 'account-1',
|
||||||
pkcOptions: {
|
pkcOptions: {
|
||||||
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
|
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
|
||||||
},
|
},
|
||||||
} as Record<string, any>,
|
} as Record<string, any>,
|
||||||
|
rpcSettings: {
|
||||||
|
state: 'disconnected',
|
||||||
|
} as Record<string, any>,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('@bitsocial/bitsocial-react-hooks', () => ({
|
vi.mock('@bitsocial/bitsocial-react-hooks', () => ({
|
||||||
useAccount: () => testState.account,
|
useAccount: () => testState.account,
|
||||||
|
usePkcRpcSettings: () => testState.rpcSettings,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('react-i18next', () => ({
|
vi.mock('react-i18next', () => ({
|
||||||
@@ -26,39 +32,39 @@ vi.mock('react-i18next', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../account-settings', () => ({
|
vi.mock('../account-settings/account-settings', () => ({
|
||||||
default: () => <div data-testid='account-settings'>account-settings</div>,
|
default: () => <div data-testid='account-settings'>account-settings</div>,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../crypto-address-setting', () => ({
|
vi.mock('../crypto-address-setting/crypto-address-setting', () => ({
|
||||||
default: () => <div data-testid='crypto-address-setting'>crypto-address-setting</div>,
|
default: () => <div data-testid='crypto-address-setting'>crypto-address-setting</div>,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../crypto-wallets-setting', () => ({
|
vi.mock('../crypto-wallets-setting/crypto-wallets-setting', () => ({
|
||||||
default: () => <div data-testid='crypto-wallets-setting'>crypto-wallets-setting</div>,
|
default: () => <div data-testid='crypto-wallets-setting'>crypto-wallets-setting</div>,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../interface-settings', () => ({
|
vi.mock('../interface-settings/interface-settings', () => ({
|
||||||
default: () => <div data-testid='interface-settings-panel'>interface-settings</div>,
|
default: () => <div data-testid='interface-settings-panel'>interface-settings</div>,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../media-hosting-settings', () => ({
|
vi.mock('../media-hosting-settings/media-hosting-settings', () => ({
|
||||||
default: () => <div data-testid='media-hosting-settings-panel'>media-hosting-settings</div>,
|
default: () => <div data-testid='media-hosting-settings-panel'>media-hosting-settings</div>,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../advanced-settings', () => ({
|
vi.mock('../advanced-settings/advanced-settings', () => ({
|
||||||
default: () => <div data-testid='advanced-settings-panel'>advanced-settings</div>,
|
default: () => <div data-testid='advanced-settings-panel'>advanced-settings</div>,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../subscriptions-setting', () => ({
|
vi.mock('../subscriptions-setting/subscriptions-setting', () => ({
|
||||||
default: () => <div data-testid='subscriptions-settings-panel'>subscriptions-settings</div>,
|
default: () => <div data-testid='subscriptions-settings-panel'>subscriptions-settings</div>,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../trusted-board-links-setting', () => ({
|
vi.mock('../trusted-board-links-setting/trusted-board-links-setting', () => ({
|
||||||
default: () => <div data-testid='trusted-board-links-settings-panel'>trusted-board-links-settings</div>,
|
default: () => <div data-testid='trusted-board-links-settings-panel'>trusted-board-links-settings</div>,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../p2p-stats-settings', () => ({
|
vi.mock('../p2p-stats-settings/p2p-stats-settings', () => ({
|
||||||
default: () => <div data-testid='p2p-stats-settings-panel'>p2p-stats-settings</div>,
|
default: () => <div data-testid='p2p-stats-settings-panel'>p2p-stats-settings</div>,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -91,6 +97,22 @@ const getSectionToggleByText = (text: string) => {
|
|||||||
describe('SettingsModal', () => {
|
describe('SettingsModal', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
localStorage.clear();
|
||||||
|
testState.account = {
|
||||||
|
id: 'account-1',
|
||||||
|
pkcOptions: {
|
||||||
|
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
testState.rpcSettings = {
|
||||||
|
state: 'disconnected',
|
||||||
|
};
|
||||||
|
useSettingsUpgradeReviewStore.setState({
|
||||||
|
hiddenReviewUpgradeKeys: [],
|
||||||
|
persistentDismissedUpgradeKeys: [],
|
||||||
|
reviewedUpgradeKeys: [],
|
||||||
|
reviewRequestId: 0,
|
||||||
|
});
|
||||||
container = document.createElement('div');
|
container = document.createElement('div');
|
||||||
document.body.appendChild(container);
|
document.body.appendChild(container);
|
||||||
root = createRoot(container);
|
root = createRoot(container);
|
||||||
@@ -175,6 +197,42 @@ describe('SettingsModal', () => {
|
|||||||
expect(container.querySelector('[data-testid="p2p-stats-settings-panel"]')).toBeNull();
|
expect(container.querySelector('[data-testid="p2p-stats-settings-panel"]')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('shows a review button for dismissed settings upgrades', async () => {
|
||||||
|
const upgradeKey = 'account-1:http-routers:https://routerofbitsocial.xyz|https://bsotracker.online';
|
||||||
|
testState.account = {
|
||||||
|
id: 'account-1',
|
||||||
|
pkcOptions: {
|
||||||
|
httpRoutersOptions: ['https://routing.lol', 'https://peers.pleb.bot', 'https://peers.plebpubsub.xyz', 'https://peers.forumindex.com'],
|
||||||
|
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
useSettingsUpgradeReviewStore.getState().dismissUpgradeKeys([upgradeKey]);
|
||||||
|
|
||||||
|
render('/all/settings');
|
||||||
|
|
||||||
|
expect(container.textContent).toContain('settings_upgrade_review_notice');
|
||||||
|
const reviewButton = Array.from(container.querySelectorAll('button')).find((candidate) => candidate.textContent === 'settings_upgrade_review_button');
|
||||||
|
if (!reviewButton) {
|
||||||
|
throw new Error('settings upgrade review button not found');
|
||||||
|
}
|
||||||
|
const reviewBanner = container.querySelector('[data-testid="settings-upgrade-review-banner"]');
|
||||||
|
const settingsDialog = container.querySelector('dialog[aria-labelledby="settings-modal-title"]');
|
||||||
|
|
||||||
|
expect(reviewBanner).not.toBeNull();
|
||||||
|
expect(reviewBanner?.textContent).toBe('settings_upgrade_review_notice [settings_upgrade_review_button]');
|
||||||
|
expect(reviewBanner?.querySelector('br')).toBeNull();
|
||||||
|
expect(reviewBanner?.textContent).not.toContain('settings_upgrade_never_show_again');
|
||||||
|
expect(settingsDialog?.lastElementChild).toBe(reviewBanner);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
reviewButton.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(localStorage.getItem('5chan:dismissed-settings-upgrades')).not.toContain(upgradeKey);
|
||||||
|
expect(localStorage.getItem('5chan:hidden-settings-upgrade-reviews')).not.toContain(upgradeKey);
|
||||||
|
expect(useSettingsUpgradeReviewStore.getState().reviewRequestId).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('opens the p2p stats section from its hash when browser pure p2p is enabled', () => {
|
it('opens the p2p stats section from its hash when browser pure p2p is enabled', () => {
|
||||||
render('/all/settings#p2p-stats-settings');
|
render('/all/settings#p2p-stats-settings');
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import AccountSettings from '../account-settings';
|
|||||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||||
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
|
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
|
||||||
const IMPORTED_ACCOUNT_ADDRESSES_STORAGE_KEY = 'importedAccountAddresses';
|
const IMPORTED_ACCOUNT_ADDRESSES_STORAGE_KEY = 'importedAccountAddresses';
|
||||||
|
const LEGACY_DEFAULT_HTTP_ROUTERS = ['https://routing.lol', 'https://peers.pleb.bot', 'https://peers.plebpubsub.xyz', 'https://peers.forumindex.com'];
|
||||||
|
|
||||||
const hookMocks = vi.hoisted(() => ({
|
const hookMocks = vi.hoisted(() => ({
|
||||||
deleteAccount: vi.fn(),
|
deleteAccount: vi.fn(),
|
||||||
@@ -343,6 +344,7 @@ describe('AccountSettings', () => {
|
|||||||
expect(hookMocks.importAccount).toHaveBeenCalledOnce();
|
expect(hookMocks.importAccount).toHaveBeenCalledOnce();
|
||||||
const importedPayload = JSON.parse(hookMocks.importAccount.mock.calls[0][0]);
|
const importedPayload = JSON.parse(hookMocks.importAccount.mock.calls[0][0]);
|
||||||
expect(importedPayload.account.subscriptions).toEqual(['business.eth', 'music-posting.bso']);
|
expect(importedPayload.account.subscriptions).toEqual(['business.eth', 'music-posting.bso']);
|
||||||
|
expect(importedPayload.account.pkcOptions.httpRoutersOptions).toEqual(LEGACY_DEFAULT_HTTP_ROUTERS);
|
||||||
expect(localStorage.getItem(IMPORTED_ACCOUNT_ADDRESSES_STORAGE_KEY)).toBe(JSON.stringify(['0x999']));
|
expect(localStorage.getItem(IMPORTED_ACCOUNT_ADDRESSES_STORAGE_KEY)).toBe(JSON.stringify(['0x999']));
|
||||||
expect(localStorage.getItem('importedAccountAddress')).toBe('0x999');
|
expect(localStorage.getItem('importedAccountAddress')).toBe('0x999');
|
||||||
expect(hookMocks.setActiveAccount).toHaveBeenCalledWith('Imported');
|
expect(hookMocks.setActiveAccount).toHaveBeenCalledWith('Imported');
|
||||||
@@ -350,6 +352,74 @@ describe('AccountSettings', () => {
|
|||||||
expect(getLocationText()).toBe('/subs/settings#account-settings');
|
expect(getLocationText()).toBe('/subs/settings#account-settings');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('preserves explicit HTTP routers when importing an account backup', async () => {
|
||||||
|
fileReaderState.result = JSON.stringify({
|
||||||
|
account: {
|
||||||
|
name: 'Imported',
|
||||||
|
author: { address: '0x999' },
|
||||||
|
pkcOptions: {
|
||||||
|
httpRoutersOptions: ['https://router.custom.example'],
|
||||||
|
ipfsGatewayUrls: ['https://gateway.custom.example'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
hookMocks.importAccount.mockResolvedValue(undefined);
|
||||||
|
hookMocks.setActiveAccount.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
render();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
getButtonByText('import_account_backup').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
const file = new File(['{}'], 'account.json', { type: 'application/json' });
|
||||||
|
await act(async () => {
|
||||||
|
createdInput?.onchange?.({ target: { files: [file] } } as unknown as Event);
|
||||||
|
await Promise.resolve();
|
||||||
|
});
|
||||||
|
await flushMicrotasks();
|
||||||
|
|
||||||
|
const importedPayload = JSON.parse(hookMocks.importAccount.mock.calls[0][0]);
|
||||||
|
expect(importedPayload.account.pkcOptions).toEqual({
|
||||||
|
httpRoutersOptions: ['https://router.custom.example'],
|
||||||
|
ipfsGatewayUrls: ['https://gateway.custom.example'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves an explicit empty HTTP routers list when importing an account backup', async () => {
|
||||||
|
fileReaderState.result = JSON.stringify({
|
||||||
|
account: {
|
||||||
|
name: 'Imported',
|
||||||
|
author: { address: '0x999' },
|
||||||
|
pkcOptions: {
|
||||||
|
httpRoutersOptions: [],
|
||||||
|
ipfsGatewayUrls: ['https://gateway.custom.example'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
hookMocks.importAccount.mockResolvedValue(undefined);
|
||||||
|
hookMocks.setActiveAccount.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
render();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
getButtonByText('import_account_backup').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
const file = new File(['{}'], 'account.json', { type: 'application/json' });
|
||||||
|
await act(async () => {
|
||||||
|
createdInput?.onchange?.({ target: { files: [file] } } as unknown as Event);
|
||||||
|
await Promise.resolve();
|
||||||
|
});
|
||||||
|
await flushMicrotasks();
|
||||||
|
|
||||||
|
const importedPayload = JSON.parse(hookMocks.importAccount.mock.calls[0][0]);
|
||||||
|
expect(importedPayload.account.pkcOptions).toEqual({
|
||||||
|
httpRoutersOptions: [],
|
||||||
|
ipfsGatewayUrls: ['https://gateway.custom.example'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('activates the resolved account name when an imported account name already exists', async () => {
|
it('activates the resolved account name when an imported account name already exists', async () => {
|
||||||
hookMocks.useAccounts.mockReturnValue({
|
hookMocks.useAccounts.mockReturnValue({
|
||||||
accounts: [
|
accounts: [
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { deleteAccount, exportAccount, importAccount, setActiveAccount, useAccou
|
|||||||
import styles from './account-settings.module.css';
|
import styles from './account-settings.module.css';
|
||||||
import { Capacitor } from '@capacitor/core';
|
import { Capacitor } from '@capacitor/core';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
import { getLegacyDefaultBrowserHttpRoutersOptions } from '../../../lib/p2p-browser-config';
|
||||||
|
|
||||||
const isAndroid = Capacitor.getPlatform() === 'android';
|
const isAndroid = Capacitor.getPlatform() === 'android';
|
||||||
const IMPORTED_ACCOUNT_ADDRESSES_STORAGE_KEY = 'importedAccountAddresses';
|
const IMPORTED_ACCOUNT_ADDRESSES_STORAGE_KEY = 'importedAccountAddresses';
|
||||||
@@ -64,6 +65,18 @@ const getImportedAccountActiveName = (importedAccountName: string | undefined, a
|
|||||||
return accounts.some((account) => account?.name === importedAccountName) ? `${importedAccountName} 2` : importedAccountName;
|
return accounts.some((account) => account?.name === importedAccountName) ? `${importedAccountName} 2` : importedAccountName;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ImportedAccountProtocolOptions = Record<string, unknown> & {
|
||||||
|
httpRoutersOptions?: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
const toImportedAccountProtocolOptions = (protocolOptions: unknown): ImportedAccountProtocolOptions =>
|
||||||
|
protocolOptions && typeof protocolOptions === 'object' && !Array.isArray(protocolOptions) ? (protocolOptions as ImportedAccountProtocolOptions) : {};
|
||||||
|
|
||||||
|
const hasExplicitHttpRoutersOptions = (protocolOptions: unknown) => {
|
||||||
|
const httpRoutersOptions = toImportedAccountProtocolOptions(protocolOptions).httpRoutersOptions;
|
||||||
|
return Array.isArray(httpRoutersOptions);
|
||||||
|
};
|
||||||
|
|
||||||
// Inner component keyed by account id so state resets when user switches account
|
// Inner component keyed by account id so state resets when user switches account
|
||||||
const AccountSettingsEditor = ({
|
const AccountSettingsEditor = ({
|
||||||
account,
|
account,
|
||||||
@@ -139,7 +152,13 @@ const AccountSettingsEditor = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const accountData = safeParseJSON<{
|
const accountData = safeParseJSON<{
|
||||||
account?: { communities?: Record<string, unknown>; subscriptions?: string[]; author?: { address?: string }; name?: string };
|
account?: {
|
||||||
|
communities?: Record<string, unknown>;
|
||||||
|
subscriptions?: string[];
|
||||||
|
author?: { address?: string };
|
||||||
|
name?: string;
|
||||||
|
pkcOptions?: ImportedAccountProtocolOptions;
|
||||||
|
};
|
||||||
}>(fileContent);
|
}>(fileContent);
|
||||||
if (!accountData) {
|
if (!accountData) {
|
||||||
alert('Invalid JSON in file.');
|
alert('Invalid JSON in file.');
|
||||||
@@ -162,6 +181,13 @@ const AccountSettingsEditor = ({
|
|||||||
accountData.account.subscriptions = uniqueSubscriptions;
|
accountData.account.subscriptions = uniqueSubscriptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (accountData.account && !hasExplicitHttpRoutersOptions(accountData.account.pkcOptions)) {
|
||||||
|
accountData.account.pkcOptions = {
|
||||||
|
...toImportedAccountProtocolOptions(accountData.account.pkcOptions),
|
||||||
|
httpRoutersOptions: getLegacyDefaultBrowserHttpRoutersOptions(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const modifiedAccountJson = JSON.stringify(accountData);
|
const modifiedAccountJson = JSON.stringify(accountData);
|
||||||
const importedAccountActiveName = getImportedAccountActiveName(accountData.account?.name, accounts);
|
const importedAccountActiveName = getImportedAccountActiveName(accountData.account?.name, accounts);
|
||||||
const result = await withErrorHandling(
|
const result = await withErrorHandling(
|
||||||
|
|||||||
@@ -220,6 +220,22 @@ describe('AdvancedSettings', () => {
|
|||||||
expect(nodeRpcInput).toBeTruthy();
|
expect(nodeRpcInput).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps settings upgrade review out of advanced settings', async () => {
|
||||||
|
testState.account = {
|
||||||
|
mediaIpfsGatewayUrl: 'https://media.old.example',
|
||||||
|
pkcOptions: {
|
||||||
|
httpRoutersOptions: ['https://routing.lol', 'https://peers.pleb.bot', 'https://peers.plebpubsub.xyz', 'https://peers.forumindex.com'],
|
||||||
|
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await renderSettings(false);
|
||||||
|
|
||||||
|
expect(container.textContent).not.toContain('advanced_settings_upgrade_available');
|
||||||
|
expect(container.textContent).not.toContain('https://routerofbitsocial.xyz');
|
||||||
|
expect(container.textContent).not.toContain('https://bsotracker.online');
|
||||||
|
});
|
||||||
|
|
||||||
it('saves browser pure p2p settings when the toggle is turned on', async () => {
|
it('saves browser pure p2p settings when the toggle is turned on', async () => {
|
||||||
localStorage.setItem('5chan:pure-p2p-browser-enabled', 'false');
|
localStorage.setItem('5chan:pure-p2p-browser-enabled', 'false');
|
||||||
|
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ const AdvancedSettings = () => {
|
|||||||
|
|
||||||
const ethRpcUrls = getTrimmedLines(ethRpcRef.current?.value);
|
const ethRpcUrls = getTrimmedLines(ethRpcRef.current?.value);
|
||||||
|
|
||||||
const httpRoutersOptions = getTrimmedLines(httpRoutersRef.current?.value);
|
const httpRoutersOptions = httpRoutersRef.current ? getTrimmedLines(httpRoutersRef.current.value) : protocolOptions?.httpRoutersOptions;
|
||||||
|
|
||||||
const pkcRpcClientsOptions = p2pRpcRef.current?.value.trim() ? [p2pRpcRef.current.value.trim()] : undefined;
|
const pkcRpcClientsOptions = p2pRpcRef.current?.value.trim() ? [p2pRpcRef.current.value.trim()] : undefined;
|
||||||
const dataPath = p2pDataPathRef.current?.value.trim() || undefined;
|
const dataPath = p2pDataPathRef.current?.value.trim() || undefined;
|
||||||
|
|||||||
@@ -99,6 +99,24 @@
|
|||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settingsUpgradeNotice {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1;
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
margin: 8px 0 0;
|
||||||
|
padding: 3px 5px;
|
||||||
|
background-color: #E62020;
|
||||||
|
color: white;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 0 1px rgba(0, 0, 0, 0.20);
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
.expandAllSettings span, .expandAllSettings button {
|
.expandAllSettings span, .expandAllSettings button {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -109,11 +127,29 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settingsUpgradeNotice button {
|
||||||
|
appearance: none;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
color: inherit;
|
||||||
|
filter: none;
|
||||||
|
font: inherit;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
.expandAllSettings span:hover, .expandAllSettings button:hover {
|
.expandAllSettings span:hover, .expandAllSettings button:hover {
|
||||||
color: var(--button-desktop-text-color-hover);
|
color: var(--button-desktop-text-color-hover);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settingsUpgradeNotice button:hover {
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.setting {
|
.setting {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useAccount } from '@bitsocial/bitsocial-react-hooks';
|
import { useAccount, usePkcRpcSettings } from '@bitsocial/bitsocial-react-hooks';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import styles from './settings-modal.module.css';
|
import styles from './settings-modal.module.css';
|
||||||
import AccountSettings from './account-settings';
|
import AccountSettings from './account-settings/account-settings';
|
||||||
import CryptoAddressSetting from './crypto-address-setting';
|
import CryptoAddressSetting from './crypto-address-setting/crypto-address-setting';
|
||||||
import CryptoWalletsSetting from './crypto-wallets-setting';
|
import CryptoWalletsSetting from './crypto-wallets-setting/crypto-wallets-setting';
|
||||||
import InterfaceSettings from './interface-settings';
|
import InterfaceSettings from './interface-settings/interface-settings';
|
||||||
import MediaHostingSettings from './media-hosting-settings';
|
import MediaHostingSettings from './media-hosting-settings/media-hosting-settings';
|
||||||
import AdvancedSettings from './advanced-settings';
|
import AdvancedSettings from './advanced-settings/advanced-settings';
|
||||||
import SubscriptionsSetting from './subscriptions-setting';
|
import SubscriptionsSetting from './subscriptions-setting/subscriptions-setting';
|
||||||
import TrustedBoardLinksSetting from './trusted-board-links-setting';
|
import TrustedBoardLinksSetting from './trusted-board-links-setting/trusted-board-links-setting';
|
||||||
import P2PStatsSettings from './p2p-stats-settings';
|
import P2PStatsSettings from './p2p-stats-settings/p2p-stats-settings';
|
||||||
import { P2P_STATS_SECTION_ID, shouldShowP2PSettingsSection } from '../../lib/p2p-runtime';
|
import { P2P_STATS_SECTION_ID, shouldShowP2PSettingsSection } from '../../lib/p2p-runtime';
|
||||||
|
import { getReviewableSettingsUpgrades, getSettingsUpgradeKey, type SettingsUpgradeAccount } from '../../lib/settings-upgrades';
|
||||||
|
import useSettingsUpgradeReviewStore from '../../stores/use-settings-upgrade-review-store';
|
||||||
|
|
||||||
const allSectionIds = [
|
const allSectionIds = [
|
||||||
'interface-settings',
|
'interface-settings',
|
||||||
@@ -32,11 +34,22 @@ const hashToSection = (hash: string, sectionIds = allSectionIds): string | null
|
|||||||
const SettingsModal = () => {
|
const SettingsModal = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
|
const pkcRpc = usePkcRpcSettings();
|
||||||
const { hash: locationHash, pathname } = useLocation();
|
const { hash: locationHash, pathname } = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const hiddenReviewUpgradeKeys = useSettingsUpgradeReviewStore((state) => state.hiddenReviewUpgradeKeys);
|
||||||
|
const reviewUpgradeKeys = useSettingsUpgradeReviewStore((state) => state.reviewUpgradeKeys);
|
||||||
const hash = locationHash.slice(1);
|
const hash = locationHash.slice(1);
|
||||||
const sectionIds = useMemo(() => (shouldShowP2PSettingsSection(account) ? [...allSectionIds, P2P_STATS_SECTION_ID] : allSectionIds), [account]);
|
const sectionIds = useMemo(() => (shouldShowP2PSettingsSection(account) ? [...allSectionIds, P2P_STATS_SECTION_ID] : allSectionIds), [account]);
|
||||||
const hashSection = hashToSection(hash, sectionIds);
|
const hashSection = hashToSection(hash, sectionIds);
|
||||||
|
const settingsUpgradeKeys = useMemo(() => {
|
||||||
|
if (!account || pkcRpc?.state === 'connected') return [];
|
||||||
|
|
||||||
|
const settingsUpgradeAccount = account as SettingsUpgradeAccount;
|
||||||
|
return getReviewableSettingsUpgrades(settingsUpgradeAccount).map((upgrade) => getSettingsUpgradeKey(settingsUpgradeAccount, upgrade));
|
||||||
|
}, [account, pkcRpc?.state]);
|
||||||
|
const visibleSettingsUpgradeKeys = settingsUpgradeKeys.filter((upgradeKey) => !hiddenReviewUpgradeKeys.includes(upgradeKey));
|
||||||
|
const showSettingsUpgradeReview = visibleSettingsUpgradeKeys.length > 0;
|
||||||
|
|
||||||
const closeModal = useCallback(() => {
|
const closeModal = useCallback(() => {
|
||||||
const newPath = pathname.replace(/\/settings$/, '');
|
const newPath = pathname.replace(/\/settings$/, '');
|
||||||
@@ -109,6 +122,10 @@ const SettingsModal = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleReviewSettingsUpgrades = () => {
|
||||||
|
reviewUpgradeKeys(visibleSettingsUpgradeKeys);
|
||||||
|
};
|
||||||
|
|
||||||
const handleKeyDown = (handler: () => void) => (e: React.KeyboardEvent) => {
|
const handleKeyDown = (handler: () => void) => (e: React.KeyboardEvent) => {
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -202,6 +219,16 @@ const SettingsModal = () => {
|
|||||||
{showP2PStatsSettings && <P2PStatsSettings />}
|
{showP2PStatsSettings && <P2PStatsSettings />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{showSettingsUpgradeReview && (
|
||||||
|
<output className={styles.settingsUpgradeNotice} aria-live='polite' data-testid='settings-upgrade-review-banner'>
|
||||||
|
<span>{t('settings_upgrade_review_notice')}</span>
|
||||||
|
{' ['}
|
||||||
|
<button type='button' tabIndex={0} onClick={handleReviewSettingsUpgrades} onKeyDown={handleKeyDown(handleReviewSettingsUpgrades)}>
|
||||||
|
{t('settings_upgrade_review_button')}
|
||||||
|
</button>
|
||||||
|
{']'}
|
||||||
|
</output>
|
||||||
|
)}
|
||||||
</dialog>
|
</dialog>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,232 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { createElement } from 'react';
|
||||||
|
import { createRoot, type Root } from 'react-dom/client';
|
||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||||
|
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
|
||||||
|
|
||||||
|
const testState = vi.hoisted(() => ({
|
||||||
|
account: {
|
||||||
|
id: 'account-1',
|
||||||
|
name: 'Imported',
|
||||||
|
mediaIpfsGatewayUrl: 'https://media.example',
|
||||||
|
pkcOptions: {
|
||||||
|
httpRoutersOptions: ['https://routing.lol', 'https://peers.pleb.bot', 'https://peers.plebpubsub.xyz', 'https://peers.forumindex.com'],
|
||||||
|
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
|
||||||
|
},
|
||||||
|
} as Record<string, any> | undefined,
|
||||||
|
rpcSettings: {
|
||||||
|
state: 'disconnected',
|
||||||
|
} as Record<string, any>,
|
||||||
|
setAccountMock: vi.fn().mockResolvedValue(undefined),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('react-i18next', () => ({
|
||||||
|
useTranslation: () => ({
|
||||||
|
t: (key: string) => key,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('@bitsocial/bitsocial-react-hooks', () => ({
|
||||||
|
setAccount: (account: unknown) => testState.setAccountMock(account),
|
||||||
|
useAccount: () => testState.account,
|
||||||
|
usePkcRpcSettings: () => testState.rpcSettings,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let alertSpy: ReturnType<typeof vi.spyOn>;
|
||||||
|
let consoleLogSpy: ReturnType<typeof vi.spyOn>;
|
||||||
|
let container: HTMLDivElement;
|
||||||
|
const originalLocation = window.location;
|
||||||
|
let reloadMock: ReturnType<typeof vi.fn>;
|
||||||
|
let root: Root;
|
||||||
|
|
||||||
|
const renderModal = async () => {
|
||||||
|
const SettingsUpgradeModal = (await import('../settings-upgrade-modal')).default;
|
||||||
|
await act(async () => {
|
||||||
|
root.render(createElement(SettingsUpgradeModal));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUpgradeCheckbox = (routerUrl: string) => {
|
||||||
|
const label = Array.from(container.querySelectorAll('label')).find((candidate) => candidate.textContent?.includes(routerUrl));
|
||||||
|
return label?.querySelector<HTMLInputElement>('input[type="checkbox"]');
|
||||||
|
};
|
||||||
|
|
||||||
|
const clickButton = async (text: string) => {
|
||||||
|
const button = Array.from(container.querySelectorAll('button')).find((candidate) => candidate.textContent === text);
|
||||||
|
await act(async () => {
|
||||||
|
button?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||||
|
await Promise.resolve();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('SettingsUpgradeModal', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.resetModules();
|
||||||
|
vi.clearAllMocks();
|
||||||
|
localStorage.clear();
|
||||||
|
testState.account = {
|
||||||
|
id: 'account-1',
|
||||||
|
name: 'Imported',
|
||||||
|
mediaIpfsGatewayUrl: 'https://media.example',
|
||||||
|
pkcOptions: {
|
||||||
|
httpRoutersOptions: ['https://routing.lol', 'https://peers.pleb.bot', 'https://peers.plebpubsub.xyz', 'https://peers.forumindex.com'],
|
||||||
|
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
testState.rpcSettings = {
|
||||||
|
state: 'disconnected',
|
||||||
|
};
|
||||||
|
testState.setAccountMock.mockReset().mockResolvedValue(undefined);
|
||||||
|
reloadMock = vi.fn();
|
||||||
|
Object.defineProperty(window, 'location', {
|
||||||
|
configurable: true,
|
||||||
|
value: {
|
||||||
|
...originalLocation,
|
||||||
|
reload: reloadMock,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
alertSpy = vi.spyOn(window, 'alert').mockImplementation(() => undefined);
|
||||||
|
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
||||||
|
container = document.createElement('div');
|
||||||
|
document.body.appendChild(container);
|
||||||
|
root = createRoot(container);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (root) {
|
||||||
|
act(() => root.unmount());
|
||||||
|
}
|
||||||
|
container?.remove();
|
||||||
|
Object.defineProperty(window, 'location', {
|
||||||
|
configurable: true,
|
||||||
|
value: originalLocation,
|
||||||
|
});
|
||||||
|
alertSpy?.mockRestore();
|
||||||
|
consoleLogSpy?.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('opens automatically when an active account has reviewable HTTP tracker upgrades', async () => {
|
||||||
|
await renderModal();
|
||||||
|
|
||||||
|
expect(container.textContent).toContain('advanced_settings_upgrade_available');
|
||||||
|
expect(container.textContent).toContain('advanced_settings_upgrade_tip');
|
||||||
|
expect(container.textContent).toContain('https://routerofbitsocial.xyz');
|
||||||
|
expect(container.textContent).toContain('https://bsotracker.online');
|
||||||
|
expect(container.textContent).not.toContain('settings_upgrade_never_show_again');
|
||||||
|
expect(getUpgradeCheckbox('https://routerofbitsocial.xyz')?.checked).toBe(true);
|
||||||
|
expect(getUpgradeCheckbox('https://bsotracker.online')?.checked).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('applies only selected HTTP tracker upgrades', async () => {
|
||||||
|
await renderModal();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
getUpgradeCheckbox('https://bsotracker.online')?.click();
|
||||||
|
});
|
||||||
|
await clickButton('settings_upgrade_apply_selected');
|
||||||
|
|
||||||
|
expect(testState.setAccountMock).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
mediaIpfsGatewayUrl: 'https://media.example',
|
||||||
|
pkcOptions: expect.objectContaining({
|
||||||
|
httpRoutersOptions: [
|
||||||
|
'https://routing.lol',
|
||||||
|
'https://peers.pleb.bot',
|
||||||
|
'https://peers.plebpubsub.xyz',
|
||||||
|
'https://peers.forumindex.com',
|
||||||
|
'https://routerofbitsocial.xyz',
|
||||||
|
],
|
||||||
|
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(reloadMock).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('remembers when the user keeps current settings', async () => {
|
||||||
|
await renderModal();
|
||||||
|
|
||||||
|
await clickButton('settings_upgrade_keep_current');
|
||||||
|
|
||||||
|
expect(container.querySelector('dialog')).toBeNull();
|
||||||
|
expect(testState.setAccountMock).not.toHaveBeenCalled();
|
||||||
|
expect(localStorage.getItem('5chan:dismissed-settings-upgrades')).toContain('account-1:http-routers:https://routerofbitsocial.xyz|https://bsotracker.online');
|
||||||
|
|
||||||
|
await renderModal();
|
||||||
|
|
||||||
|
expect(container.querySelector('dialog')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reopens dismissed upgrades when the user reviews them from settings', async () => {
|
||||||
|
await renderModal();
|
||||||
|
|
||||||
|
await clickButton('settings_upgrade_keep_current');
|
||||||
|
expect(container.querySelector('dialog')).toBeNull();
|
||||||
|
|
||||||
|
const useSettingsUpgradeReviewStore = (await import('../../../stores/use-settings-upgrade-review-store')).default;
|
||||||
|
await act(async () => {
|
||||||
|
useSettingsUpgradeReviewStore.getState().reviewUpgradeKeys(['account-1:http-routers:https://routerofbitsocial.xyz|https://bsotracker.online']);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container.querySelector('dialog')).not.toBeNull();
|
||||||
|
expect(container.textContent).toContain('https://routerofbitsocial.xyz');
|
||||||
|
expect(localStorage.getItem('5chan:dismissed-settings-upgrades')).not.toContain('account-1:http-routers:https://routerofbitsocial.xyz|https://bsotracker.online');
|
||||||
|
expect(localStorage.getItem('5chan:hidden-settings-upgrade-reviews')).not.toContain('account-1:http-routers:https://routerofbitsocial.xyz|https://bsotracker.online');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the permanent hide action only after the upgrade is reopened from settings review', async () => {
|
||||||
|
const upgradeKey = 'account-1:http-routers:https://routerofbitsocial.xyz|https://bsotracker.online';
|
||||||
|
await renderModal();
|
||||||
|
|
||||||
|
expect(container.textContent).not.toContain('settings_upgrade_never_show_again');
|
||||||
|
|
||||||
|
await clickButton('settings_upgrade_keep_current');
|
||||||
|
const useSettingsUpgradeReviewStore = (await import('../../../stores/use-settings-upgrade-review-store')).default;
|
||||||
|
await act(async () => {
|
||||||
|
useSettingsUpgradeReviewStore.getState().reviewUpgradeKeys([upgradeKey]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container.querySelector('dialog')).not.toBeNull();
|
||||||
|
expect(container.textContent).toContain('settings_upgrade_never_show_again');
|
||||||
|
expect(
|
||||||
|
Array.from(container.querySelectorAll('button')).find((candidate) => candidate.textContent === 'settings_upgrade_never_show_again')?.parentElement?.textContent,
|
||||||
|
).toBe('settings_upgrade_never_show_again');
|
||||||
|
|
||||||
|
await clickButton('settings_upgrade_never_show_again');
|
||||||
|
|
||||||
|
expect(container.querySelector('dialog')).toBeNull();
|
||||||
|
expect(localStorage.getItem('5chan:dismissed-settings-upgrades')).toContain(upgradeKey);
|
||||||
|
expect(localStorage.getItem('5chan:hidden-settings-upgrade-reviews')).toContain(upgradeKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not reopen upgrade prompts hidden from settings review', async () => {
|
||||||
|
localStorage.setItem('5chan:hidden-settings-upgrade-reviews', JSON.stringify(['account-1:http-routers:https://routerofbitsocial.xyz|https://bsotracker.online']));
|
||||||
|
|
||||||
|
await renderModal();
|
||||||
|
|
||||||
|
expect(container.querySelector('dialog')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stays hidden for custom routers and connected RPC settings', async () => {
|
||||||
|
testState.account = {
|
||||||
|
id: 'account-1',
|
||||||
|
pkcOptions: {
|
||||||
|
httpRoutersOptions: ['https://router.custom.example'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await renderModal();
|
||||||
|
expect(container.querySelector('dialog')).toBeNull();
|
||||||
|
|
||||||
|
testState.account = {
|
||||||
|
id: 'account-2',
|
||||||
|
pkcOptions: {
|
||||||
|
httpRoutersOptions: ['https://routing.lol', 'https://peers.pleb.bot', 'https://peers.plebpubsub.xyz', 'https://peers.forumindex.com'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
testState.rpcSettings = { state: 'connected' };
|
||||||
|
await renderModal();
|
||||||
|
expect(container.querySelector('dialog')).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './settings-upgrade-modal';
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
.backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.25);
|
||||||
|
z-index: 1200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backdropButton {
|
||||||
|
all: unset;
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
position: fixed;
|
||||||
|
top: 75px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 420px;
|
||||||
|
max-width: calc(100vw - 20px);
|
||||||
|
max-height: calc(100vh - 30px);
|
||||||
|
overflow-y: auto;
|
||||||
|
margin: 0;
|
||||||
|
padding: 2px;
|
||||||
|
background-color: var(--settings-modal-background-color);
|
||||||
|
border-top: var(--settings-modal-border-top);
|
||||||
|
border-right: var(--settings-modal-border-right);
|
||||||
|
border-bottom: var(--settings-modal-border-bottom);
|
||||||
|
border-left: var(--settings-modal-border-left);
|
||||||
|
color: inherit;
|
||||||
|
font-size: var(--settings-modal-font-size);
|
||||||
|
z-index: 1201;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
position: relative;
|
||||||
|
margin: 5px 0;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: var(--settings-modal-header-border-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h2 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 14px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton {
|
||||||
|
appearance: none;
|
||||||
|
position: absolute;
|
||||||
|
top: -2px;
|
||||||
|
right: 3px;
|
||||||
|
display: block;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
background-image: var(--settings-modal-close-button-background-image);
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
padding: 3px 5px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options {
|
||||||
|
margin: 0;
|
||||||
|
padding: 4px 5px 5px;
|
||||||
|
border: var(--settings-modal-border-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options + .options {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options legend {
|
||||||
|
padding: 0 2px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option {
|
||||||
|
display: block;
|
||||||
|
margin-top: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option input {
|
||||||
|
margin: 0 5px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
margin-top: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions button {
|
||||||
|
margin: 0 3px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.neverShowAgainAction {
|
||||||
|
margin-top: 14px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.neverShowAgainAction button {
|
||||||
|
margin: 0 3px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.dialog {
|
||||||
|
top: 20px;
|
||||||
|
width: calc(100vw - 20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { setAccount, useAccount, usePkcRpcSettings } from '@bitsocial/bitsocial-react-hooks';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
applySelectedSettingsUpgrades,
|
||||||
|
getReviewableSettingsUpgrades,
|
||||||
|
getSelectedSettingsUpgradeOptionCount,
|
||||||
|
getSettingsUpgradeKey,
|
||||||
|
getSettingsUpgradeOptionSelectionKey,
|
||||||
|
isSettingsUpgradeOptionSelected,
|
||||||
|
type ReviewableSettingsUpgrade,
|
||||||
|
type SettingsUpgradeAccount,
|
||||||
|
type SettingsUpgradeSelections,
|
||||||
|
} from '../../lib/settings-upgrades';
|
||||||
|
import useSettingsUpgradeReviewStore from '../../stores/use-settings-upgrade-review-store';
|
||||||
|
import styles from './settings-upgrade-modal.module.css';
|
||||||
|
|
||||||
|
const SettingsUpgradeModalContent = ({
|
||||||
|
account,
|
||||||
|
allowPermanentHide,
|
||||||
|
dismissUpgrades,
|
||||||
|
hideUpgrades,
|
||||||
|
upgradeKeys,
|
||||||
|
upgrades,
|
||||||
|
}: {
|
||||||
|
account: SettingsUpgradeAccount;
|
||||||
|
allowPermanentHide: boolean;
|
||||||
|
dismissUpgrades: (upgradeKeys: string[], persist: boolean) => void;
|
||||||
|
hideUpgrades: (upgradeKeys: string[]) => void;
|
||||||
|
upgradeKeys: string[];
|
||||||
|
upgrades: ReviewableSettingsUpgrade[];
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [upgradeSelections, setUpgradeSelections] = useState<SettingsUpgradeSelections>({});
|
||||||
|
const selectedUpgradeOptionCount = getSelectedSettingsUpgradeOptionCount(upgrades, upgradeSelections);
|
||||||
|
|
||||||
|
const handleSelectionChange = (upgrade: ReviewableSettingsUpgrade, optionId: string, selected: boolean) => {
|
||||||
|
const option = upgrade.options.find((candidate) => candidate.id === optionId);
|
||||||
|
if (!option) return;
|
||||||
|
|
||||||
|
setUpgradeSelections((selections) => ({
|
||||||
|
...selections,
|
||||||
|
[getSettingsUpgradeOptionSelectionKey(upgrade, option)]: selected,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeepCurrent = () => {
|
||||||
|
dismissUpgrades(upgradeKeys, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNeverShowAgain = () => {
|
||||||
|
hideUpgrades(upgradeKeys);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleApplySelected = async () => {
|
||||||
|
if (selectedUpgradeOptionCount === 0) {
|
||||||
|
dismissUpgrades(upgradeKeys, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await setAccount(applySelectedSettingsUpgrades(account, upgrades, upgradeSelections));
|
||||||
|
window.location.reload();
|
||||||
|
} catch (error) {
|
||||||
|
alert(t('settings_upgrade_error_saving'));
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.backdrop}>
|
||||||
|
<button type='button' className={styles.backdropButton} aria-label={t('close')} onClick={() => dismissUpgrades(upgradeKeys, false)} />
|
||||||
|
<dialog open className={styles.dialog} aria-modal='true' aria-labelledby='settings-upgrade-modal-title'>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<h2 id='settings-upgrade-modal-title'>{t('advanced_settings_upgrade_available')}</h2>
|
||||||
|
<button type='button' className={styles.closeButton} aria-label={t('close')} title={t('close')} onClick={() => dismissUpgrades(upgradeKeys, false)} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.body}>
|
||||||
|
<p className={styles.tip}>{t('advanced_settings_upgrade_tip')}</p>
|
||||||
|
{upgrades.map((upgrade) => (
|
||||||
|
<fieldset className={styles.options} key={upgrade.id}>
|
||||||
|
<legend>{t(upgrade.labelKey)}</legend>
|
||||||
|
{upgrade.options.map((option) => (
|
||||||
|
<label className={styles.option} key={option.id}>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
aria-label={t(option.ariaLabelKey, option.ariaLabelValues)}
|
||||||
|
checked={isSettingsUpgradeOptionSelected(upgrade, option, upgradeSelections)}
|
||||||
|
onChange={(event) => handleSelectionChange(upgrade, option.id, event.currentTarget.checked)}
|
||||||
|
/>
|
||||||
|
{option.label}
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</fieldset>
|
||||||
|
))}
|
||||||
|
<div className={styles.actions}>
|
||||||
|
<button type='button' onClick={handleKeepCurrent}>
|
||||||
|
{t('settings_upgrade_keep_current')}
|
||||||
|
</button>
|
||||||
|
<button type='button' onClick={handleApplySelected} disabled={selectedUpgradeOptionCount === 0}>
|
||||||
|
{t('settings_upgrade_apply_selected')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{allowPermanentHide && (
|
||||||
|
<div className={styles.neverShowAgainAction}>
|
||||||
|
<button type='button' onClick={handleNeverShowAgain}>
|
||||||
|
{t('settings_upgrade_never_show_again')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SettingsUpgradeModal = () => {
|
||||||
|
const account = useAccount() as SettingsUpgradeAccount | undefined;
|
||||||
|
const pkcRpc = usePkcRpcSettings();
|
||||||
|
const { dismissUpgradeKeys, hiddenReviewUpgradeKeys, hideReviewUpgradeKeys, persistentDismissedUpgradeKeys, reviewedUpgradeKeys, reviewRequestId } =
|
||||||
|
useSettingsUpgradeReviewStore();
|
||||||
|
const [sessionDismissedUpgradeKeys, setSessionDismissedUpgradeKeys] = useState<{ reviewRequestId: number; upgradeKeys: string[] }>(() => ({
|
||||||
|
reviewRequestId,
|
||||||
|
upgradeKeys: [],
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!account || pkcRpc?.state === 'connected') return null;
|
||||||
|
|
||||||
|
const activeSessionDismissedUpgradeKeys = sessionDismissedUpgradeKeys.reviewRequestId === reviewRequestId ? sessionDismissedUpgradeKeys.upgradeKeys : [];
|
||||||
|
const dismissedUpgradeKeys = new Set([...persistentDismissedUpgradeKeys, ...hiddenReviewUpgradeKeys, ...activeSessionDismissedUpgradeKeys]);
|
||||||
|
const settingsUpgrades = getReviewableSettingsUpgrades(account).filter((upgrade) => !dismissedUpgradeKeys.has(getSettingsUpgradeKey(account, upgrade)));
|
||||||
|
if (settingsUpgrades.length === 0) return null;
|
||||||
|
|
||||||
|
const upgradeKeys = settingsUpgrades.map((upgrade) => getSettingsUpgradeKey(account, upgrade));
|
||||||
|
const reviewedUpgradeKeySet = new Set(reviewedUpgradeKeys);
|
||||||
|
const allowPermanentHide = upgradeKeys.some((upgradeKey) => reviewedUpgradeKeySet.has(upgradeKey));
|
||||||
|
|
||||||
|
const dismissUpgrades = (nextUpgradeKeys: string[], persist: boolean) => {
|
||||||
|
if (persist) {
|
||||||
|
dismissUpgradeKeys(nextUpgradeKeys);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSessionDismissedUpgradeKeys({
|
||||||
|
reviewRequestId,
|
||||||
|
upgradeKeys: [...new Set([...activeSessionDismissedUpgradeKeys, ...nextUpgradeKeys])],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsUpgradeModalContent
|
||||||
|
key={upgradeKeys.join('\n')}
|
||||||
|
account={account}
|
||||||
|
allowPermanentHide={allowPermanentHide}
|
||||||
|
dismissUpgrades={dismissUpgrades}
|
||||||
|
hideUpgrades={hideReviewUpgradeKeys}
|
||||||
|
upgradeKeys={upgradeKeys}
|
||||||
|
upgrades={settingsUpgrades}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SettingsUpgradeModal;
|
||||||
@@ -133,6 +133,28 @@ describe('useBrowserPureP2PAccountUpgrade', () => {
|
|||||||
expect(reloadMock).toHaveBeenCalledOnce();
|
expect(reloadMock).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('upgrades imported accounts without protocol options to a reviewable router baseline', async () => {
|
||||||
|
testState.account = {
|
||||||
|
id: 'account-1',
|
||||||
|
name: 'Imported',
|
||||||
|
};
|
||||||
|
testState.accounts = [testState.account];
|
||||||
|
|
||||||
|
await renderHook();
|
||||||
|
|
||||||
|
expect(testState.setAccountMock).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: 'account-1',
|
||||||
|
pkcOptions: expect.objectContaining({
|
||||||
|
httpRoutersOptions: ['https://routing.lol', 'https://peers.pleb.bot', 'https://peers.plebpubsub.xyz', 'https://peers.forumindex.com'],
|
||||||
|
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
|
||||||
|
pkcRpcClientsOptions: undefined,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(reloadMock).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
|
||||||
it('does not upgrade browser full-node accounts', async () => {
|
it('does not upgrade browser full-node accounts', async () => {
|
||||||
testState.account = {
|
testState.account = {
|
||||||
id: 'account-1',
|
id: 'account-1',
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ import { DEFAULT_HTTP_ROUTER_URLS } from '@bitsocial/bitsocial-react-hooks/dist/
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
addBrowserHttpRoutersOptions,
|
||||||
configureP2PBrowserPkcOptions,
|
configureP2PBrowserPkcOptions,
|
||||||
|
getBrowserHttpRoutersSettingsUpgrade,
|
||||||
getBrowserGatewayPkcOptions,
|
getBrowserGatewayPkcOptions,
|
||||||
|
getLegacyDefaultBrowserHttpRoutersOptions,
|
||||||
getPureP2PBrowserPreference,
|
getPureP2PBrowserPreference,
|
||||||
PURE_P2P_BROWSER_SETTING_KEY,
|
PURE_P2P_BROWSER_SETTING_KEY,
|
||||||
setPureP2PBrowserPreference,
|
setPureP2PBrowserPreference,
|
||||||
@@ -19,6 +22,8 @@ const createStorage = (values: Record<string, string | undefined> = {}) => ({
|
|||||||
|
|
||||||
describe('p2p-browser-config', () => {
|
describe('p2p-browser-config', () => {
|
||||||
const defaultHttpRouters = DEFAULT_HTTP_ROUTER_URLS;
|
const defaultHttpRouters = DEFAULT_HTTP_ROUTER_URLS;
|
||||||
|
const legacyGatewayHttpRouters = ['https://routing.lol', 'https://peers.pleb.bot', 'https://peers.plebpubsub.xyz', 'https://peers.forumindex.com'];
|
||||||
|
const legacyPureP2PHttpRouters = ['https://peers.plebpubsub.xyz', 'https://routing.lol', 'https://peers.pleb.bot'];
|
||||||
|
|
||||||
it('configures browser PKC options for pure p2p mode by default', () => {
|
it('configures browser PKC options for pure p2p mode by default', () => {
|
||||||
const chainProviders = {
|
const chainProviders = {
|
||||||
@@ -117,4 +122,41 @@ describe('p2p-browser-config', () => {
|
|||||||
expect(getPureP2PBrowserPreference(targetWindow)).toBe(true);
|
expect(getPureP2PBrowserPreference(targetWindow)).toBe(true);
|
||||||
expect(shouldUsePureP2PBrowser(targetWindow)).toBe(true);
|
expect(shouldUsePureP2PBrowser(targetWindow)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('describes a reviewable upgrade for legacy shipped router defaults', () => {
|
||||||
|
expect(getLegacyDefaultBrowserHttpRoutersOptions()).toEqual(legacyGatewayHttpRouters);
|
||||||
|
expect(getBrowserHttpRoutersSettingsUpgrade(legacyGatewayHttpRouters)).toEqual({
|
||||||
|
currentHttpRoutersOptions: legacyGatewayHttpRouters,
|
||||||
|
missingDefaultHttpRoutersOptions: ['https://routerofbitsocial.xyz', 'https://bsotracker.online'],
|
||||||
|
upgradedHttpRoutersOptions: [...legacyGatewayHttpRouters, 'https://routerofbitsocial.xyz', 'https://bsotracker.online'],
|
||||||
|
});
|
||||||
|
expect(getBrowserHttpRoutersSettingsUpgrade(legacyPureP2PHttpRouters)).toEqual({
|
||||||
|
currentHttpRoutersOptions: legacyPureP2PHttpRouters,
|
||||||
|
missingDefaultHttpRoutersOptions: ['https://peers.forumindex.com', 'https://routerofbitsocial.xyz', 'https://bsotracker.online'],
|
||||||
|
upgradedHttpRoutersOptions: [...legacyPureP2PHttpRouters, 'https://peers.forumindex.com', 'https://routerofbitsocial.xyz', 'https://bsotracker.online'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps partially applied shipped router defaults reviewable', () => {
|
||||||
|
const partiallyUpgradedRouters = [...legacyGatewayHttpRouters, 'https://routerofbitsocial.xyz'];
|
||||||
|
|
||||||
|
expect(getBrowserHttpRoutersSettingsUpgrade(partiallyUpgradedRouters)).toEqual({
|
||||||
|
currentHttpRoutersOptions: partiallyUpgradedRouters,
|
||||||
|
missingDefaultHttpRoutersOptions: ['https://bsotracker.online'],
|
||||||
|
upgradedHttpRoutersOptions: [...partiallyUpgradedRouters, 'https://bsotracker.online'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not offer default upgrades for custom router lists', () => {
|
||||||
|
const customRouters = ['https://router.custom.example', 'https://peers.pleb.bot'];
|
||||||
|
|
||||||
|
expect(getBrowserHttpRoutersSettingsUpgrade(customRouters)).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds selected browser routers without changing existing order', () => {
|
||||||
|
expect(addBrowserHttpRoutersOptions(legacyGatewayHttpRouters, ['https://bsotracker.online', 'https://routing.lol'])).toEqual([
|
||||||
|
...legacyGatewayHttpRouters,
|
||||||
|
'https://bsotracker.online',
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { DEFAULT_HTTP_ROUTER_URLS } from '@bitsocial/bitsocial-react-hooks/dist/stores/accounts/account-generator.js';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import {
|
import {
|
||||||
getBrowserGatewayAccountOptions,
|
getBrowserGatewayAccountOptions,
|
||||||
@@ -7,6 +8,7 @@ import {
|
|||||||
shouldShowP2PSettingsSection,
|
shouldShowP2PSettingsSection,
|
||||||
shouldUpgradeBrowserPureP2PAccount,
|
shouldUpgradeBrowserPureP2PAccount,
|
||||||
} from '../p2p-runtime';
|
} from '../p2p-runtime';
|
||||||
|
import { getLegacyDefaultBrowserHttpRoutersOptions } from '../p2p-browser-config';
|
||||||
|
|
||||||
const browserWindow = {
|
const browserWindow = {
|
||||||
electronApi: undefined,
|
electronApi: undefined,
|
||||||
@@ -94,13 +96,27 @@ describe('p2p-runtime', () => {
|
|||||||
|
|
||||||
it('upgrades only stale gateway browser accounts when pure p2p is enabled', () => {
|
it('upgrades only stale gateway browser accounts when pure p2p is enabled', () => {
|
||||||
const gatewayAccount = { pkcOptions: { ipfsGatewayUrls: ['https://gateway.example'] } };
|
const gatewayAccount = { pkcOptions: { ipfsGatewayUrls: ['https://gateway.example'] } };
|
||||||
const browserAccount = { pkcOptions: { libp2pJsClientsOptions: [{ key: 'libp2pjs' }] } };
|
const browserAccount = { pkcOptions: { httpRoutersOptions: DEFAULT_HTTP_ROUTER_URLS, libp2pJsClientsOptions: [{ key: 'libp2pjs' }] } };
|
||||||
|
const browserAccountWithLegacyDefaultRouters = {
|
||||||
|
pkcOptions: {
|
||||||
|
httpRoutersOptions: ['https://routing.lol', 'https://peers.pleb.bot', 'https://peers.plebpubsub.xyz', 'https://peers.forumindex.com'],
|
||||||
|
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const browserAccountWithCustomRouters = {
|
||||||
|
pkcOptions: {
|
||||||
|
httpRoutersOptions: ['https://router.custom.example'],
|
||||||
|
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
|
||||||
|
},
|
||||||
|
};
|
||||||
const mixedBrowserAccount = { pkcOptions: { libp2pJsClientsOptions: [{ key: 'libp2pjs' }], pubsubKuboRpcClientsOptions: ['https://pubsub.example/api/v0'] } };
|
const mixedBrowserAccount = { pkcOptions: { libp2pJsClientsOptions: [{ key: 'libp2pjs' }], pubsubKuboRpcClientsOptions: ['https://pubsub.example/api/v0'] } };
|
||||||
const fullNodeAccount = { pkcOptions: { pkcRpcClientsOptions: ['ws://node.example'] } };
|
const fullNodeAccount = { pkcOptions: { pkcRpcClientsOptions: ['ws://node.example'] } };
|
||||||
|
|
||||||
expect(shouldUpgradeBrowserPureP2PAccount(gatewayAccount, browserWindow)).toBe(true);
|
expect(shouldUpgradeBrowserPureP2PAccount(gatewayAccount, browserWindow)).toBe(true);
|
||||||
expect(shouldUpgradeBrowserPureP2PAccount(gatewayAccount, browserWindowWithEnabledPureP2P)).toBe(true);
|
expect(shouldUpgradeBrowserPureP2PAccount(gatewayAccount, browserWindowWithEnabledPureP2P)).toBe(true);
|
||||||
expect(shouldUpgradeBrowserPureP2PAccount(browserAccount, browserWindow)).toBe(false);
|
expect(shouldUpgradeBrowserPureP2PAccount(browserAccount, browserWindow)).toBe(false);
|
||||||
|
expect(shouldUpgradeBrowserPureP2PAccount(browserAccountWithLegacyDefaultRouters, browserWindow)).toBe(false);
|
||||||
|
expect(shouldUpgradeBrowserPureP2PAccount(browserAccountWithCustomRouters, browserWindow)).toBe(false);
|
||||||
expect(shouldUpgradeBrowserPureP2PAccount(mixedBrowserAccount, browserWindow)).toBe(true);
|
expect(shouldUpgradeBrowserPureP2PAccount(mixedBrowserAccount, browserWindow)).toBe(true);
|
||||||
expect(shouldUpgradeBrowserPureP2PAccount(fullNodeAccount, browserWindow)).toBe(false);
|
expect(shouldUpgradeBrowserPureP2PAccount(fullNodeAccount, browserWindow)).toBe(false);
|
||||||
expect(shouldUpgradeBrowserPureP2PAccount(gatewayAccount, browserWindowWithDisabledPureP2P)).toBe(false);
|
expect(shouldUpgradeBrowserPureP2PAccount(gatewayAccount, browserWindowWithDisabledPureP2P)).toBe(false);
|
||||||
@@ -117,6 +133,7 @@ describe('p2p-runtime', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
expect(getBrowserPureP2PAccountOptions(account)).toMatchObject({
|
expect(getBrowserPureP2PAccountOptions(account)).toMatchObject({
|
||||||
|
httpRoutersOptions: ['https://custom-router.example'],
|
||||||
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
|
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
|
||||||
ipfsGatewayUrls: undefined,
|
ipfsGatewayUrls: undefined,
|
||||||
pkcRpcClientsOptions: undefined,
|
pkcRpcClientsOptions: undefined,
|
||||||
@@ -130,4 +147,14 @@ describe('p2p-runtime', () => {
|
|||||||
pubsubKuboRpcClientsOptions: ['https://pubsubprovider.xyz/api/v0', 'https://plebpubsub.xyz/api/v0', 'https://rannithepleb.com/api/v0'],
|
pubsubKuboRpcClientsOptions: ['https://pubsubprovider.xyz/api/v0', 'https://plebpubsub.xyz/api/v0', 'https://rannithepleb.com/api/v0'],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('uses the legacy router baseline when upgrading imported accounts without saved protocol options', () => {
|
||||||
|
expect(getBrowserPureP2PAccountOptions({ id: 'imported-account' })).toMatchObject({
|
||||||
|
httpRoutersOptions: getLegacyDefaultBrowserHttpRoutersOptions(),
|
||||||
|
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
|
||||||
|
ipfsGatewayUrls: undefined,
|
||||||
|
pkcRpcClientsOptions: undefined,
|
||||||
|
pubsubKuboRpcClientsOptions: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import {
|
||||||
|
applySelectedSettingsUpgrades,
|
||||||
|
getReviewableSettingsUpgrades,
|
||||||
|
getSelectedSettingsUpgradeOptionCount,
|
||||||
|
getSettingsUpgradeKey,
|
||||||
|
getSettingsUpgradeOptionSelectionKey,
|
||||||
|
type ReviewableSettingsUpgrade,
|
||||||
|
type SettingsUpgradeAccount,
|
||||||
|
} from '../settings-upgrades';
|
||||||
|
|
||||||
|
describe('settings-upgrades', () => {
|
||||||
|
it('describes HTTP tracker upgrades through the generic settings upgrade shape', () => {
|
||||||
|
const account: SettingsUpgradeAccount = {
|
||||||
|
id: 'account-1',
|
||||||
|
pkcOptions: {
|
||||||
|
httpRoutersOptions: ['https://routing.lol', 'https://peers.pleb.bot', 'https://peers.plebpubsub.xyz', 'https://peers.forumindex.com'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getReviewableSettingsUpgrades(account)).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: 'http-routers',
|
||||||
|
labelKey: 'advanced_settings_upgrade_http_routers',
|
||||||
|
options: [
|
||||||
|
expect.objectContaining({ id: 'https://routerofbitsocial.xyz', label: 'https://routerofbitsocial.xyz' }),
|
||||||
|
expect.objectContaining({ id: 'https://bsotracker.online', label: 'https://bsotracker.online' }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('applies any selected setting upgrade that follows the shared contract', () => {
|
||||||
|
const account: SettingsUpgradeAccount = { id: 'account-1', existing: true };
|
||||||
|
const firstUpgrade: ReviewableSettingsUpgrade = {
|
||||||
|
id: 'first-setting',
|
||||||
|
labelKey: 'first_setting',
|
||||||
|
options: [
|
||||||
|
{ id: 'alpha', label: 'Alpha', ariaLabelKey: 'alpha' },
|
||||||
|
{ id: 'beta', label: 'Beta', ariaLabelKey: 'beta' },
|
||||||
|
],
|
||||||
|
applySelectedOptions: (nextAccount, selectedOptionIds) => ({
|
||||||
|
...nextAccount,
|
||||||
|
firstSetting: selectedOptionIds,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const secondUpgrade: ReviewableSettingsUpgrade = {
|
||||||
|
id: 'second-setting',
|
||||||
|
labelKey: 'second_setting',
|
||||||
|
options: [{ id: 'gamma', label: 'Gamma', ariaLabelKey: 'gamma' }],
|
||||||
|
applySelectedOptions: (nextAccount, selectedOptionIds) => ({
|
||||||
|
...nextAccount,
|
||||||
|
secondSetting: selectedOptionIds,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const selections = {
|
||||||
|
[getSettingsUpgradeOptionSelectionKey(firstUpgrade, firstUpgrade.options[0])]: true,
|
||||||
|
[getSettingsUpgradeOptionSelectionKey(firstUpgrade, firstUpgrade.options[1])]: false,
|
||||||
|
[getSettingsUpgradeOptionSelectionKey(secondUpgrade, secondUpgrade.options[0])]: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getSelectedSettingsUpgradeOptionCount([firstUpgrade, secondUpgrade], selections)).toBe(2);
|
||||||
|
expect(applySelectedSettingsUpgrades(account, [firstUpgrade, secondUpgrade], selections)).toEqual({
|
||||||
|
id: 'account-1',
|
||||||
|
existing: true,
|
||||||
|
firstSetting: ['alpha'],
|
||||||
|
secondSetting: ['gamma'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keys dismissed upgrades by account, upgrade id, and option signature', () => {
|
||||||
|
const account: SettingsUpgradeAccount = { id: 'account-1' };
|
||||||
|
const upgrade: ReviewableSettingsUpgrade = {
|
||||||
|
id: 'future-setting',
|
||||||
|
labelKey: 'future_setting',
|
||||||
|
options: [
|
||||||
|
{ id: 'one', label: 'One', ariaLabelKey: 'one' },
|
||||||
|
{ id: 'two', label: 'Two', ariaLabelKey: 'two' },
|
||||||
|
],
|
||||||
|
applySelectedOptions: (nextAccount) => nextAccount,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getSettingsUpgradeKey(account, upgrade)).toBe('account-1:future-setting:one|two');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -12,6 +12,16 @@ const DEFAULT_HTTP_ROUTER_URLS = [
|
|||||||
'https://bsotracker.online',
|
'https://bsotracker.online',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const LEGACY_DEFAULT_BROWSER_HTTP_ROUTER_URLS = ['https://routing.lol', 'https://peers.pleb.bot', 'https://peers.plebpubsub.xyz', 'https://peers.forumindex.com'];
|
||||||
|
|
||||||
|
const LEGACY_DEFAULT_HTTP_ROUTER_URL_SETS = [['https://peers.plebpubsub.xyz', 'https://routing.lol', 'https://peers.pleb.bot'], LEGACY_DEFAULT_BROWSER_HTTP_ROUTER_URLS];
|
||||||
|
|
||||||
|
export type BrowserHttpRoutersSettingsUpgrade = {
|
||||||
|
currentHttpRoutersOptions: string[];
|
||||||
|
missingDefaultHttpRoutersOptions: string[];
|
||||||
|
upgradedHttpRoutersOptions: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export const P2P_BROWSER_PKC_OPTIONS = {
|
export const P2P_BROWSER_PKC_OPTIONS = {
|
||||||
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
|
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
|
||||||
ipfsGatewayUrls: undefined,
|
ipfsGatewayUrls: undefined,
|
||||||
@@ -40,6 +50,54 @@ type P2PBrowserConfigWindow = {
|
|||||||
|
|
||||||
const cloneArray = <T>(value: T[] | undefined) => (value ? [...value] : undefined);
|
const cloneArray = <T>(value: T[] | undefined) => (value ? [...value] : undefined);
|
||||||
|
|
||||||
|
const getUniqueTrimmedUrls = (urls: string[] | undefined) =>
|
||||||
|
urls?.reduce<string[]>((uniqueUrls, url) => {
|
||||||
|
const trimmedUrl = url.trim();
|
||||||
|
if (trimmedUrl && !uniqueUrls.includes(trimmedUrl)) uniqueUrls.push(trimmedUrl);
|
||||||
|
return uniqueUrls;
|
||||||
|
}, []) ?? [];
|
||||||
|
|
||||||
|
const hasSameUrlSet = (urls: string[], comparisonUrls: string[]) => {
|
||||||
|
if (urls.length !== comparisonUrls.length) return false;
|
||||||
|
return urls.every((url) => comparisonUrls.includes(url));
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasUrlSet = (urls: string[], comparisonUrls: string[]) => comparisonUrls.every((url) => urls.includes(url));
|
||||||
|
|
||||||
|
const isKnownDefaultHttpRoutersOptions = (httpRoutersOptions: string[]) => {
|
||||||
|
if (hasSameUrlSet(httpRoutersOptions, DEFAULT_HTTP_ROUTER_URLS)) return true;
|
||||||
|
if (LEGACY_DEFAULT_HTTP_ROUTER_URL_SETS.some((legacyRouters) => hasSameUrlSet(httpRoutersOptions, legacyRouters))) return true;
|
||||||
|
return (
|
||||||
|
httpRoutersOptions.every((routerUrl) => DEFAULT_HTTP_ROUTER_URLS.includes(routerUrl)) &&
|
||||||
|
LEGACY_DEFAULT_HTTP_ROUTER_URL_SETS.some((legacyRouters) => hasUrlSet(httpRoutersOptions, legacyRouters))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getBrowserHttpRoutersSettingsUpgrade = (httpRoutersOptions: string[] | undefined): BrowserHttpRoutersSettingsUpgrade | undefined => {
|
||||||
|
const httpRouters = getUniqueTrimmedUrls(httpRoutersOptions);
|
||||||
|
if (httpRouters.length === 0 || !isKnownDefaultHttpRoutersOptions(httpRouters)) return undefined;
|
||||||
|
|
||||||
|
const missingDefaultHttpRoutersOptions = DEFAULT_HTTP_ROUTER_URLS.filter((routerUrl) => !httpRouters.includes(routerUrl));
|
||||||
|
if (missingDefaultHttpRoutersOptions.length === 0) return undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentHttpRoutersOptions: httpRouters,
|
||||||
|
missingDefaultHttpRoutersOptions,
|
||||||
|
upgradedHttpRoutersOptions: [...httpRouters, ...missingDefaultHttpRoutersOptions],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addBrowserHttpRoutersOptions = (httpRoutersOptions: string[] | undefined, routerUrls: string[]) => {
|
||||||
|
const normalizedRouters = getUniqueTrimmedUrls(httpRoutersOptions);
|
||||||
|
return routerUrls.reduce<string[]>((routers, routerUrl) => {
|
||||||
|
const trimmedRouterUrl = routerUrl.trim();
|
||||||
|
if (trimmedRouterUrl && !routers.includes(trimmedRouterUrl)) routers.push(trimmedRouterUrl);
|
||||||
|
return routers;
|
||||||
|
}, normalizedRouters);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getLegacyDefaultBrowserHttpRoutersOptions = () => [...LEGACY_DEFAULT_BROWSER_HTTP_ROUTER_URLS];
|
||||||
|
|
||||||
export const getBrowserPureP2PPkcOptions = () => ({
|
export const getBrowserPureP2PPkcOptions = () => ({
|
||||||
...P2P_BROWSER_PKC_OPTIONS,
|
...P2P_BROWSER_PKC_OPTIONS,
|
||||||
libp2pJsClientsOptions: P2P_BROWSER_PKC_OPTIONS.libp2pJsClientsOptions.map((options) => ({ ...options })),
|
libp2pJsClientsOptions: P2P_BROWSER_PKC_OPTIONS.libp2pJsClientsOptions.map((options) => ({ ...options })),
|
||||||
|
|||||||
+19
-6
@@ -1,4 +1,11 @@
|
|||||||
import { canUsePureP2PBrowser, getBrowserGatewayPkcOptions, getBrowserPureP2PPkcOptions, isElectronRuntime, shouldUsePureP2PBrowser } from './p2p-browser-config';
|
import {
|
||||||
|
canUsePureP2PBrowser,
|
||||||
|
getBrowserGatewayPkcOptions,
|
||||||
|
getBrowserPureP2PPkcOptions,
|
||||||
|
getLegacyDefaultBrowserHttpRoutersOptions,
|
||||||
|
isElectronRuntime,
|
||||||
|
shouldUsePureP2PBrowser,
|
||||||
|
} from './p2p-browser-config';
|
||||||
|
|
||||||
export const P2P_STATS_SECTION_ID = 'p2p-stats-settings';
|
export const P2P_STATS_SECTION_ID = 'p2p-stats-settings';
|
||||||
|
|
||||||
@@ -64,11 +71,17 @@ export const isBrowserPureP2PEnabled = (account?: unknown, targetWindow: Window
|
|||||||
return shouldUsePureP2PBrowser(targetWindow);
|
return shouldUsePureP2PBrowser(targetWindow);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getBrowserPureP2PAccountOptions = (account?: unknown) => ({
|
export const getBrowserPureP2PAccountOptions = (account?: unknown) => {
|
||||||
...toAccountShape(account)?.pkcOptions,
|
const protocolOptions = toAccountShape(account)?.pkcOptions;
|
||||||
...getBrowserPureP2PPkcOptions(),
|
const pureP2POptions = getBrowserPureP2PPkcOptions();
|
||||||
pkcRpcClientsOptions: undefined,
|
|
||||||
});
|
return {
|
||||||
|
...protocolOptions,
|
||||||
|
...pureP2POptions,
|
||||||
|
httpRoutersOptions: hasArrayItems(protocolOptions?.httpRoutersOptions) ? protocolOptions?.httpRoutersOptions : getLegacyDefaultBrowserHttpRoutersOptions(),
|
||||||
|
pkcRpcClientsOptions: undefined,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const shouldUpgradeBrowserPureP2PAccount = (account?: unknown, targetWindow: Window = window) => {
|
export const shouldUpgradeBrowserPureP2PAccount = (account?: unknown, targetWindow: Window = window) => {
|
||||||
if (!account || typeof account !== 'object' || !canConfigureBrowserPureP2P(targetWindow) || !isBrowserPureP2PEnabled(account, targetWindow)) return false;
|
if (!account || typeof account !== 'object' || !canConfigureBrowserPureP2P(targetWindow) || !isBrowserPureP2PEnabled(account, targetWindow)) return false;
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import { addBrowserHttpRoutersOptions, getBrowserHttpRoutersSettingsUpgrade } from './p2p-browser-config';
|
||||||
|
|
||||||
|
type AccountProtocolOptions = {
|
||||||
|
httpRoutersOptions?: string[];
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SettingsUpgradeAccount = Record<string, unknown> & {
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
pkcOptions?: AccountProtocolOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ReviewableSettingsUpgradeOption = {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
ariaLabelKey: string;
|
||||||
|
ariaLabelValues?: Record<string, string>;
|
||||||
|
selectedByDefault?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ReviewableSettingsUpgrade = {
|
||||||
|
id: string;
|
||||||
|
labelKey: string;
|
||||||
|
options: ReviewableSettingsUpgradeOption[];
|
||||||
|
applySelectedOptions: (account: SettingsUpgradeAccount, selectedOptionIds: string[]) => SettingsUpgradeAccount;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SettingsUpgradeDetector = (account: SettingsUpgradeAccount) => ReviewableSettingsUpgrade | undefined;
|
||||||
|
|
||||||
|
export type SettingsUpgradeSelections = Record<string, boolean>;
|
||||||
|
|
||||||
|
const getHttpRoutersSettingsUpgrade: SettingsUpgradeDetector = (account) => {
|
||||||
|
const httpRoutersUpgrade = getBrowserHttpRoutersSettingsUpgrade(account.pkcOptions?.httpRoutersOptions);
|
||||||
|
if (!httpRoutersUpgrade) return undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: 'http-routers',
|
||||||
|
labelKey: 'advanced_settings_upgrade_http_routers',
|
||||||
|
options: httpRoutersUpgrade.missingDefaultHttpRoutersOptions.map((routerUrl) => ({
|
||||||
|
id: routerUrl,
|
||||||
|
label: routerUrl,
|
||||||
|
ariaLabelKey: 'advanced_settings_upgrade_http_router_aria',
|
||||||
|
ariaLabelValues: { router: routerUrl },
|
||||||
|
selectedByDefault: true,
|
||||||
|
})),
|
||||||
|
applySelectedOptions: (nextAccount, selectedRouters) => ({
|
||||||
|
...nextAccount,
|
||||||
|
pkcOptions: {
|
||||||
|
...nextAccount.pkcOptions,
|
||||||
|
httpRoutersOptions: addBrowserHttpRoutersOptions(nextAccount.pkcOptions?.httpRoutersOptions, selectedRouters),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const SETTINGS_UPGRADE_DETECTORS: SettingsUpgradeDetector[] = [getHttpRoutersSettingsUpgrade];
|
||||||
|
|
||||||
|
export const getReviewableSettingsUpgrades = (account: SettingsUpgradeAccount) =>
|
||||||
|
SETTINGS_UPGRADE_DETECTORS.reduce<ReviewableSettingsUpgrade[]>((upgrades, getUpgrade) => {
|
||||||
|
const upgrade = getUpgrade(account);
|
||||||
|
if (upgrade) upgrades.push(upgrade);
|
||||||
|
return upgrades;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
export const getSettingsUpgradeKey = (account: SettingsUpgradeAccount, upgrade: ReviewableSettingsUpgrade) =>
|
||||||
|
`${account.id ?? account.name ?? 'unknown'}:${upgrade.id}:${upgrade.options.map((option) => option.id).join('|')}`;
|
||||||
|
|
||||||
|
export const getSettingsUpgradeOptionSelectionKey = (upgrade: ReviewableSettingsUpgrade, option: ReviewableSettingsUpgradeOption) => `${upgrade.id}:${option.id}`;
|
||||||
|
|
||||||
|
export const isSettingsUpgradeOptionSelected = (upgrade: ReviewableSettingsUpgrade, option: ReviewableSettingsUpgradeOption, selections: SettingsUpgradeSelections) =>
|
||||||
|
selections[getSettingsUpgradeOptionSelectionKey(upgrade, option)] ?? option.selectedByDefault ?? true;
|
||||||
|
|
||||||
|
export const getSelectedSettingsUpgradeOptionIds = (upgrade: ReviewableSettingsUpgrade, selections: SettingsUpgradeSelections) =>
|
||||||
|
upgrade.options.filter((option) => isSettingsUpgradeOptionSelected(upgrade, option, selections)).map((option) => option.id);
|
||||||
|
|
||||||
|
export const getSelectedSettingsUpgradeOptionCount = (upgrades: ReviewableSettingsUpgrade[], selections: SettingsUpgradeSelections) =>
|
||||||
|
upgrades.reduce((selectedCount, upgrade) => selectedCount + getSelectedSettingsUpgradeOptionIds(upgrade, selections).length, 0);
|
||||||
|
|
||||||
|
export const applySelectedSettingsUpgrades = (account: SettingsUpgradeAccount, upgrades: ReviewableSettingsUpgrade[], selections: SettingsUpgradeSelections) =>
|
||||||
|
upgrades.reduce<SettingsUpgradeAccount>((nextAccount, upgrade) => {
|
||||||
|
const selectedOptionIds = getSelectedSettingsUpgradeOptionIds(upgrade, selections);
|
||||||
|
return selectedOptionIds.length > 0 ? upgrade.applySelectedOptions(nextAccount, selectedOptionIds) : nextAccount;
|
||||||
|
}, account);
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
export const DISMISSED_SETTINGS_UPGRADES_KEY = '5chan:dismissed-settings-upgrades';
|
||||||
|
export const HIDDEN_SETTINGS_UPGRADE_REVIEWS_KEY = '5chan:hidden-settings-upgrade-reviews';
|
||||||
|
|
||||||
|
const uniqueUpgradeKeys = (upgradeKeys: string[]) => [...new Set(upgradeKeys)];
|
||||||
|
|
||||||
|
const readStoredUpgradeKeys = (storageKey: string) => {
|
||||||
|
try {
|
||||||
|
const parsedKeys = JSON.parse(localStorage.getItem(storageKey) ?? '[]');
|
||||||
|
return Array.isArray(parsedKeys) ? parsedKeys.filter((key): key is string => typeof key === 'string') : [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const writeStoredUpgradeKeys = (storageKey: string, upgradeKeys: string[]) => {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(storageKey, JSON.stringify(uniqueUpgradeKeys(upgradeKeys)));
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type SettingsUpgradeReviewState = {
|
||||||
|
hiddenReviewUpgradeKeys: string[];
|
||||||
|
persistentDismissedUpgradeKeys: string[];
|
||||||
|
reviewedUpgradeKeys: string[];
|
||||||
|
reviewRequestId: number;
|
||||||
|
dismissUpgradeKeys: (upgradeKeys: string[]) => void;
|
||||||
|
hideReviewUpgradeKeys: (upgradeKeys: string[]) => void;
|
||||||
|
reviewUpgradeKeys: (upgradeKeys: string[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useSettingsUpgradeReviewStore = create<SettingsUpgradeReviewState>((set, get) => ({
|
||||||
|
hiddenReviewUpgradeKeys: readStoredUpgradeKeys(HIDDEN_SETTINGS_UPGRADE_REVIEWS_KEY),
|
||||||
|
persistentDismissedUpgradeKeys: readStoredUpgradeKeys(DISMISSED_SETTINGS_UPGRADES_KEY),
|
||||||
|
reviewedUpgradeKeys: [],
|
||||||
|
reviewRequestId: 0,
|
||||||
|
dismissUpgradeKeys: (upgradeKeys) => {
|
||||||
|
const persistentDismissedUpgradeKeys = uniqueUpgradeKeys([...get().persistentDismissedUpgradeKeys, ...upgradeKeys]);
|
||||||
|
writeStoredUpgradeKeys(DISMISSED_SETTINGS_UPGRADES_KEY, persistentDismissedUpgradeKeys);
|
||||||
|
set({ persistentDismissedUpgradeKeys });
|
||||||
|
},
|
||||||
|
hideReviewUpgradeKeys: (upgradeKeys) => {
|
||||||
|
const persistentDismissedUpgradeKeys = uniqueUpgradeKeys([...get().persistentDismissedUpgradeKeys, ...upgradeKeys]);
|
||||||
|
const hiddenReviewUpgradeKeys = uniqueUpgradeKeys([...get().hiddenReviewUpgradeKeys, ...upgradeKeys]);
|
||||||
|
writeStoredUpgradeKeys(DISMISSED_SETTINGS_UPGRADES_KEY, persistentDismissedUpgradeKeys);
|
||||||
|
writeStoredUpgradeKeys(HIDDEN_SETTINGS_UPGRADE_REVIEWS_KEY, hiddenReviewUpgradeKeys);
|
||||||
|
set({ hiddenReviewUpgradeKeys, persistentDismissedUpgradeKeys });
|
||||||
|
},
|
||||||
|
reviewUpgradeKeys: (upgradeKeys) => {
|
||||||
|
const upgradeKeysToReview = new Set(upgradeKeys);
|
||||||
|
const persistentDismissedUpgradeKeys = get().persistentDismissedUpgradeKeys.filter((upgradeKey) => !upgradeKeysToReview.has(upgradeKey));
|
||||||
|
const hiddenReviewUpgradeKeys = get().hiddenReviewUpgradeKeys.filter((upgradeKey) => !upgradeKeysToReview.has(upgradeKey));
|
||||||
|
writeStoredUpgradeKeys(DISMISSED_SETTINGS_UPGRADES_KEY, persistentDismissedUpgradeKeys);
|
||||||
|
writeStoredUpgradeKeys(HIDDEN_SETTINGS_UPGRADE_REVIEWS_KEY, hiddenReviewUpgradeKeys);
|
||||||
|
set((state) => ({
|
||||||
|
hiddenReviewUpgradeKeys,
|
||||||
|
persistentDismissedUpgradeKeys,
|
||||||
|
reviewedUpgradeKeys: uniqueUpgradeKeys([...state.reviewedUpgradeKeys, ...upgradeKeys]),
|
||||||
|
reviewRequestId: state.reviewRequestId + 1,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default useSettingsUpgradeReviewStore;
|
||||||
Reference in New Issue
Block a user