From 5bc8eda50b4008139af32c17e85dbade592916f9 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Thu, 2 Jul 2026 22:38:53 +0900 Subject: [PATCH] feat(proofread): editable Find pattern and per-rule enable/disable toggle (#4859) (#4888) * fix(proofread): keep disabled book rules visible in the manager list Co-Authored-By: Claude Opus 4.8 (1M context) * feat(proofread): add per-rule enable/disable toggle in the manager Co-Authored-By: Claude Opus 4.8 (1M context) * feat(proofread): allow editing Find pattern, regex, and case on existing rules Co-Authored-By: Claude Opus 4.8 (1M context) * i18n: add proofread edit and toggle strings Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- .../public/locales/ar/translation.json | 5 +- .../public/locales/bn/translation.json | 5 +- .../public/locales/bo/translation.json | 5 +- .../public/locales/de/translation.json | 5 +- .../public/locales/el/translation.json | 5 +- .../public/locales/es/translation.json | 5 +- .../public/locales/fa/translation.json | 5 +- .../public/locales/fr/translation.json | 5 +- .../public/locales/he/translation.json | 5 +- .../public/locales/hi/translation.json | 5 +- .../public/locales/hu/translation.json | 5 +- .../public/locales/id/translation.json | 5 +- .../public/locales/it/translation.json | 5 +- .../public/locales/ja/translation.json | 5 +- .../public/locales/ko/translation.json | 5 +- .../public/locales/ms/translation.json | 5 +- .../public/locales/nl/translation.json | 5 +- .../public/locales/pl/translation.json | 5 +- .../public/locales/pt-BR/translation.json | 5 +- .../public/locales/pt/translation.json | 5 +- .../public/locales/ro/translation.json | 5 +- .../public/locales/ru/translation.json | 5 +- .../public/locales/si/translation.json | 5 +- .../public/locales/sl/translation.json | 5 +- .../public/locales/sv/translation.json | 5 +- .../public/locales/ta/translation.json | 5 +- .../public/locales/th/translation.json | 5 +- .../public/locales/tr/translation.json | 5 +- .../public/locales/uk/translation.json | 5 +- .../public/locales/uz/translation.json | 5 +- .../public/locales/vi/translation.json | 5 +- .../public/locales/zh-CN/translation.json | 5 +- .../public/locales/zh-TW/translation.json | 5 +- .../components/ProofreadRules.test.tsx | 265 ++++++++++++++++++ .../app/reader/components/ProofreadRules.tsx | 142 ++++++++-- 35 files changed, 515 insertions(+), 57 deletions(-) diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index 028eef45..9659d486 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -1941,5 +1941,8 @@ "Granularity": "الدقة", "Refresh Page": "تحديث الصفحة", "Recently read": "المقروءة مؤخرًا", - "Show recently read": "إظهار المقروءة مؤخرًا" + "Show recently read": "إظهار المقروءة مؤخرًا", + "Find:": "بحث:", + "Enable rule": "تفعيل القاعدة", + "Disable rule": "تعطيل القاعدة" } diff --git a/apps/readest-app/public/locales/bn/translation.json b/apps/readest-app/public/locales/bn/translation.json index e32c0e80..d7ab7e7f 100644 --- a/apps/readest-app/public/locales/bn/translation.json +++ b/apps/readest-app/public/locales/bn/translation.json @@ -1809,5 +1809,8 @@ "Granularity": "সূক্ষ্মতা", "Refresh Page": "পৃষ্ঠা রিফ্রেশ করুন", "Recently read": "সম্প্রতি পড়া", - "Show recently read": "সম্প্রতি পড়া দেখান" + "Show recently read": "সম্প্রতি পড়া দেখান", + "Find:": "খুঁজুন:", + "Enable rule": "নিয়ম সক্রিয় করুন", + "Disable rule": "নিয়ম নিষ্ক্রিয় করুন" } diff --git a/apps/readest-app/public/locales/bo/translation.json b/apps/readest-app/public/locales/bo/translation.json index 4a7afd09..2331f048 100644 --- a/apps/readest-app/public/locales/bo/translation.json +++ b/apps/readest-app/public/locales/bo/translation.json @@ -1770,5 +1770,8 @@ "Granularity": "ཞིབ་ཕྲའི་ཚད", "Refresh Page": "ཤོག་ལྷེ་བསྐྱར་གསོ།", "Recently read": "ཉེ་ཆར་བཀླགས་པ།", - "Show recently read": "ཉེ་ཆར་བཀླགས་པ་སྟོན་པ།" + "Show recently read": "ཉེ་ཆར་བཀླགས་པ་སྟོན་པ།", + "Find:": "འཚོལ་བཤེར།:", + "Enable rule": "སྲིད་སྒྲིག་སྤྱོད་པ།", + "Disable rule": "སྲིད་སྒྲིག་སྒོ་བརྒྱབ།" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index bde2b80f..25bed85a 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -1809,5 +1809,8 @@ "Granularity": "Granularität", "Refresh Page": "Seite aktualisieren", "Recently read": "Zuletzt gelesen", - "Show recently read": "Zuletzt gelesen anzeigen" + "Show recently read": "Zuletzt gelesen anzeigen", + "Find:": "Suchen:", + "Enable rule": "Regel aktivieren", + "Disable rule": "Regel deaktivieren" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index a107fd26..66ebb071 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -1809,5 +1809,8 @@ "Granularity": "Διαβάθμιση", "Refresh Page": "Ανανέωση σελίδας", "Recently read": "Πρόσφατα αναγνωσμένα", - "Show recently read": "Εμφάνιση πρόσφατα αναγνωσμένων" + "Show recently read": "Εμφάνιση πρόσφατα αναγνωσμένων", + "Find:": "Εύρεση:", + "Enable rule": "Ενεργοποίηση κανόνα", + "Disable rule": "Απενεργοποίηση κανόνα" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index 4a21407f..e0edfc95 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -1842,5 +1842,8 @@ "Granularity": "Granularidad", "Refresh Page": "Actualizar página", "Recently read": "Leídos recientemente", - "Show recently read": "Mostrar leídos recientemente" + "Show recently read": "Mostrar leídos recientemente", + "Find:": "Buscar:", + "Enable rule": "Activar regla", + "Disable rule": "Desactivar regla" } diff --git a/apps/readest-app/public/locales/fa/translation.json b/apps/readest-app/public/locales/fa/translation.json index 1a0045cf..0db165c7 100644 --- a/apps/readest-app/public/locales/fa/translation.json +++ b/apps/readest-app/public/locales/fa/translation.json @@ -1809,5 +1809,8 @@ "Granularity": "دانه‌بندی", "Refresh Page": "تازه‌سازی صفحه", "Recently read": "اخیراً خوانده‌شده", - "Show recently read": "نمایش موارد اخیراً خوانده‌شده" + "Show recently read": "نمایش موارد اخیراً خوانده‌شده", + "Find:": "جستجو:", + "Enable rule": "فعال کردن قانون", + "Disable rule": "غیرفعال کردن قانون" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index e9be5ab6..71c7ced9 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -1842,5 +1842,8 @@ "Granularity": "Granularité", "Refresh Page": "Actualiser la page", "Recently read": "Lectures récentes", - "Show recently read": "Afficher les lectures récentes" + "Show recently read": "Afficher les lectures récentes", + "Find:": "Rechercher :", + "Enable rule": "Activer la règle", + "Disable rule": "Désactiver la règle" } diff --git a/apps/readest-app/public/locales/he/translation.json b/apps/readest-app/public/locales/he/translation.json index e8e9ebc2..de87495c 100644 --- a/apps/readest-app/public/locales/he/translation.json +++ b/apps/readest-app/public/locales/he/translation.json @@ -1842,5 +1842,8 @@ "Granularity": "רמת פירוט", "Refresh Page": "רענן דף", "Recently read": "נקראו לאחרונה", - "Show recently read": "הצגת ספרים שנקראו לאחרונה" + "Show recently read": "הצגת ספרים שנקראו לאחרונה", + "Find:": "חיפוש:", + "Enable rule": "הפעל כלל", + "Disable rule": "השבת כלל" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index 586fdffa..7c1c83db 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -1809,5 +1809,8 @@ "Granularity": "सूक्ष्मता", "Refresh Page": "पृष्ठ ताज़ा करें", "Recently read": "हाल ही में पढ़े गए", - "Show recently read": "हाल ही में पढ़े गए दिखाएँ" + "Show recently read": "हाल ही में पढ़े गए दिखाएँ", + "Find:": "खोजें:", + "Enable rule": "नियम सक्षम करें", + "Disable rule": "नियम अक्षम करें" } diff --git a/apps/readest-app/public/locales/hu/translation.json b/apps/readest-app/public/locales/hu/translation.json index 330ffa2e..49886da9 100644 --- a/apps/readest-app/public/locales/hu/translation.json +++ b/apps/readest-app/public/locales/hu/translation.json @@ -1809,5 +1809,8 @@ "Granularity": "Részletesség", "Refresh Page": "Oldal frissítése", "Recently read": "Nemrég olvasott", - "Show recently read": "Nemrég olvasottak megjelenítése" + "Show recently read": "Nemrég olvasottak megjelenítése", + "Find:": "Keresés:", + "Enable rule": "Szabály engedélyezése", + "Disable rule": "Szabály letiltása" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index 9ddcde4a..49f8fb4e 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -1776,5 +1776,8 @@ "Granularity": "Granularitas", "Refresh Page": "Segarkan Halaman", "Recently read": "Baru dibaca", - "Show recently read": "Tampilkan baru dibaca" + "Show recently read": "Tampilkan baru dibaca", + "Find:": "Cari:", + "Enable rule": "Aktifkan aturan", + "Disable rule": "Nonaktifkan aturan" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index 28def618..26b87233 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -1842,5 +1842,8 @@ "Granularity": "Granularità", "Refresh Page": "Aggiorna pagina", "Recently read": "Letti di recente", - "Show recently read": "Mostra letti di recente" + "Show recently read": "Mostra letti di recente", + "Find:": "Trova:", + "Enable rule": "Attiva regola", + "Disable rule": "Disattiva regola" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index 073f35af..8ec7d575 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -1776,5 +1776,8 @@ "Granularity": "粒度", "Refresh Page": "ページを更新", "Recently read": "最近読んだ本", - "Show recently read": "最近読んだ本を表示" + "Show recently read": "最近読んだ本を表示", + "Find:": "検索:", + "Enable rule": "ルールを有効化", + "Disable rule": "ルールを無効化" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index 22a3238b..d72e96b7 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -1776,5 +1776,8 @@ "Granularity": "단위", "Refresh Page": "페이지 새로고침", "Recently read": "최근 읽은 책", - "Show recently read": "최근 읽은 책 표시" + "Show recently read": "최근 읽은 책 표시", + "Find:": "찾기:", + "Enable rule": "규칙 활성화", + "Disable rule": "규칙 비활성화" } diff --git a/apps/readest-app/public/locales/ms/translation.json b/apps/readest-app/public/locales/ms/translation.json index 3922a832..81a64b10 100644 --- a/apps/readest-app/public/locales/ms/translation.json +++ b/apps/readest-app/public/locales/ms/translation.json @@ -1776,5 +1776,8 @@ "Granularity": "Granulariti", "Refresh Page": "Segarkan Halaman", "Recently read": "Baru dibaca", - "Show recently read": "Tunjukkan baru dibaca" + "Show recently read": "Tunjukkan baru dibaca", + "Find:": "Cari:", + "Enable rule": "Dayakan peraturan", + "Disable rule": "Nyahdayakan peraturan" } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index 00cf1d21..89d98ee2 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -1809,5 +1809,8 @@ "Granularity": "Granulariteit", "Refresh Page": "Pagina vernieuwen", "Recently read": "Onlangs gelezen", - "Show recently read": "Onlangs gelezen tonen" + "Show recently read": "Onlangs gelezen tonen", + "Find:": "Zoeken:", + "Enable rule": "Regel inschakelen", + "Disable rule": "Regel uitschakelen" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index 8f74af28..9938a89c 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -1875,5 +1875,8 @@ "Granularity": "Ziarnistość", "Refresh Page": "Odśwież stronę", "Recently read": "Ostatnio czytane", - "Show recently read": "Pokaż ostatnio czytane" + "Show recently read": "Pokaż ostatnio czytane", + "Find:": "Znajdź:", + "Enable rule": "Włącz regułę", + "Disable rule": "Wyłącz regułę" } diff --git a/apps/readest-app/public/locales/pt-BR/translation.json b/apps/readest-app/public/locales/pt-BR/translation.json index bb26cf63..4710c06a 100644 --- a/apps/readest-app/public/locales/pt-BR/translation.json +++ b/apps/readest-app/public/locales/pt-BR/translation.json @@ -1842,5 +1842,8 @@ "Granularity": "Granularidade", "Refresh Page": "Atualizar página", "Recently read": "Lidos recentemente", - "Show recently read": "Mostrar lidos recentemente" + "Show recently read": "Mostrar lidos recentemente", + "Find:": "Localizar:", + "Enable rule": "Ativar regra", + "Disable rule": "Desativar regra" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index 53a0b96a..74e03a54 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -1842,5 +1842,8 @@ "Granularity": "Granularidade", "Refresh Page": "Atualizar Página", "Recently read": "Lidos recentemente", - "Show recently read": "Mostrar lidos recentemente" + "Show recently read": "Mostrar lidos recentemente", + "Find:": "Localizar:", + "Enable rule": "Ativar regra", + "Disable rule": "Desativar regra" } diff --git a/apps/readest-app/public/locales/ro/translation.json b/apps/readest-app/public/locales/ro/translation.json index e742fb98..5f62d401 100644 --- a/apps/readest-app/public/locales/ro/translation.json +++ b/apps/readest-app/public/locales/ro/translation.json @@ -1842,5 +1842,8 @@ "Granularity": "Granularitate", "Refresh Page": "Reîmprospătează pagina", "Recently read": "Citite recent", - "Show recently read": "Afișează citite recent" + "Show recently read": "Afișează citite recent", + "Find:": "Găsește:", + "Enable rule": "Activează regula", + "Disable rule": "Dezactivează regula" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index 436c26e3..26195a02 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -1875,5 +1875,8 @@ "Granularity": "Детализация", "Refresh Page": "Обновить страницу", "Recently read": "Недавно прочитанные", - "Show recently read": "Показывать недавно прочитанные" + "Show recently read": "Показывать недавно прочитанные", + "Find:": "Найти:", + "Enable rule": "Включить правило", + "Disable rule": "Отключить правило" } diff --git a/apps/readest-app/public/locales/si/translation.json b/apps/readest-app/public/locales/si/translation.json index 6e1acb99..5399b885 100644 --- a/apps/readest-app/public/locales/si/translation.json +++ b/apps/readest-app/public/locales/si/translation.json @@ -1809,5 +1809,8 @@ "Granularity": "විස්තර මට්ටම", "Refresh Page": "පිටුව නැවුම් කරන්න", "Recently read": "මෑතකදී කියවූ", - "Show recently read": "මෑතකදී කියවූ ඒවා පෙන්වන්න" + "Show recently read": "මෑතකදී කියවූ ඒවා පෙන්වන්න", + "Find:": "සොයන්න:", + "Enable rule": "නියමය සක්‍රීය කරන්න", + "Disable rule": "නියමය අක්‍රීය කරන්න" } diff --git a/apps/readest-app/public/locales/sl/translation.json b/apps/readest-app/public/locales/sl/translation.json index c34dda5b..52ae83a1 100644 --- a/apps/readest-app/public/locales/sl/translation.json +++ b/apps/readest-app/public/locales/sl/translation.json @@ -1875,5 +1875,8 @@ "Granularity": "Granularnost", "Refresh Page": "Osveži stran", "Recently read": "Nedavno prebrano", - "Show recently read": "Pokaži nedavno prebrano" + "Show recently read": "Pokaži nedavno prebrano", + "Find:": "Najdi:", + "Enable rule": "Omogoči pravilo", + "Disable rule": "Onemogoči pravilo" } diff --git a/apps/readest-app/public/locales/sv/translation.json b/apps/readest-app/public/locales/sv/translation.json index 2bde388d..e4e1a3ae 100644 --- a/apps/readest-app/public/locales/sv/translation.json +++ b/apps/readest-app/public/locales/sv/translation.json @@ -1809,5 +1809,8 @@ "Granularity": "Detaljnivå", "Refresh Page": "Uppdatera sidan", "Recently read": "Nyligen lästa", - "Show recently read": "Visa nyligen lästa" + "Show recently read": "Visa nyligen lästa", + "Find:": "Sök:", + "Enable rule": "Aktivera regel", + "Disable rule": "Inaktivera regel" } diff --git a/apps/readest-app/public/locales/ta/translation.json b/apps/readest-app/public/locales/ta/translation.json index 6420e51e..42cbe010 100644 --- a/apps/readest-app/public/locales/ta/translation.json +++ b/apps/readest-app/public/locales/ta/translation.json @@ -1809,5 +1809,8 @@ "Granularity": "நுண்மை", "Refresh Page": "பக்கத்தைப் புதுப்பி", "Recently read": "சமீபத்தில் படித்தவை", - "Show recently read": "சமீபத்தில் படித்தவற்றைக் காட்டு" + "Show recently read": "சமீபத்தில் படித்தவற்றைக் காட்டு", + "Find:": "தேடு:", + "Enable rule": "விதியை இயக்கு", + "Disable rule": "விதியை முடக்கு" } diff --git a/apps/readest-app/public/locales/th/translation.json b/apps/readest-app/public/locales/th/translation.json index 4a322f4d..caa85901 100644 --- a/apps/readest-app/public/locales/th/translation.json +++ b/apps/readest-app/public/locales/th/translation.json @@ -1776,5 +1776,8 @@ "Granularity": "ระดับความละเอียด", "Refresh Page": "รีเฟรชหน้า", "Recently read": "อ่านล่าสุด", - "Show recently read": "แสดงรายการอ่านล่าสุด" + "Show recently read": "แสดงรายการอ่านล่าสุด", + "Find:": "ค้นหา:", + "Enable rule": "เปิดใช้กฎ", + "Disable rule": "ปิดใช้กฎ" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index 9ec71601..2d53a182 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -1809,5 +1809,8 @@ "Granularity": "Ayrıntı düzeyi", "Refresh Page": "Sayfayı Yenile", "Recently read": "Son okunanlar", - "Show recently read": "Son okunanları göster" + "Show recently read": "Son okunanları göster", + "Find:": "Bul:", + "Enable rule": "Kuralı etkinleştir", + "Disable rule": "Kuralı devre dışı bırak" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index 64c60bfb..b2ff6897 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -1875,5 +1875,8 @@ "Granularity": "Деталізація", "Refresh Page": "Оновити сторінку", "Recently read": "Нещодавно прочитані", - "Show recently read": "Показувати нещодавно прочитані" + "Show recently read": "Показувати нещодавно прочитані", + "Find:": "Знайти:", + "Enable rule": "Увімкнути правило", + "Disable rule": "Вимкнути правило" } diff --git a/apps/readest-app/public/locales/uz/translation.json b/apps/readest-app/public/locales/uz/translation.json index bfb7aa93..4d7775be 100644 --- a/apps/readest-app/public/locales/uz/translation.json +++ b/apps/readest-app/public/locales/uz/translation.json @@ -1809,5 +1809,8 @@ "Granularity": "Tafsilot darajasi", "Refresh Page": "Sahifani yangilash", "Recently read": "Yaqinda o'qilgan", - "Show recently read": "Yaqinda o'qilganlarni ko'rsatish" + "Show recently read": "Yaqinda o'qilganlarni ko'rsatish", + "Find:": "Topish:", + "Enable rule": "Qoidani yoqish", + "Disable rule": "Qoidani o‘chirish" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index 0a0722ff..5c96215e 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -1776,5 +1776,8 @@ "Granularity": "Mức độ chi tiết", "Refresh Page": "Làm mới trang", "Recently read": "Đã đọc gần đây", - "Show recently read": "Hiển thị mục đã đọc gần đây" + "Show recently read": "Hiển thị mục đã đọc gần đây", + "Find:": "Tìm:", + "Enable rule": "Bật quy tắc", + "Disable rule": "Tắt quy tắc" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index 77744576..381895c2 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -1776,5 +1776,8 @@ "Granularity": "粒度", "Refresh Page": "刷新页面", "Recently read": "最近阅读", - "Show recently read": "显示最近阅读" + "Show recently read": "显示最近阅读", + "Find:": "查找:", + "Enable rule": "启用规则", + "Disable rule": "禁用规则" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index 8f84481c..8f4c44d0 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -1776,5 +1776,8 @@ "Granularity": "粒度", "Refresh Page": "重新整理頁面", "Recently read": "最近閱讀", - "Show recently read": "顯示最近閱讀" + "Show recently read": "顯示最近閱讀", + "Find:": "尋找:", + "Enable rule": "啟用規則", + "Disable rule": "停用規則" } diff --git a/apps/readest-app/src/__tests__/components/ProofreadRules.test.tsx b/apps/readest-app/src/__tests__/components/ProofreadRules.test.tsx index dce5d9a9..3aaa6f6a 100644 --- a/apps/readest-app/src/__tests__/components/ProofreadRules.test.tsx +++ b/apps/readest-app/src/__tests__/components/ProofreadRules.test.tsx @@ -305,6 +305,271 @@ describe('ProofreadRulesManager', () => { expect(screen.queryByText('deleted-pattern')).toBeNull(); }); + it('keeps a disabled book rule visible so it can be re-enabled', async () => { + (useSettingsStore.setState as unknown as (state: unknown) => void)({ + settings: { ...DEFAULT_SYSTEM_SETTINGS, globalViewSettings: { proofreadRules: [] } }, + }); + + const disabledRule: ProofreadRule = { + id: 'd1', + scope: 'book', + pattern: 'disabled-pattern', + replacement: 'x', + enabled: false, + isRegex: false, + caseSensitive: true, + order: 1, + wholeWord: true, + }; + + (useReaderStore.setState as unknown as (state: unknown) => void)({ + viewStates: { book1: { viewSettings: { proofreadRules: [disabledRule] } } }, + }); + (useBookDataStore.setState as unknown as (state: unknown) => void)({ + booksData: { + book1: { + id: 'book1', + book: null, + file: null, + config: { viewSettings: { proofreadRules: [disabledRule] } }, + bookDoc: null, + isFixedLayout: false, + }, + }, + }); + useSidebarStore.setState({ sideBarBookKey: 'book1' }); + + renderWithProviders(); + await Promise.resolve(); + setProofreadRulesVisibility(true); + + await screen.findByRole('dialog'); + expect(screen.getByText('disabled-pattern')).toBeTruthy(); + }); + + it('toggles a rule on/off via its switch', async () => { + (useSettingsStore.setState as unknown as (state: unknown) => void)({ + settings: { ...DEFAULT_SYSTEM_SETTINGS, globalViewSettings: { proofreadRules: [] } }, + }); + + const rule: ProofreadRule = { + id: 'b1', + scope: 'book', + pattern: 'toggle-me', + replacement: 'x', + enabled: true, + isRegex: false, + caseSensitive: true, + order: 1, + wholeWord: true, + }; + + (useReaderStore.setState as unknown as (state: unknown) => void)({ + viewStates: { book1: { viewSettings: { proofreadRules: [rule] } } }, + }); + (useBookDataStore.setState as unknown as (state: unknown) => void)({ + booksData: { + book1: { + id: 'book1', + book: null, + file: null, + config: { viewSettings: { proofreadRules: [rule] } }, + bookDoc: null, + isFixedLayout: false, + }, + }, + }); + useSidebarStore.setState({ sideBarBookKey: 'book1' }); + + const toggleSpy = vi + .spyOn(useProofreadStore.getState(), 'toggleRule') + .mockResolvedValue(undefined); + const recreateSpy = vi + .spyOn(useReaderStore.getState(), 'recreateViewer') + .mockResolvedValue(undefined as never); + + renderWithProviders(); + await Promise.resolve(); + setProofreadRulesVisibility(true); + await screen.findByRole('dialog'); + + const row = screen.getByText('toggle-me').closest('li'); + const toggle = within(row!).getByLabelText('Disable rule'); + fireEvent.click(toggle); + + await new Promise((r) => setTimeout(r, 0)); + + expect(toggleSpy).toHaveBeenCalledWith(expect.anything(), 'book1', 'b1'); + expect(recreateSpy).toHaveBeenCalled(); + }); + + it('edits a book rule Find pattern and saves via updateRule', async () => { + (useSettingsStore.setState as unknown as (state: unknown) => void)({ + settings: { ...DEFAULT_SYSTEM_SETTINGS, globalViewSettings: { proofreadRules: [] } }, + }); + + const rule: ProofreadRule = { + id: 'b1', + scope: 'book', + pattern: 'old-find', + replacement: 'r', + enabled: true, + isRegex: false, + caseSensitive: true, + order: 1, + wholeWord: true, + }; + + (useReaderStore.setState as unknown as (state: unknown) => void)({ + viewStates: { book1: { viewSettings: { proofreadRules: [rule] } } }, + }); + (useBookDataStore.setState as unknown as (state: unknown) => void)({ + booksData: { + book1: { + id: 'book1', + book: null, + file: null, + config: { viewSettings: { proofreadRules: [rule] } }, + bookDoc: null, + isFixedLayout: false, + }, + }, + }); + useSidebarStore.setState({ sideBarBookKey: 'book1' }); + + const updateSpy = vi + .spyOn(useProofreadStore.getState(), 'updateRule') + .mockResolvedValue(undefined); + vi.spyOn(useReaderStore.getState(), 'recreateViewer').mockResolvedValue(undefined as never); + + renderWithProviders(); + await Promise.resolve(); + setProofreadRulesVisibility(true); + await screen.findByRole('dialog'); + + const row = screen.getByText('old-find').closest('li'); + fireEvent.click(within(row!).getByLabelText('Edit')); + + const findInput = screen.getByDisplayValue('old-find') as HTMLInputElement; + expect(findInput.disabled).toBe(false); + fireEvent.change(findInput, { target: { value: 'new-find' } }); + fireEvent.click(screen.getByRole('button', { name: 'Save' })); + + await new Promise((r) => setTimeout(r, 0)); + + expect(updateSpy).toHaveBeenCalledWith( + expect.anything(), + 'book1', + 'b1', + expect.objectContaining({ pattern: 'new-find' }), + ); + }); + + it('keeps Find read-only when editing a selection rule', async () => { + (useSettingsStore.setState as unknown as (state: unknown) => void)({ + settings: { ...DEFAULT_SYSTEM_SETTINGS, globalViewSettings: { proofreadRules: [] } }, + }); + + const sel: ProofreadRule = { + id: 's1', + scope: 'selection', + pattern: 'sel-text', + replacement: 'r', + enabled: true, + isRegex: false, + caseSensitive: true, + order: 1, + wholeWord: true, + cfi: 'epubcfi(/6/14!/4/2,/1:0,/1:4)', + sectionHref: 'chapter1.html', + }; + + (useReaderStore.setState as unknown as (state: unknown) => void)({ + viewStates: { book1: { viewSettings: { proofreadRules: [sel] } } }, + }); + (useBookDataStore.setState as unknown as (state: unknown) => void)({ + booksData: { + book1: { + id: 'book1', + book: null, + file: null, + config: { viewSettings: { proofreadRules: [sel] } }, + bookDoc: null, + isFixedLayout: false, + }, + }, + }); + useSidebarStore.setState({ sideBarBookKey: 'book1' }); + + renderWithProviders(); + await Promise.resolve(); + setProofreadRulesVisibility(true); + await screen.findByRole('dialog'); + + const row = screen.getByText('sel-text').closest('li'); + fireEvent.click(within(row!).getByLabelText('Edit')); + + const findInput = screen.getByDisplayValue('sel-text') as HTMLInputElement; + expect(findInput.disabled).toBe(true); + }); + + it('warns and does not save when the edited regex is invalid', async () => { + (useSettingsStore.setState as unknown as (state: unknown) => void)({ + settings: { ...DEFAULT_SYSTEM_SETTINGS, globalViewSettings: { proofreadRules: [] } }, + }); + + const rule: ProofreadRule = { + id: 'b1', + scope: 'book', + pattern: 'valid', + replacement: 'r', + enabled: true, + isRegex: true, + caseSensitive: true, + order: 1, + wholeWord: true, + }; + + (useReaderStore.setState as unknown as (state: unknown) => void)({ + viewStates: { book1: { viewSettings: { proofreadRules: [rule] } } }, + }); + (useBookDataStore.setState as unknown as (state: unknown) => void)({ + booksData: { + book1: { + id: 'book1', + book: null, + file: null, + config: { viewSettings: { proofreadRules: [rule] } }, + bookDoc: null, + isFixedLayout: false, + }, + }, + }); + useSidebarStore.setState({ sideBarBookKey: 'book1' }); + + const updateSpy = vi + .spyOn(useProofreadStore.getState(), 'updateRule') + .mockResolvedValue(undefined); + const dispatchSpy = vi.spyOn(eventDispatcher, 'dispatch'); + + renderWithProviders(); + await Promise.resolve(); + setProofreadRulesVisibility(true); + await screen.findByRole('dialog'); + + const row = screen.getByText('valid').closest('li'); + fireEvent.click(within(row!).getByLabelText('Edit')); + + const findInput = screen.getByDisplayValue('valid') as HTMLInputElement; + fireEvent.change(findInput, { target: { value: '(' } }); + fireEvent.click(screen.getByRole('button', { name: 'Save' })); + + await new Promise((r) => setTimeout(r, 0)); + + expect(updateSpy).not.toHaveBeenCalled(); + expect(dispatchSpy).toHaveBeenCalledWith('toast', expect.objectContaining({ type: 'warning' })); + }); + it('renders a drag handle for each reorderable rule', async () => { const selectionRule: ProofreadRule = { id: 's1', diff --git a/apps/readest-app/src/app/reader/components/ProofreadRules.tsx b/apps/readest-app/src/app/reader/components/ProofreadRules.tsx index c03f38a3..d52db4f7 100644 --- a/apps/readest-app/src/app/reader/components/ProofreadRules.tsx +++ b/apps/readest-app/src/app/reader/components/ProofreadRules.tsx @@ -68,12 +68,26 @@ const RuleItem: React.FC<{ rule: ProofreadRule; scope: ProofreadScope; isEditing: boolean; - editingData: { pattern: string; replacement: string; enabled: boolean }; + editingData: { + pattern: string; + replacement: string; + enabled: boolean; + isRegex: boolean; + caseSensitive: boolean; + }; onEdit: () => void; onDelete: () => void; + onToggle: () => void; onSave: () => void; onCancel: () => void; - onEditChange: (field: 'replacement', value: string) => void; + onEditChange: ( + patch: Partial<{ + pattern: string; + replacement: string; + isRegex: boolean; + caseSensitive: boolean; + }>, + ) => void; }> = ({ rule, scope, @@ -81,6 +95,7 @@ const RuleItem: React.FC<{ editingData, onEdit, onDelete, + onToggle, onSave, onCancel, onEditChange, @@ -96,14 +111,19 @@ const RuleItem: React.FC<{ }; if (isEditing) { + const isSelection = scope === 'selection'; return (
- + onEditChange({ pattern: e.target.value })} />
@@ -112,10 +132,34 @@ const RuleItem: React.FC<{ onEditChange('replacement', e.target.value)} + spellCheck='false' + onChange={(e) => onEditChange({ replacement: e.target.value })} />
+ {!isSelection && ( +
+ + +
+ )} +