From a42897ec5cd9a96f0023081d3b2af03788da4669 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Tue, 30 Dec 2025 10:32:01 +0100 Subject: [PATCH] feat(annotator): support quick actions for text selection, closes #2505 (#2813) --- .../public/locales/ar/translation.json | 18 +- .../public/locales/bn/translation.json | 18 +- .../public/locales/bo/translation.json | 18 +- .../public/locales/de/translation.json | 18 +- .../public/locales/el/translation.json | 18 +- .../public/locales/es/translation.json | 18 +- .../public/locales/fa/translation.json | 18 +- .../public/locales/fr/translation.json | 18 +- .../public/locales/hi/translation.json | 18 +- .../public/locales/id/translation.json | 18 +- .../public/locales/it/translation.json | 18 +- .../public/locales/ja/translation.json | 18 +- .../public/locales/ko/translation.json | 18 +- .../public/locales/ms/translation.json | 18 +- .../public/locales/nl/translation.json | 18 +- .../public/locales/pl/translation.json | 18 +- .../public/locales/pt/translation.json | 18 +- .../public/locales/ru/translation.json | 18 +- .../public/locales/si/translation.json | 18 +- .../public/locales/sv/translation.json | 18 +- .../public/locales/ta/translation.json | 18 +- .../public/locales/th/translation.json | 18 +- .../public/locales/tr/translation.json | 18 +- .../public/locales/uk/translation.json | 18 +- .../public/locales/vi/translation.json | 18 +- .../public/locales/zh-CN/translation.json | 18 +- .../public/locales/zh-TW/translation.json | 18 +- .../com/bilingify/readest/MainActivity.kt | 43 ++++ .../src/app/reader/components/HeaderBar.tsx | 68 ++++++- .../components/annotator/AnnotationPopup.tsx | 4 +- ...pupButton.tsx => AnnotationToolButton.tsx} | 6 +- .../components/annotator/AnnotationTools.tsx | 92 +++++++++ .../reader/components/annotator/Annotator.tsx | 186 ++++++++++++------ .../components/annotator/QuickActionMenu.tsx | 52 +++++ .../app/reader/components/tts/TTSControl.tsx | 8 +- .../src/app/reader/hooks/useTextSelector.ts | 23 ++- .../src/app/reader/utils/annotatorUtil.ts | 10 + .../src/components/HighlighterIcon.tsx | 35 ++++ .../src/components/settings/ControlPanel.tsx | 84 ++++++++ apps/readest-app/src/helpers/shortcuts.ts | 1 + apps/readest-app/src/services/appService.ts | 2 + apps/readest-app/src/services/constants.ts | 7 + .../src/services/tts/TTSController.ts | 12 +- apps/readest-app/src/store/deviceStore.ts | 9 + apps/readest-app/src/types/annotator.ts | 10 + apps/readest-app/src/types/book.ts | 10 +- apps/readest-app/src/types/system.ts | 10 + 47 files changed, 1036 insertions(+), 122 deletions(-) rename apps/readest-app/src/app/reader/components/annotator/{PopupButton.tsx => AnnotationToolButton.tsx} (85%) create mode 100644 apps/readest-app/src/app/reader/components/annotator/AnnotationTools.tsx create mode 100644 apps/readest-app/src/app/reader/components/annotator/QuickActionMenu.tsx create mode 100644 apps/readest-app/src/app/reader/utils/annotatorUtil.ts create mode 100644 apps/readest-app/src/components/HighlighterIcon.tsx create mode 100644 apps/readest-app/src/types/annotator.ts diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index 2e08c52b..7a04777c 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -824,5 +824,21 @@ "Selected Text Rules": "قواعد النص المحدد", "No selected text replacement rules": "لا توجد قواعد استبدال نص محدد", "Book Specific Rules": "قواعد خاصة بالكتاب", - "No book-level replacement rules": "لا توجد قواعد استبدال على مستوى الكتاب" + "No book-level replacement rules": "لا توجد قواعد استبدال على مستوى الكتاب", + "Disable Quick Action": "تعطيل الإجراء السريع", + "Enable Quick Action on Selection": "تمكين الإجراء السريع عند التحديد", + "None": "لا شيء", + "Annotation Tools": "أدوات التعليق", + "Enable Quick Actions": "تمكين الإجراءات السريعة", + "Quick Action": "الإجراء السريع", + "Copy to Notebook": "نسخ إلى الدفتر", + "Copy text after selection": "نسخ النص بعد التحديد", + "Highlight text after selection": "تمييز النص بعد التحديد", + "Annotate text after selection": "تعليق على النص بعد التحديد", + "Search text after selection": "البحث في النص بعد التحديد", + "Look up text in dictionary after selection": "البحث عن النص في القاموس بعد التحديد", + "Look up text in Wikipedia after selection": "البحث عن النص في ويكيبيديا بعد التحديد", + "Translate text after selection": "ترجمة النص بعد التحديد", + "Read text aloud after selection": "قراءة النص بصوت عالٍ بعد التحديد", + "Proofread text after selection": "تدقيق النص بعد التحديد" } diff --git a/apps/readest-app/public/locales/bn/translation.json b/apps/readest-app/public/locales/bn/translation.json index 1a8817b9..7e12af47 100644 --- a/apps/readest-app/public/locales/bn/translation.json +++ b/apps/readest-app/public/locales/bn/translation.json @@ -784,5 +784,21 @@ "Selected Text Rules": "নির্বাচিত টেক্সট নিয়ম", "No selected text replacement rules": "কোনও নির্বাচিত টেক্সট বিকল্প নিয়ম নেই", "Book Specific Rules": "বই নির্দিষ্ট নিয়ম", - "No book-level replacement rules": "কোনও বই-স্তরের বিকল্প নিয়ম নেই" + "No book-level replacement rules": "কোনও বই-স্তরের বিকল্প নিয়ম নেই", + "Disable Quick Action": "দ্রুত ক্রিয়া নিষ্ক্রিয় করুন", + "Enable Quick Action on Selection": "নির্বাচনের উপর দ্রুত ক্রিয়া সক্ষম করুন", + "None": "কোনোটিই নয়", + "Annotation Tools": "অ্যনোটেশন সরঞ্জামসমূহ", + "Enable Quick Actions": "দ্রুত ক্রিয়া সক্ষম করুন", + "Quick Action": "দ্রুত ক্রিয়া", + "Copy to Notebook": "নোটবুকে কপি করুন", + "Copy text after selection": "নির্বাচনের পরে টেক্সট কপি করুন", + "Highlight text after selection": "নির্বাচনের পরে টেক্সট হাইলাইট করুন", + "Annotate text after selection": "নির্বাচনের পরে টেক্সট মন্তব্য করুন", + "Search text after selection": "নির্বাচনের পরে টেক্সট অনুসন্ধান করুন", + "Look up text in dictionary after selection": "নির্বাচনের পরে টেক্সট অভিধানে দেখুন", + "Look up text in Wikipedia after selection": "নির্বাচনের পরে টেক্সট উইকিপিডিয়ায় দেখুন", + "Translate text after selection": "নির্বাচনের পরে টেক্সট অনুবাদ করুন", + "Read text aloud after selection": "নির্বাচনের পরে টেক্সট উচ্চারণ করুন", + "Proofread text after selection": "নির্বাচনের পরে টেক্সট প্রুফরিড করুন" } diff --git a/apps/readest-app/public/locales/bo/translation.json b/apps/readest-app/public/locales/bo/translation.json index 4a2193d1..82ed7181 100644 --- a/apps/readest-app/public/locales/bo/translation.json +++ b/apps/readest-app/public/locales/bo/translation.json @@ -774,5 +774,21 @@ "Selected Text Rules": "བདམས་པའི་ཡིག་ཆ་བརྗེ་སྒྱུར་སྲིད་སྒྲིག", "No selected text replacement rules": "བདམས་པའི་ཡིག་ཆ་བརྗེ་སྒྱུར་སྲིད་སྒྲིག་མེད་པ", "Book Specific Rules": "དེབ་སྒོས་ཀྱི་བརྗེ་སྒྱུར་སྲིད་སྒྲིག", - "No book-level replacement rules": "དེབ་རིམ་གྱི་བརྗེ་སྒྱུར་སྲིད་སྒྲིག་མེད་པ" + "No book-level replacement rules": "དེབ་རིམ་གྱི་བརྗེ་སྒྱུར་སྲིད་སྒྲིག་མེད་པ", + "Disable Quick Action": "མགྱོགས་མྱུར་བྱ་བ་སྤང་", + "Enable Quick Action on Selection": "འདེམས་པའི་སྐབས་མགྱོགས་མྱུར་བྱ་བ་སྤྱོད་", + "None": "མེད་", + "Annotation Tools": "མཆན་འགྲེལ་ལག་ཆ་", + "Enable Quick Actions": "མགྱོགས་མྱུར་བྱ་བ་སྤྱོད་", + "Quick Action": "མགྱོགས་མྱུར་བྱ་བ་", + "Copy to Notebook": "ཟིན་དེབ་ནང་འདྲ་བཤུས་", + "Copy text after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་འདྲ་བཤུས་", + "Highlight text after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་མངོན་གསལ་བྱེད་", + "Annotate text after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་མཆན་འགྲེལ་བྱེད་", + "Search text after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་འཚོལ་ཞིབ་བྱེད་", + "Look up text in dictionary after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་ཚིག་མཛོད་ནང་འཚོལ་", + "Look up text in Wikipedia after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་ཝེ་ཀི་པི་ཌི་ཡི་ནང་འཚོལ་", + "Translate text after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་སྐད་བསྒྱུར་བྱེད་", + "Read text aloud after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་སྐད་ཀྱིས་ཀློག་", + "Proofread text after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་ཞིབ་བཤེར་བྱེད་" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index edf7af5f..714e6fa5 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -784,5 +784,21 @@ "Selected Text Rules": "Regeln für ausgewählten Text", "No selected text replacement rules": "Keine Ersetzungsregeln für ausgewählten Text", "Book Specific Rules": "Buchspezifische Regeln", - "No book-level replacement rules": "Keine Ersetzungsregeln auf Buchebene" + "No book-level replacement rules": "Keine Ersetzungsregeln auf Buchebene", + "Disable Quick Action": "Schnellaktion deaktivieren", + "Enable Quick Action on Selection": "Schnellaktion bei Auswahl aktivieren", + "None": "Keine", + "Annotation Tools": "Anmerkungswerkzeuge", + "Enable Quick Actions": "Schnellaktionen aktivieren", + "Quick Action": "Schnellaktion", + "Copy to Notebook": "In Notizbuch kopieren", + "Copy text after selection": "Text nach Auswahl kopieren", + "Highlight text after selection": "Text nach Auswahl hervorheben", + "Annotate text after selection": "Text nach Auswahl annotieren", + "Search text after selection": "Text nach Auswahl suchen", + "Look up text in dictionary after selection": "Text nach Auswahl im Wörterbuch nachschlagen", + "Look up text in Wikipedia after selection": "Text nach Auswahl in Wikipedia nachschlagen", + "Translate text after selection": "Text nach Auswahl übersetzen", + "Read text aloud after selection": "Text nach Auswahl vorlesen", + "Proofread text after selection": "Text nach Auswahl Korrektur lesen" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index 828fad6f..9322f9c8 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -784,5 +784,21 @@ "Selected Text Rules": "Κανόνες επιλεγμένου κειμένου", "No selected text replacement rules": "Δεν υπάρχουν κανόνες αντικατάστασης για το επιλεγμένο κείμενο", "Book Specific Rules": "Κανόνες για συγκεκριμένο βιβλίο", - "No book-level replacement rules": "Δεν υπάρχουν κανόνες αντικατάστασης σε επίπεδο βιβλίου" + "No book-level replacement rules": "Δεν υπάρχουν κανόνες αντικατάστασης σε επίπεδο βιβλίου", + "Disable Quick Action": "Απενεργοποίηση γρήγορης ενέργειας", + "Enable Quick Action on Selection": "Ενεργοποίηση γρήγορης ενέργειας κατά την επιλογή", + "None": "Καμία", + "Annotation Tools": "Εργαλεία σχολιασμού", + "Enable Quick Actions": "Ενεργοποίηση γρήγορων ενεργειών", + "Quick Action": "Γρήγορη ενέργεια", + "Copy to Notebook": "Αντιγραφή στο σημειωματάριο", + "Copy text after selection": "Αντιγραφή κειμένου μετά την επιλογή", + "Highlight text after selection": "Επισήμανση κειμένου μετά την επιλογή", + "Annotate text after selection": "Σχολιασμός κειμένου μετά την επιλογή", + "Search text after selection": "Αναζήτηση κειμένου μετά την επιλογή", + "Look up text in dictionary after selection": "Αναζήτηση κειμένου στο λεξικό μετά την επιλογή", + "Look up text in Wikipedia after selection": "Αναζήτηση κειμένου στη Wikipedia μετά την επιλογή", + "Translate text after selection": "Μετάφραση κειμένου μετά την επιλογή", + "Read text aloud after selection": "Ανάγνωση κειμένου μετά την επιλογή", + "Proofread text after selection": "Διόρθωση κειμένου μετά την επιλογή" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index f3ab6fbb..36161cfe 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -794,5 +794,21 @@ "Selected Text Rules": "Reglas de texto seleccionado", "No selected text replacement rules": "No hay reglas de reemplazo para el texto seleccionado", "Book Specific Rules": "Reglas específicas del libro", - "No book-level replacement rules": "No hay reglas de reemplazo a nivel de libro" + "No book-level replacement rules": "No hay reglas de reemplazo a nivel de libro", + "Disable Quick Action": "Desactivar acción rápida", + "Enable Quick Action on Selection": "Activar acción rápida al seleccionar", + "None": "Ninguna", + "Annotation Tools": "Herramientas de anotación", + "Enable Quick Actions": "Activar acciones rápidas", + "Quick Action": "Acción rápida", + "Copy to Notebook": "Copiar al cuaderno", + "Copy text after selection": "Copiar texto después de la selección", + "Highlight text after selection": "Resaltar texto después de la selección", + "Annotate text after selection": "Anotar texto después de la selección", + "Search text after selection": "Buscar texto después de la selección", + "Look up text in dictionary after selection": "Buscar texto en el diccionario después de la selección", + "Look up text in Wikipedia after selection": "Buscar texto en Wikipedia después de la selección", + "Translate text after selection": "Traducir texto después de la selección", + "Read text aloud after selection": "Leer texto en voz alta después de la selección", + "Proofread text after selection": "Corregir texto después de la selección" } diff --git a/apps/readest-app/public/locales/fa/translation.json b/apps/readest-app/public/locales/fa/translation.json index b01cbd3a..849bb0d3 100644 --- a/apps/readest-app/public/locales/fa/translation.json +++ b/apps/readest-app/public/locales/fa/translation.json @@ -784,5 +784,21 @@ "Selected Text Rules": "قوانین متن انتخاب‌شده", "No selected text replacement rules": "هیچ قانون جایگزینی برای متن انتخاب‌شده وجود ندارد", "Book Specific Rules": "قوانین مخصوص کتاب", - "No book-level replacement rules": "هیچ قانون جایگزینی در سطح کتاب وجود ندارد" + "No book-level replacement rules": "هیچ قانون جایگزینی در سطح کتاب وجود ندارد", + "Disable Quick Action": "غیرفعال‌سازی اقدام سریع", + "Enable Quick Action on Selection": "فعال‌سازی اقدام سریع هنگام انتخاب", + "None": "هیچ‌کدام", + "Annotation Tools": "ابزارهای یادداشت‌گذاری", + "Enable Quick Actions": "فعال‌سازی اقدامات سریع", + "Quick Action": "اقدام سریع", + "Copy to Notebook": "کپی به دفترچه", + "Copy text after selection": "کپی متن پس از انتخاب", + "Highlight text after selection": "برجسته‌سازی متن پس از انتخاب", + "Annotate text after selection": "یادداشت‌گذاری متن پس از انتخاب", + "Search text after selection": "جستجوی متن پس از انتخاب", + "Look up text in dictionary after selection": "جستجوی متن در فرهنگ لغت پس از انتخاب", + "Look up text in Wikipedia after selection": "جستجوی متن در ویکی‌پدیا پس از انتخاب", + "Translate text after selection": "ترجمه متن پس از انتخاب", + "Read text aloud after selection": "خواندن متن با صدای بلند پس از انتخاب", + "Proofread text after selection": "بازبینی متن پس از انتخاب" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index 4d1bcb33..9da592eb 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -794,5 +794,21 @@ "Selected Text Rules": "Règles du texte sélectionné", "No selected text replacement rules": "Aucune règle de remplacement pour le texte sélectionné", "Book Specific Rules": "Règles spécifiques au livre", - "No book-level replacement rules": "Aucune règle de remplacement au niveau du livre" + "No book-level replacement rules": "Aucune règle de remplacement au niveau du livre", + "Disable Quick Action": "Désactiver l’action rapide", + "Enable Quick Action on Selection": "Activer l’action rapide lors de la sélection", + "None": "Aucune", + "Annotation Tools": "Outils d’annotation", + "Enable Quick Actions": "Activer les actions rapides", + "Quick Action": "Action rapide", + "Copy to Notebook": "Copier dans le carnet", + "Copy text after selection": "Copier le texte après la sélection", + "Highlight text after selection": "Mettre en surbrillance le texte après la sélection", + "Annotate text after selection": "Annoter le texte après la sélection", + "Search text after selection": "Rechercher le texte après la sélection", + "Look up text in dictionary after selection": "Chercher le texte dans le dictionnaire après la sélection", + "Look up text in Wikipedia after selection": "Chercher le texte dans Wikipédia après la sélection", + "Translate text after selection": "Traduire le texte après la sélection", + "Read text aloud after selection": "Lire le texte à haute voix après la sélection", + "Proofread text after selection": "Relire le texte après la sélection" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index 2f56c464..9b8056a7 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -784,5 +784,21 @@ "Selected Text Rules": "चयनित पाठ के नियम", "No selected text replacement rules": "चयनित पाठ के लिए कोई प्रतिस्थापन नियम नहीं हैं", "Book Specific Rules": "पुस्तक-विशिष्ट नियम", - "No book-level replacement rules": "पुस्तक स्तर पर कोई प्रतिस्थापन नियम नहीं हैं" + "No book-level replacement rules": "पुस्तक स्तर पर कोई प्रतिस्थापन नियम नहीं हैं", + "Disable Quick Action": "त्वरित क्रिया अक्षम करें", + "Enable Quick Action on Selection": "चयन पर त्वरित क्रिया सक्षम करें", + "None": "कोई नहीं", + "Annotation Tools": "टिप्पणी उपकरण", + "Enable Quick Actions": "त्वरित क्रियाएँ सक्षम करें", + "Quick Action": "त्वरित क्रिया", + "Copy to Notebook": "नोटबुक में कॉपी करें", + "Copy text after selection": "पाठ कॉपी करें चयन के बाद", + "Highlight text after selection": "चयन के बाद पाठ हाइलाइट करें", + "Annotate text after selection": "चयन के बाद पाठ पर टिप्पणी करें", + "Search text after selection": "चयन के बाद पाठ खोजें", + "Look up text in dictionary after selection": "चयन के बाद शब्दकोश में पाठ देखें", + "Look up text in Wikipedia after selection": "चयन के बाद विकिपीडिया में पाठ देखें", + "Translate text after selection": "चयन के बाद पाठ का अनुवाद करें", + "Read text aloud after selection": "चयन के बाद पाठ को जोर से पढ़ें", + "Proofread text after selection": "चयन के बाद पाठ को प्रूफरीड करें" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index 32945a56..b729a00f 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -774,5 +774,21 @@ "Selected Text Rules": "Aturan teks terpilih", "No selected text replacement rules": "Tidak ada aturan penggantian untuk teks terpilih", "Book Specific Rules": "Aturan khusus buku", - "No book-level replacement rules": "Tidak ada aturan penggantian tingkat buku" + "No book-level replacement rules": "Tidak ada aturan penggantian tingkat buku", + "Disable Quick Action": "Nonaktifkan Aksi Cepat", + "Enable Quick Action on Selection": "Aktifkan Aksi Cepat saat memilih", + "None": "Tidak ada", + "Annotation Tools": "Alat Anotasi", + "Enable Quick Actions": "Aktifkan Aksi Cepat", + "Quick Action": "Aksi Cepat", + "Copy to Notebook": "Salin ke Buku Catatan", + "Copy text after selection": "Salin teks setelah pemilihan", + "Highlight text after selection": "Sorot teks setelah pemilihan", + "Annotate text after selection": "Anotasi teks setelah pemilihan", + "Search text after selection": "Cari teks setelah pemilihan", + "Look up text in dictionary after selection": "Cari teks di kamus setelah pemilihan", + "Look up text in Wikipedia after selection": "Cari teks di Wikipedia setelah pemilihan", + "Translate text after selection": "Terjemahkan teks setelah pemilihan", + "Read text aloud after selection": "Bacakan teks setelah pemilihan", + "Proofread text after selection": "Periksa teks setelah pemilihan" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index 3a2b1c6b..19cb0e13 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -794,5 +794,21 @@ "Selected Text Rules": "Regole per il testo selezionato", "No selected text replacement rules": "Nessuna regola di sostituzione per il testo selezionato", "Book Specific Rules": "Regole specifiche del libro", - "No book-level replacement rules": "Nessuna regola di sostituzione a livello di libro" + "No book-level replacement rules": "Nessuna regola di sostituzione a livello di libro", + "Disable Quick Action": "Disattiva azione rapida", + "Enable Quick Action on Selection": "Attiva azione rapida sulla selezione", + "None": "Nessuno", + "Annotation Tools": "Strumenti di annotazione", + "Enable Quick Actions": "Attiva azioni rapide", + "Quick Action": "Azione rapida", + "Copy to Notebook": "Copia nel taccuino", + "Copy text after selection": "Copia testo dopo la selezione", + "Highlight text after selection": "Evidenzia testo dopo la selezione", + "Annotate text after selection": "Annota testo dopo la selezione", + "Search text after selection": "Cerca testo dopo la selezione", + "Look up text in dictionary after selection": "Cerca testo nel dizionario dopo la selezione", + "Look up text in Wikipedia after selection": "Cerca testo in Wikipedia dopo la selezione", + "Translate text after selection": "Traduci testo dopo la selezione", + "Read text aloud after selection": "Leggi ad alta voce il testo dopo la selezione", + "Proofread text after selection": "Correggi il testo dopo la selezione" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index 3784f2c8..8f113d82 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -774,5 +774,21 @@ "Selected Text Rules": "選択テキストのルール", "No selected text replacement rules": "選択されたテキストの置換ルールはありません", "Book Specific Rules": "書籍別ルール", - "No book-level replacement rules": "書籍レベルの置換ルールはありません" + "No book-level replacement rules": "書籍レベルの置換ルールはありません", + "Disable Quick Action": "クイックアクションを無効にする", + "Enable Quick Action on Selection": "選択時にクイックアクションを有効にする", + "None": "なし", + "Annotation Tools": "注釈ツール", + "Enable Quick Actions": "クイックアクションを有効にする", + "Quick Action": "クイックアクション", + "Copy to Notebook": "ノートにコピー", + "Copy text after selection": "選択後のテキストをコピー", + "Highlight text after selection": "選択後のテキストをハイライト", + "Annotate text after selection": "選択後のテキストに注釈を付ける", + "Search text after selection": "選択後のテキストを検索", + "Look up text in dictionary after selection": "選択後のテキストを辞書で調べる", + "Look up text in Wikipedia after selection": "選択後のテキストをWikipediaで調べる", + "Translate text after selection": "選択後のテキストを翻訳する", + "Read text aloud after selection": "選択後のテキストを音読する", + "Proofread text after selection": "選択後のテキストを校正する" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index 76d4fc00..3c2b6ec2 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -774,5 +774,21 @@ "Selected Text Rules": "선택한 텍스트 규칙", "No selected text replacement rules": "선택한 텍스트에 대한 대체 규칙이 없습니다", "Book Specific Rules": "책별 규칙", - "No book-level replacement rules": "책 수준의 대체 규칙이 없습니다" + "No book-level replacement rules": "책 수준의 대체 규칙이 없습니다", + "Disable Quick Action": "빠른 작업 비활성화", + "Enable Quick Action on Selection": "선택 시 빠른 작업 활성화", + "None": "없음", + "Annotation Tools": "주석 도구", + "Enable Quick Actions": "빠른 작업 활성화", + "Quick Action": "빠른 작업", + "Copy to Notebook": "노트에 복사", + "Copy text after selection": "선택 후 텍스트 복사", + "Highlight text after selection": "선택 후 텍스트 강조", + "Annotate text after selection": "선택 후 텍스트 주석 달기", + "Search text after selection": "선택 후 텍스트 검색", + "Look up text in dictionary after selection": "선택 후 텍스트를 사전에서 찾기", + "Look up text in Wikipedia after selection": "선택 후 텍스트를 위키피디아에서 찾기", + "Translate text after selection": "선택 후 텍스트 번역", + "Read text aloud after selection": "선택 후 텍스트 음성 읽기", + "Proofread text after selection": "선택 후 텍스트 교정" } diff --git a/apps/readest-app/public/locales/ms/translation.json b/apps/readest-app/public/locales/ms/translation.json index c774f977..7a0035c8 100644 --- a/apps/readest-app/public/locales/ms/translation.json +++ b/apps/readest-app/public/locales/ms/translation.json @@ -774,5 +774,21 @@ "Selected Text Rules": "Peraturan teks dipilih", "No selected text replacement rules": "Tiada peraturan penggantian untuk teks dipilih", "Book Specific Rules": "Peraturan khusus buku", - "No book-level replacement rules": "Tiada peraturan penggantian peringkat buku" + "No book-level replacement rules": "Tiada peraturan penggantian peringkat buku", + "Disable Quick Action": "Nyahaktifkan Tindakan Pantas", + "Enable Quick Action on Selection": "Aktifkan Tindakan Pantas pada Pilihan", + "None": "Tiada", + "Annotation Tools": "Alat Anotasi", + "Enable Quick Actions": "Aktifkan Tindakan Pantas", + "Quick Action": "Tindakan Pantas", + "Copy to Notebook": "Salin ke Buku Nota", + "Copy text after selection": "Salin teks selepas pilihan", + "Highlight text after selection": "Sorot teks selepas pilihan", + "Annotate text after selection": "Anotasi teks selepas pilihan", + "Search text after selection": "Cari teks selepas pilihan", + "Look up text in dictionary after selection": "Cari teks dalam kamus selepas pilihan", + "Look up text in Wikipedia after selection": "Cari teks dalam Wikipedia selepas pilihan", + "Translate text after selection": "Terjemah teks selepas pilihan", + "Read text aloud after selection": "Baca teks dengan kuat selepas pilihan", + "Proofread text after selection": "Semak teks selepas pilihan" } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index ffed771c..925af63b 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -784,5 +784,21 @@ "Selected Text Rules": "Regels voor geselecteerde tekst", "No selected text replacement rules": "Geen vervangingsregels voor geselecteerde tekst", "Book Specific Rules": "Boekspecifieke regels", - "No book-level replacement rules": "Geen vervangingsregels op boekniveau" + "No book-level replacement rules": "Geen vervangingsregels op boekniveau", + "Disable Quick Action": "Snelle actie uitschakelen", + "Enable Quick Action on Selection": "Snelle actie bij selectie inschakelen", + "None": "Geen", + "Annotation Tools": "Annotatiehulpmiddelen", + "Enable Quick Actions": "Snelle acties inschakelen", + "Quick Action": "Snelle actie", + "Copy to Notebook": "Kopiëren naar notitieboek", + "Copy text after selection": "Kopieer tekst na selectie", + "Highlight text after selection": "Markeer tekst na selectie", + "Annotate text after selection": "Annoteren tekst na selectie", + "Search text after selection": "Zoek tekst na selectie", + "Look up text in dictionary after selection": "Zoek tekst op in woordenboek na selectie", + "Look up text in Wikipedia after selection": "Zoek tekst op in Wikipedia na selectie", + "Translate text after selection": "Vertaal tekst na selectie", + "Read text aloud after selection": "Lees tekst hardop na selectie", + "Proofread text after selection": "Corrigeer tekst na selectie" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index 07d6d9d1..adaadaa1 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -804,5 +804,21 @@ "Selected Text Rules": "Reguły dla zaznaczonego tekstu", "No selected text replacement rules": "Brak reguł zastępowania dla zaznaczonego tekstu", "Book Specific Rules": "Reguły specyficzne dla książki", - "No book-level replacement rules": "Brak reguł zastępowania na poziomie książki" + "No book-level replacement rules": "Brak reguł zastępowania na poziomie książki", + "Disable Quick Action": "Wyłącz szybką akcję", + "Enable Quick Action on Selection": "Włącz szybką akcję przy zaznaczeniu", + "None": "Brak", + "Annotation Tools": "Narzędzia do adnotacji", + "Enable Quick Actions": "Włącz szybkie akcje", + "Quick Action": "Szybka akcja", + "Copy to Notebook": "Kopiuj do notatnika", + "Copy text after selection": "Kopiuj zaznaczony tekst", + "Highlight text after selection": "Podświetl zaznaczony tekst", + "Annotate text after selection": "Dodaj adnotację do zaznaczonego tekstu", + "Search text after selection": "Wyszukaj zaznaczony tekst", + "Look up text in dictionary after selection": "Sprawdź zaznaczony tekst w słowniku", + "Look up text in Wikipedia after selection": "Sprawdź zaznaczony tekst w Wikipedii", + "Translate text after selection": "Przetłumacz zaznaczony tekst", + "Read text aloud after selection": "Odczytaj zaznaczony tekst na głos", + "Proofread text after selection": "Sprawdź zaznaczony tekst" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index 292d004a..41f7b473 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -794,5 +794,21 @@ "Selected Text Rules": "Regras do texto selecionado", "No selected text replacement rules": "Não existem regras de substituição para o texto selecionado", "Book Specific Rules": "Regras específicas do livro", - "No book-level replacement rules": "Não existem regras de substituição ao nível do livro" + "No book-level replacement rules": "Não existem regras de substituição ao nível do livro", + "Disable Quick Action": "Desativar Ação Rápida", + "Enable Quick Action on Selection": "Ativar Ação Rápida na Seleção", + "None": "Nenhum", + "Annotation Tools": "Ferramentas de Anotação", + "Enable Quick Actions": "Ativar Ações Rápidas", + "Quick Action": "Ação Rápida", + "Copy to Notebook": "Copiar para o Caderno", + "Copy text after selection": "Copiar texto após a seleção", + "Highlight text after selection": "Destacar texto após a seleção", + "Annotate text after selection": "Anotar texto após a seleção", + "Search text after selection": "Pesquisar texto após a seleção", + "Look up text in dictionary after selection": "Procurar texto no dicionário após a seleção", + "Look up text in Wikipedia after selection": "Procurar texto na Wikipedia após a seleção", + "Translate text after selection": "Traduzir texto após a seleção", + "Read text aloud after selection": "Ler texto em voz alta após a seleção", + "Proofread text after selection": "Revisar texto após a seleção" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index a2ff902a..d82e46f6 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -804,5 +804,21 @@ "Selected Text Rules": "Правила для выделенного текста", "No selected text replacement rules": "Нет правил замены для выделенного текста", "Book Specific Rules": "Правила для конкретной книги", - "No book-level replacement rules": "Нет правил замены на уровне книги" + "No book-level replacement rules": "Нет правил замены на уровне книги", + "Disable Quick Action": "Отключить быстрые действия", + "Enable Quick Action on Selection": "Включить быстрые действия при выборе", + "None": "Нет", + "Annotation Tools": "Инструменты для аннотаций", + "Enable Quick Actions": "Включить быстрые действия", + "Quick Action": "Быстрое действие", + "Copy to Notebook": "Скопировать в блокнот", + "Copy text after selection": "Копировать текст после выделения", + "Highlight text after selection": "Выделить текст после выделения", + "Annotate text after selection": "Аннотировать текст после выделения", + "Search text after selection": "Искать текст после выделения", + "Look up text in dictionary after selection": "Искать текст в словаре после выделения", + "Look up text in Wikipedia after selection": "Искать текст в Википедии после выделения", + "Translate text after selection": "Перевести текст после выделения", + "Read text aloud after selection": "Прочитать текст вслух после выделения", + "Proofread text after selection": "Корректировать текст после выделения" } diff --git a/apps/readest-app/public/locales/si/translation.json b/apps/readest-app/public/locales/si/translation.json index 90d4e497..d079757b 100644 --- a/apps/readest-app/public/locales/si/translation.json +++ b/apps/readest-app/public/locales/si/translation.json @@ -784,5 +784,21 @@ "Selected Text Rules": "තේරූ පෙළ සඳහා නියමයන්", "No selected text replacement rules": "තේරූ පෙළ සඳහා ප්‍රතිස්ථාපන නියමයන් නොමැත", "Book Specific Rules": "පොතට විශේෂිත නියමයන්", - "No book-level replacement rules": "පොත් මට්ටමේ ප්‍රතිස්ථාපන නියමයන් නොමැත" + "No book-level replacement rules": "පොත් මට්ටමේ ප්‍රතිස්ථාපන නියමයන් නොමැත", + "Disable Quick Action": "තට්ටු ක්‍රියාව අක්‍රිය කරන්න", + "Enable Quick Action on Selection": "තෝරා ගැනීමේදී තට්ටු ක්‍රියාව සක්‍රීය කරන්න", + "None": "කිසිවක් නැත", + "Annotation Tools": "සටහන් මෙවලම්", + "Enable Quick Actions": "තට්ටු ක්‍රියාවන් සක්‍රීය කරන්න", + "Quick Action": "තට්ටු ක්‍රියාව", + "Copy to Notebook": "සටහන් පොතට පිටපත් කරන්න", + "Copy text after selection": "තේරීමෙන් පසු පෙළ පිටපත් කරන්න", + "Highlight text after selection": "තේරීමෙන් පසු පෙළ හයිලයිට් කරන්න", + "Annotate text after selection": "තේරීමෙන් පසු පෙළ සටහන් කරන්න", + "Search text after selection": "තේරීමෙන් පසු පෙළ සෙවීම", + "Look up text in dictionary after selection": "තේරීමෙන් පසු ශබ්දකෝෂයේ පෙළ සෙවීම", + "Look up text in Wikipedia after selection": "තේරීමෙන් පසු විකිපීඩියාවේ පෙළ සෙවීම", + "Translate text after selection": "තේරීමෙන් පසු පෙළ පරිවර්තනය කරන්න", + "Read text aloud after selection": "තේරීමෙන් පසු පෙළ උච්චාරණය කරන්න", + "Proofread text after selection": "තේරීමෙන් පසු පෙළ සංශෝධනය කරන්න" } diff --git a/apps/readest-app/public/locales/sv/translation.json b/apps/readest-app/public/locales/sv/translation.json index 00016a8e..aeb7eb44 100644 --- a/apps/readest-app/public/locales/sv/translation.json +++ b/apps/readest-app/public/locales/sv/translation.json @@ -784,5 +784,21 @@ "Selected Text Rules": "Regler för markerad text", "No selected text replacement rules": "Inga ersättningsregler för markerad text", "Book Specific Rules": "Bokspecifika regler", - "No book-level replacement rules": "Inga ersättningsregler på boknivå" + "No book-level replacement rules": "Inga ersättningsregler på boknivå", + "Disable Quick Action": "Inaktivera snabbåtgärd", + "Enable Quick Action on Selection": "Aktivera snabbåtgärd vid val", + "None": "Ingen", + "Annotation Tools": "Anteckningsverktyg", + "Enable Quick Actions": "Aktivera snabba åtgärder", + "Quick Action": "Snabbåtgärd", + "Copy to Notebook": "Kopiera till anteckningsbok", + "Copy text after selection": "Kopiera text efter markering", + "Highlight text after selection": "Markera text efter markering", + "Annotate text after selection": "Anteckna text efter markering", + "Search text after selection": "Sök text efter markering", + "Look up text in dictionary after selection": "Slå upp text i ordbok efter markering", + "Look up text in Wikipedia after selection": "Slå upp text i Wikipedia efter markering", + "Translate text after selection": "Översätt text efter markering", + "Read text aloud after selection": "Läs upp text efter markering", + "Proofread text after selection": "Korrekturläs text efter markering" } diff --git a/apps/readest-app/public/locales/ta/translation.json b/apps/readest-app/public/locales/ta/translation.json index f362bccd..7adca3f2 100644 --- a/apps/readest-app/public/locales/ta/translation.json +++ b/apps/readest-app/public/locales/ta/translation.json @@ -784,5 +784,21 @@ "Selected Text Rules": "தேர்ந்தெடுக்கப்பட்ட உரைக்கான விதிகள்", "No selected text replacement rules": "தேர்ந்தெடுக்கப்பட்ட உரைக்கான மாற்று விதிகள் இல்லை", "Book Specific Rules": "புத்தகத்திற்கு குறிப்பிட்ட விதிகள்", - "No book-level replacement rules": "புத்தக மட்டத்தில் மாற்று விதிகள் இல்லை" + "No book-level replacement rules": "புத்தக மட்டத்தில் மாற்று விதிகள் இல்லை", + "Disable Quick Action": "விரைவு செயலை முடக்கு", + "Enable Quick Action on Selection": "தேர்வில் விரைவு செயலை இயக்கு", + "None": "எதுவும் இல்லை", + "Annotation Tools": "கருத்துரை கருவிகள்", + "Enable Quick Actions": "விரைவு செயல்களை இயக்கு", + "Quick Action": "விரைவு செயல்", + "Copy to Notebook": "குறிப்பேட்டியில் நகலெடு", + "Copy text after selection": "உரையை தேர்வுக்குப் பிறகு நகலெடு", + "Highlight text after selection": "உரையை தேர்வுக்குப் பிறகு முத்திரை இடு", + "Annotate text after selection": "உரையை தேர்வுக்குப் பிறகு கருத்துரை இடு", + "Search text after selection": "உரையை தேர்வுக்குப் பிறகு தேடு", + "Look up text in dictionary after selection": "தேர்வுக்குப் பிறகு அகராதியில் உரையைத் தேடு", + "Look up text in Wikipedia after selection": "தேர்வுக்குப் பிறகு விக்கிப்பீடியாவில் உரையைத் தேடு", + "Translate text after selection": "தேர்வுக்குப் பிறகு உரையை மொழிபெயர்", + "Read text aloud after selection": "தேர்வுக்குப் பிறகு உரையை ஓதுக", + "Proofread text after selection": "தேர்வுக்குப் பிறகு உரையை திருத்துக" } diff --git a/apps/readest-app/public/locales/th/translation.json b/apps/readest-app/public/locales/th/translation.json index 73051390..12abb074 100644 --- a/apps/readest-app/public/locales/th/translation.json +++ b/apps/readest-app/public/locales/th/translation.json @@ -774,5 +774,21 @@ "Selected Text Rules": "กฎสำหรับข้อความที่เลือก", "No selected text replacement rules": "ไม่มีกฎการแทนที่สำหรับข้อความที่เลือก", "Book Specific Rules": "กฎเฉพาะของหนังสือ", - "No book-level replacement rules": "ไม่มีกฎการแทนที่ระดับหนังสือ" + "No book-level replacement rules": "ไม่มีกฎการแทนที่ระดับหนังสือ", + "Disable Quick Action": "ปิดใช้งานการดำเนินการด่วน", + "Enable Quick Action on Selection": "เปิดใช้งานการดำเนินการด่วนเมื่อเลือก", + "None": "ไม่มี", + "Annotation Tools": "เครื่องมือหมายเหตุ", + "Enable Quick Actions": "เปิดใช้งานการดำเนินการด่วน", + "Quick Action": "การดำเนินการด่วน", + "Copy to Notebook": "คัดลอกไปยังสมุดบันทึก", + "Copy text after selection": "คัดลอกข้อความหลังการเลือก", + "Highlight text after selection": "ไฮไลต์ข้อความหลังการเลือก", + "Annotate text after selection": "เพิ่มหมายเหตุข้อความหลังการเลือก", + "Search text after selection": "ค้นหาข้อความหลังการเลือก", + "Look up text in dictionary after selection": "ค้นหาข้อความในพจนานุกรมหลังการเลือก", + "Look up text in Wikipedia after selection": "ค้นหาข้อความในวิกิพีเดียหลังการเลือก", + "Translate text after selection": "แปลข้อความหลังการเลือก", + "Read text aloud after selection": "อ่านข้อความออกเสียงหลังการเลือก", + "Proofread text after selection": "ตรวจทานข้อความหลังการเลือก" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index 22dfaa97..af07d42e 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -784,5 +784,21 @@ "Selected Text Rules": "Seçili Metin Kuralları", "No selected text replacement rules": "Seçili metin için değiştirme kuralı yok", "Book Specific Rules": "Kitaba Özel Kurallar", - "No book-level replacement rules": "Kitap düzeyinde değiştirme kuralı yok" + "No book-level replacement rules": "Kitap düzeyinde değiştirme kuralı yok", + "Disable Quick Action": "Hızlı işlemi devre dışı bırak", + "Enable Quick Action on Selection": "Seçimde hızlı işlemi etkinleştir", + "None": "Hiçbiri", + "Annotation Tools": "Not araçları", + "Enable Quick Actions": "Hızlı işlemleri etkinleştir", + "Quick Action": "Hızlı işlem", + "Copy to Notebook": "Deftere kopyala", + "Copy text after selection": "Seçimden sonra metni kopyala", + "Highlight text after selection": "Seçimden sonra metni vurgula", + "Annotate text after selection": "Seçimden sonra metni not al", + "Search text after selection": "Seçimden sonra metni ara", + "Look up text in dictionary after selection": "Seçimden sonra metni sözlükte ara", + "Look up text in Wikipedia after selection": "Seçimden sonra metni Vikipedya'da ara", + "Translate text after selection": "Seçimden sonra metni çevir", + "Read text aloud after selection": "Seçimden sonra metni sesli oku", + "Proofread text after selection": "Seçimden sonra metni düzelt" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index 8d911e37..5711af51 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -804,5 +804,21 @@ "Selected Text Rules": "Правила для виділеного тексту", "No selected text replacement rules": "Немає правил заміни для виділеного тексту", "Book Specific Rules": "Правила для конкретної книги", - "No book-level replacement rules": "Немає правил заміни на рівні книги" + "No book-level replacement rules": "Немає правил заміни на рівні книги", + "Disable Quick Action": "Вимкнути швидку дію", + "Enable Quick Action on Selection": "Увімкнути швидку дію при виборі", + "None": "Жоден", + "Annotation Tools": "Інструменти анотацій", + "Enable Quick Actions": "Увімкнути швидкі дії", + "Quick Action": "Швидка дія", + "Copy to Notebook": "Скопіювати до блокнота", + "Copy text after selection": "Скопіювати текст після виділення", + "Highlight text after selection": "Виділити текст після виділення", + "Annotate text after selection": "Додати анотацію до тексту після виділення", + "Search text after selection": "Шукати текст після виділення", + "Look up text in dictionary after selection": "Шукати текст у словнику після виділення", + "Look up text in Wikipedia after selection": "Шукати текст у Вікіпедії після виділення", + "Translate text after selection": "Перекласти текст після виділення", + "Read text aloud after selection": "Прочитати текст вголос після виділення", + "Proofread text after selection": "Вичитати текст після виділення" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index 388b3dbf..9aa0f127 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -774,5 +774,21 @@ "Selected Text Rules": "Quy tắc cho văn bản đã chọn", "No selected text replacement rules": "Không có quy tắc thay thế cho văn bản đã chọn", "Book Specific Rules": "Quy tắc dành riêng cho sách", - "No book-level replacement rules": "Không có quy tắc thay thế ở cấp sách" + "No book-level replacement rules": "Không có quy tắc thay thế ở cấp sách", + "Disable Quick Action": "Vô hiệu hóa Hành động Nhanh", + "Enable Quick Action on Selection": "Bật Hành động Nhanh khi chọn", + "None": "Không có", + "Annotation Tools": "Công cụ chú thích", + "Enable Quick Actions": "Bật Hành động Nhanh", + "Quick Action": "Hành động Nhanh", + "Copy to Notebook": "Sao chép vào Sổ tay", + "Copy text after selection": "Sao chép văn bản sau khi chọn", + "Highlight text after selection": "Đánh dấu văn bản sau khi chọn", + "Annotate text after selection": "Chú thích văn bản sau khi chọn", + "Search text after selection": "Tìm kiếm văn bản sau khi chọn", + "Look up text in dictionary after selection": "Tra cứu văn bản trong từ điển sau khi chọn", + "Look up text in Wikipedia after selection": "Tra cứu văn bản trong Wikipedia sau khi chọn", + "Translate text after selection": "Dịch văn bản sau khi chọn", + "Read text aloud after selection": "Đọc to văn bản sau khi chọn", + "Proofread text after selection": "Hiệu đính văn bản sau khi chọn" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index fc24355f..fa334c05 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -774,5 +774,21 @@ "Selected Text Rules": "选中文本规则", "No selected text replacement rules": "暂无选中文本的替换规则", "Book Specific Rules": "书籍范围规则", - "No book-level replacement rules": "暂无书籍级别的替换规则" + "No book-level replacement rules": "暂无书籍级别的替换规则", + "Disable Quick Action": "禁用快速操作", + "Enable Quick Action on Selection": "在选择时启用快速操作", + "None": "无", + "Annotation Tools": "注释工具", + "Enable Quick Actions": "启用快捷操作", + "Quick Action": "快捷操作", + "Copy to Notebook": "复制到笔记本", + "Copy text after selection": "复制选中文本", + "Highlight text after selection": "划线选中文本", + "Annotate text after selection": "注释选中文本", + "Search text after selection": "搜索选中文本", + "Look up text in dictionary after selection": "在字典中查找选中文本", + "Look up text in Wikipedia after selection": "在维基百科中查找选中文本", + "Translate text after selection": "翻译选中文本", + "Read text aloud after selection": "朗读选中文本", + "Proofread text after selection": "校对选中文本" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index 69a91d97..7a34a358 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -774,5 +774,21 @@ "Selected Text Rules": "已選取文字規則", "No selected text replacement rules": "沒有已選取文字的取代規則", "Book Specific Rules": "書籍層級規則", - "No book-level replacement rules": "沒有書籍層級的取代規則" + "No book-level replacement rules": "沒有書籍層級的取代規則", + "Disable Quick Action": "停用快速操作", + "Enable Quick Action on Selection": "選取時啟用快速操作", + "None": "無", + "Annotation Tools": "註解工具", + "Enable Quick Actions": "啟用快速操作", + "Quick Action": "快速操作", + "Copy to Notebook": "複製到筆記本", + "Copy text after selection": "複製選取後的文字", + "Highlight text after selection": "劃線選取後的文字", + "Annotate text after selection": "註解選取後的文字", + "Search text after selection": "搜尋選取後的文字", + "Look up text in dictionary after selection": "在字典中查找選取後的文字", + "Look up text in Wikipedia after selection": "在維基百科中查找選取後的文字", + "Translate text after selection": "翻譯選取後的文字", + "Read text aloud after selection": "朗讀選取後的文字", + "Proofread text after selection": "校對選取後的文字" } diff --git a/apps/readest-app/src-tauri/gen/android/app/src/main/java/com/bilingify/readest/MainActivity.kt b/apps/readest-app/src-tauri/gen/android/app/src/main/java/com/bilingify/readest/MainActivity.kt index 3275a7af..ebf4f657 100644 --- a/apps/readest-app/src-tauri/gen/android/app/src/main/java/com/bilingify/readest/MainActivity.kt +++ b/apps/readest-app/src-tauri/gen/android/app/src/main/java/com/bilingify/readest/MainActivity.kt @@ -3,6 +3,7 @@ package com.bilingify.readest import android.os.Build import android.os.Bundle import android.view.KeyEvent +import android.view.MotionEvent import android.webkit.WebView import android.net.Uri import android.util.Log @@ -47,6 +48,48 @@ class MainActivity : TauriActivity(), KeyDownInterceptor { interceptBackKeyEnabled = enabled } + override fun dispatchTouchEvent(event: MotionEvent): Boolean { + val action = when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> "touchstart" + MotionEvent.ACTION_UP -> "touchend" + MotionEvent.ACTION_CANCEL -> "touchcancel" + MotionEvent.ACTION_POINTER_DOWN -> "touchstart" + MotionEvent.ACTION_POINTER_UP -> "touchend" + else -> null + } + + action?.let { eventType -> + val pointerIndex = event.actionIndex + val pointerId = event.getPointerId(pointerIndex) + val x = event.getX(pointerIndex) + val y = event.getY(pointerIndex) + val pressure = event.getPressure(pointerIndex) + + wv.evaluateJavascript( + """ + try { + if (window.onNativeTouch) { + window.onNativeTouch({ + type: "$eventType", + pointerId: $pointerId, + x: $x, + y: $y, + pressure: $pressure, + pointerCount: ${event.pointerCount}, + timestamp: ${event.eventTime} + }); + } + } catch (err) { + console.error('Native touch error:', err); + } + """.trimIndent(), + null + ) + } + + return super.dispatchTouchEvent(event) + } + override fun dispatchKeyEvent(event: KeyEvent): Boolean { if (event.action == KeyEvent.ACTION_DOWN) { val keyCode = event.keyCode diff --git a/apps/readest-app/src/app/reader/components/HeaderBar.tsx b/apps/readest-app/src/app/reader/components/HeaderBar.tsx index c05c2f3c..8a28aa41 100644 --- a/apps/readest-app/src/app/reader/components/HeaderBar.tsx +++ b/apps/readest-app/src/app/reader/components/HeaderBar.tsx @@ -8,11 +8,18 @@ import { useThemeStore } from '@/store/themeStore'; import { useReaderStore } from '@/store/readerStore'; import { useSidebarStore } from '@/store/sidebarStore'; import { useTranslation } from '@/hooks/useTranslation'; +import { useSettingsStore } from '@/store/settingsStore'; import { useTrafficLightStore } from '@/store/trafficLightStore'; import { useTrafficLight } from '@/hooks/useTrafficLight'; import { useResponsiveSize } from '@/hooks/useResponsiveSize'; -import WindowButtons from '@/components/WindowButtons'; +import { getHighlightColorHex } from '../utils/annotatorUtil'; +import { annotationToolQuickActions } from './annotator/AnnotationTools'; +import { AnnotationToolType } from '@/types/annotator'; +import { saveViewSettings } from '@/helpers/settings'; +import { HighlighterIcon } from '@/components/HighlighterIcon'; import Dropdown from '@/components/Dropdown'; +import WindowButtons from '@/components/WindowButtons'; +import QuickActionMenu from './annotator/QuickActionMenu'; import SidebarToggler from './SidebarToggler'; import BookmarkToggler from './BookmarkToggler'; import NotebookToggler from './NotebookToggler'; @@ -38,12 +45,15 @@ const HeaderBar: React.FC = ({ onCloseBook, }) => { const _ = useTranslation(); - const { appService } = useEnv(); + const { envConfig, appService } = useEnv(); + const { settings } = useSettingsStore(); const { isTrafficLightVisible } = useTrafficLight(); const { trafficLightInFullscreen, setTrafficLightVisibility } = useTrafficLightStore(); - const { bookKeys, hoveredBookKey, getView, setHoveredBookKey } = useReaderStore(); - const { systemUIVisible, statusBarHeight } = useThemeStore(); + const { bookKeys, hoveredBookKey } = useReaderStore(); + const { isDarkMode, systemUIVisible, statusBarHeight } = useThemeStore(); const { isSideBarVisible } = useSidebarStore(); + const { getView, getViewSettings, setHoveredBookKey } = useReaderStore(); + const viewSettings = getViewSettings(bookKey); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const view = getView(bookKey); @@ -54,11 +64,27 @@ const HeaderBar: React.FC = ({ const docs = view?.renderer.getContents() ?? []; const pointerInDoc = docs.some(({ doc }) => doc.body.style.cursor === 'pointer'); + const enableAnnotationQuickActions = viewSettings?.enableAnnotationQuickActions; + const annotationQuickActionButton = + annotationToolQuickActions.find( + (button) => button.type === viewSettings?.annotationQuickAction, + ) || annotationToolQuickActions[0]!; + const annotationQuickAction = viewSettings?.annotationQuickAction; + const AnnotationToolQuickActionIcon = annotationQuickActionButton.Icon; + const highlightStyle = settings.globalReadSettings.highlightStyle; + const highlightColor = settings.globalReadSettings.highlightStyles[highlightStyle]; + const highlightHexColor = getHighlightColorHex(settings, highlightColor); + const handleToggleDropdown = (isOpen: boolean) => { setIsDropdownOpen(isOpen); if (!isOpen) setHoveredBookKey(''); }; + const handleAnnotationQuickActionSelect = (action: AnnotationToolType | null) => { + if (viewSettings?.annotationQuickAction === action) action = null; + saveViewSettings(envConfig, bookKey, 'annotationQuickAction', action, false, true); + }; + useEffect(() => { if (!appService?.hasTrafficLight) return; if (isSideBarVisible) return; @@ -141,6 +167,40 @@ const HeaderBar: React.FC = ({ + {enableAnnotationQuickActions && ( + + ) : ( + + ) + } + onToggle={handleToggleDropdown} + > + + + )}
= ({ {buttons.map((button, index) => { if (button.visible === false) return null; return ( - void; } -const PopupButton: React.FC = ({ +const AnnotationToolButton: React.FC = ({ showTooltip, tooltipText, disabled, @@ -40,4 +40,4 @@ const PopupButton: React.FC = ({ ); }; -export default PopupButton; +export default AnnotationToolButton; diff --git a/apps/readest-app/src/app/reader/components/annotator/AnnotationTools.tsx b/apps/readest-app/src/app/reader/components/annotator/AnnotationTools.tsx new file mode 100644 index 00000000..f48c7f8d --- /dev/null +++ b/apps/readest-app/src/app/reader/components/annotator/AnnotationTools.tsx @@ -0,0 +1,92 @@ +import { IconType } from 'react-icons'; +import { FiSearch } from 'react-icons/fi'; +import { FiCopy } from 'react-icons/fi'; +import { PiHighlighterFill } from 'react-icons/pi'; +import { FaWikipediaW } from 'react-icons/fa'; +import { BsPencilSquare } from 'react-icons/bs'; +import { BsTranslate } from 'react-icons/bs'; +import { TbHexagonLetterD } from 'react-icons/tb'; +import { FaHeadphones } from 'react-icons/fa6'; +import { IoIosBuild } from 'react-icons/io'; +import { AnnotationToolType } from '@/types/annotator'; +import { stubTranslation as _ } from '@/utils/misc'; + +type AnnotationToolButton = { + type: AnnotationToolType; + label: string; + tooltip: string; + Icon: IconType; + quickAction?: boolean; +}; + +function createAnnotationToolButtons( + buttons: AnnotationToolType extends T + ? { + [K in T]: { + type: K; + label: string; + tooltip: string; + Icon: IconType; + quickAction?: boolean; + }; + }[T][] + : never, +): AnnotationToolButton[] { + return buttons; +} + +export const annotationToolButtons = createAnnotationToolButtons([ + { type: 'copy', label: _('Copy'), tooltip: _('Copy text after selection'), Icon: FiCopy }, + { + type: 'highlight', + label: _('Highlight'), + tooltip: _('Highlight text after selection'), + Icon: PiHighlighterFill, + quickAction: true, + }, + { + type: 'annotate', + label: _('Annotate'), + tooltip: _('Annotate text after selection'), + Icon: BsPencilSquare, + }, + { type: 'search', label: _('Search'), tooltip: _('Search text after selection'), Icon: FiSearch }, + { + type: 'dictionary', + label: _('Dictionary'), + tooltip: _('Look up text in dictionary after selection'), + Icon: TbHexagonLetterD, + quickAction: true, + }, + { + type: 'wikipedia', + label: _('Wikipedia'), + tooltip: _('Look up text in Wikipedia after selection'), + Icon: FaWikipediaW, + quickAction: true, + }, + { + type: 'translate', + label: _('Translate'), + tooltip: _('Translate text after selection'), + Icon: BsTranslate, + quickAction: true, + }, + { + type: 'tts', + label: _('Speak'), + tooltip: _('Read text aloud after selection'), + Icon: FaHeadphones, + quickAction: true, + }, + { + type: 'proofread', + label: _('Proofread'), + tooltip: _('Proofread text after selection'), + Icon: IoIosBuild, + }, +]); + +export const annotationToolQuickActions = annotationToolButtons.filter( + (button) => button.quickAction, +); diff --git a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx index 460e7a6f..9adfdaf2 100644 --- a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx +++ b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx @@ -1,20 +1,12 @@ import React, { useState, useEffect, useCallback } from 'react'; -import { FiSearch } from 'react-icons/fi'; -import { FiCopy } from 'react-icons/fi'; -import { PiHighlighterFill } from 'react-icons/pi'; -import { FaWikipediaW } from 'react-icons/fa'; -import { BsPencilSquare } from 'react-icons/bs'; import { RiDeleteBinLine } from 'react-icons/ri'; -import { BsTranslate } from 'react-icons/bs'; -import { TbHexagonLetterD } from 'react-icons/tb'; -import { FaHeadphones } from 'react-icons/fa6'; -import { IoIosBuild } from 'react-icons/io'; import * as CFI from 'foliate-js/epubcfi.js'; import { Overlayer } from 'foliate-js/overlayer.js'; import { useEnv } from '@/context/EnvContext'; import { BookNote, BooknoteGroup, HighlightColor, HighlightStyle } from '@/types/book'; import { NOTE_PREFIX } from '@/types/view'; +import { NativeTouchEventType } from '@/types/system'; import { getOSPlatform, uniqueId } from '@/utils/misc'; import { useBookDataStore } from '@/store/bookDataStore'; import { useSettingsStore } from '@/store/settingsStore'; @@ -22,6 +14,7 @@ import { useReaderStore } from '@/store/readerStore'; import { useNotebookStore } from '@/store/notebookStore'; import { useTranslation } from '@/hooks/useTranslation'; import { useResponsiveSize } from '@/hooks/useResponsiveSize'; +import { useDeviceControlStore } from '@/store/deviceStore'; import { useFoliateEvents } from '../../hooks/useFoliateEvents'; import { useNotesSync } from '../../hooks/useNotesSync'; import { useTextSelector } from '../../hooks/useTextSelector'; @@ -30,8 +23,9 @@ import { eventDispatcher } from '@/utils/event'; import { findTocItemBS } from '@/utils/toc'; import { throttle } from '@/utils/throttle'; import { runSimpleCC } from '@/utils/simplecc'; -import { HIGHLIGHT_COLOR_HEX } from '@/services/constants'; import { getWordCount } from '@/utils/word'; +import { getHighlightColorHex } from '../../utils/annotatorUtil'; +import { annotationToolButtons } from './AnnotationTools'; import AnnotationPopup from './AnnotationPopup'; import WiktionaryPopup from './WiktionaryPopup'; import WikipediaPopup from './WikipediaPopup'; @@ -46,6 +40,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { const { getConfig, saveConfig, getBookData, updateBooknotes } = useBookDataStore(); const { getProgress, getView, getViewsById, getViewSettings } = useReaderStore(); const { setNotebookVisible, setNotebookNewAnnotation } = useNotebookStore(); + const { listenToNativeTouchEvents } = useDeviceControlStore(); useNotesSync(bookKey); @@ -163,12 +158,8 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { [], ); - const handleDismissPopupAndSelection = () => { - handleDismissPopup(); - view?.deselect(); - }; - const { + isTextSelected, handleScroll, handleTouchStart, handleTouchEnd, @@ -180,6 +171,12 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { handleContextmenu, } = useTextSelector(bookKey, setSelection, handleDismissPopup); + const handleDismissPopupAndSelection = () => { + handleDismissPopup(); + view?.deselect(); + isTextSelected.current = false; + }; + const onLoad = (event: Event) => { const detail = (event as CustomEvent).detail; const { doc, index } = detail; @@ -190,6 +187,21 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { setShowAnnotPopup(false); }; + const handleNativeTouch = (event: CustomEvent) => { + const ev = event.detail as NativeTouchEventType; + if (ev.type === 'touchstart') { + handleTouchStart(); + } else if (ev.type === 'touchend') { + handleTouchEnd(); + handlePointerup(doc, index); + } + }; + + if (appService?.isAndroidApp) { + listenToNativeTouchEvents(); + eventDispatcher.on('native-touch', handleNativeTouch); + } + // Attach generic selection listeners for all formats, including PDF. // For PDF we only guarantee Copy & Translate; highlight/annotate may be limited by CFI support. view?.renderer?.addEventListener('scroll', handleScroll); @@ -204,7 +216,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { detail.doc?.addEventListener('pointerup', (ev: PointerEvent) => handlePointerup(doc, index, ev), ); - detail.doc?.addEventListener('selectionchange', () => handleSelectionchange(doc, index)); + detail.doc?.addEventListener('selectionchange', () => handleSelectionchange(doc)); // For PDF selections, enable right-click context menu to directly open translator popup. if (bookData.book?.format === 'PDF') { @@ -242,9 +254,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { const detail = (event as CustomEvent).detail; const { draw, annotation, doc, range } = detail; const { style, color } = annotation as BookNote; - const customColors = settings.globalReadSettings.customHighlightColors; - const hexColor = - color && customColors ? customColors[color] : color ? HIGHLIGHT_COLOR_HEX[color] : color; + const hexColor = getHighlightColorHex(settings, color); if (annotation.note) { const { defaultView } = doc; const node = range.startContainer; @@ -361,6 +371,32 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const handleQuickAction = () => { + const action = viewSettings.annotationQuickAction; + switch (action) { + case 'copy': + handleCopy(false); + handleDismissPopupAndSelection(); + break; + case 'highlight': + handleHighlight(); + handleDismissPopupAndSelection(); + break; + case 'dictionary': + handleDictionary(); + break; + case 'wikipedia': + handleWikipedia(); + break; + case 'translate': + handleTranslation(); + break; + case 'tts': + handleSpeakText(true); + break; + } + }; + useEffect(() => { setHighlightOptionsVisible(!!(selection && selection.annotated)); if (selection && selection.text.trim().length > 0) { @@ -406,10 +442,16 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { setTranslatorPopupPosition(transPopupPos); setProofreadPopupPosition(proofreadPopupPos); setTrianglePosition(triangPos); - handleShowAnnotPopup(); + + const { enableAnnotationQuickActions, annotationQuickAction } = viewSettings; + if (enableAnnotationQuickActions && annotationQuickAction && isTextSelected.current) { + handleQuickAction(); + } else { + handleShowAnnotPopup(); + } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selection, bookKey]); + }, [selection?.cfi, bookKey]); useEffect(() => { if (!progress) return; @@ -465,7 +507,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { setShowWikipediaPopup(false); }; - const handleCopy = (copyToNotebook = true, dismissPopup = true) => { + const handleCopy = (dismissPopup = true) => { if (!selection || !selection.text) return; setTimeout(() => { // Delay to ensure it won't be overridden by system clipboard actions @@ -475,7 +517,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { handleDismissPopupAndSelection(); } - if (!copyToNotebook) return; + if (!viewSettings?.copyToNotebook) return; eventDispatcher.dispatch('toast', { type: 'info', @@ -604,13 +646,13 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { setShowDeepLPopup(true); }; - const handleSpeakText = async () => { + const handleSpeakText = async (oneTime = false) => { if (!selection || !selection.text) return; setShowAnnotPopup(false); - eventDispatcher.dispatch('tts-speak', { bookKey, range: selection.range }); + eventDispatcher.dispatch('tts-speak', { bookKey, range: selection.range, oneTime }); }; - const handleShowProofreadOptions = () => { + const handleProofread = () => { if (!selection || !selection.text) return; setShowAnnotPopup(false); setShowProofreadPopup(true); @@ -641,7 +683,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { handleSearch(); }, onCopySelection: () => { - handleCopy(false, false); + handleCopy(false); }, onTranslateSelection: () => { handleTranslation(); @@ -655,6 +697,9 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { onReadAloudSelection: () => { handleSpeakText(); }, + onProofreadSelection: () => { + handleProofread(); + }, }, [selection?.text], ); @@ -745,42 +790,55 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { }; const selectionAnnotated = selection?.annotated; - const buttons = [ - { tooltipText: _('Copy'), Icon: FiCopy, onClick: handleCopy }, - { - tooltipText: selectionAnnotated ? _('Delete Highlight') : _('Highlight'), - Icon: selectionAnnotated ? RiDeleteBinLine : PiHighlighterFill, - onClick: handleHighlight, - disabled: bookData.book?.format === 'PDF', - }, - { - tooltipText: _('Annotate'), - Icon: BsPencilSquare, - onClick: handleAnnotate, - disabled: bookData.book?.format === 'PDF', - }, - { - tooltipText: _('Search'), - Icon: FiSearch, - onClick: handleSearch, - disabled: bookData.book?.format === 'PDF', - }, - { tooltipText: _('Dictionary'), Icon: TbHexagonLetterD, onClick: handleDictionary }, - { tooltipText: _('Wikipedia'), Icon: FaWikipediaW, onClick: handleWikipedia }, - { tooltipText: _('Translate'), Icon: BsTranslate, onClick: handleTranslation }, - { - tooltipText: _('Speak'), - Icon: FaHeadphones, - onClick: handleSpeakText, - disabled: bookData.book?.format === 'PDF', - }, - { - tooltipText: _('Proofread'), - Icon: IoIosBuild, - onClick: handleShowProofreadOptions, - disabled: bookData.book?.format !== 'EPUB', - }, - ]; + const toolButtons = annotationToolButtons.map(({ type, label, Icon }) => { + switch (type) { + case 'copy': + return { tooltipText: _(label), Icon, onClick: handleCopy }; + case 'highlight': + return { + tooltipText: selectionAnnotated ? _('Delete Highlight') : _(label), + Icon: selectionAnnotated ? RiDeleteBinLine : Icon, + onClick: handleHighlight, + disabled: bookData.book?.format === 'PDF', + }; + case 'annotate': + return { + tooltipText: _(label), + Icon, + onClick: handleAnnotate, + disabled: bookData.book?.format === 'PDF', + }; + case 'search': + return { + tooltipText: _(label), + Icon, + onClick: handleSearch, + disabled: bookData.book?.format === 'PDF', + }; + case 'dictionary': + return { tooltipText: _(label), Icon, onClick: handleDictionary }; + case 'wikipedia': + return { tooltipText: _(label), Icon, onClick: handleWikipedia }; + case 'translate': + return { tooltipText: _(label), Icon, onClick: handleTranslation }; + case 'tts': + return { + tooltipText: _(label), + Icon, + onClick: handleSpeakText, + disabled: bookData.book?.format === 'PDF', + }; + case 'proofread': + return { + tooltipText: _(label), + Icon, + onClick: handleProofread, + disabled: bookData.book?.format !== 'EPUB', + }; + default: + return { tooltipText: '', Icon, onClick: () => {} }; + } + }); return (
@@ -821,7 +879,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { bookKey={bookKey} dir={viewSettings.rtl ? 'rtl' : 'ltr'} isVertical={viewSettings.vertical} - buttons={buttons} + buttons={toolButtons} notes={annotationNotes} position={annotPopupPosition} trianglePosition={trianglePosition} diff --git a/apps/readest-app/src/app/reader/components/annotator/QuickActionMenu.tsx b/apps/readest-app/src/app/reader/components/annotator/QuickActionMenu.tsx new file mode 100644 index 00000000..4b0d7de4 --- /dev/null +++ b/apps/readest-app/src/app/reader/components/annotator/QuickActionMenu.tsx @@ -0,0 +1,52 @@ +import clsx from 'clsx'; +import React from 'react'; +import { MdCheck } from 'react-icons/md'; + +import { AnnotationToolType } from '@/types/annotator'; +import { useTranslation } from '@/hooks/useTranslation'; +import { annotationToolQuickActions } from './AnnotationTools'; +import MenuItem from '@/components/MenuItem'; +import Menu from '@/components/Menu'; + +interface QuickActionMenuProps { + selectedAction?: AnnotationToolType | null; + onActionSelect: (action: AnnotationToolType) => void; + setIsDropdownOpen?: (open: boolean) => void; +} + +const QuickActionMenu: React.FC = ({ + selectedAction, + onActionSelect, + setIsDropdownOpen, +}) => { + const _ = useTranslation(); + + const handleActionClick = (action: AnnotationToolType) => { + onActionSelect(action); + }; + + return ( + setIsDropdownOpen?.(false)} + > + {annotationToolQuickActions.map((button) => ( + handleActionClick(button.type)} + /> + ))} + + ); +}; + +export default QuickActionMenu; diff --git a/apps/readest-app/src/app/reader/components/tts/TTSControl.tsx b/apps/readest-app/src/app/reader/components/tts/TTSControl.tsx index 75e72228..63441afe 100644 --- a/apps/readest-app/src/app/reader/components/tts/TTSControl.tsx +++ b/apps/readest-app/src/app/reader/components/tts/TTSControl.tsx @@ -286,7 +286,7 @@ const TTSControl: React.FC = ({ bookKey, gridInsets }) => { }, [getTTSTargetLang]); const handleTTSSpeak = async (event: CustomEvent) => { - const { bookKey: ttsBookKey, range } = event.detail; + const { bookKey: ttsBookKey, range, oneTime = false } = event.detail; if (bookKey !== ttsBookKey) return; const view = getView(bookKey); @@ -337,7 +337,9 @@ const TTSControl: React.FC = ({ bookKey, gridInsets }) => { await initMediaSession(); setTtsClientsInitialized(false); - setShowIndicator(true); + if (!oneTime) { + setShowIndicator(true); + } const ttsController = new TTSController(appService, view, !!user?.id); ttsControllerRef.current = ttsController; setTtsController(ttsController); @@ -352,7 +354,7 @@ const TTSControl: React.FC = ({ bookKey, gridInsets }) => { ttsController.setLang(lang); ttsController.setRate(viewSettings.ttsRate); - ttsController.speak(ssml); + ttsController.speak(ssml, oneTime); ttsController.setTargetLang(getTTSTargetLang() || ''); } setTtsClientsInitialized(true); diff --git a/apps/readest-app/src/app/reader/hooks/useTextSelector.ts b/apps/readest-app/src/app/reader/hooks/useTextSelector.ts index bdac6fa1..557ea1e4 100644 --- a/apps/readest-app/src/app/reader/hooks/useTextSelector.ts +++ b/apps/readest-app/src/app/reader/hooks/useTextSelector.ts @@ -81,7 +81,7 @@ export const useTextSelector = ( }, 30); }, 30); }; - const handleSelectionchange = (doc: Document, index: number) => { + const handleSelectionchange = (doc: Document) => { // Available on iOS, Android and Desktop, fired when the selection is changed // Ideally the popup only shows when the selection is done, const sel = doc.getSelection() as Selection; @@ -98,12 +98,6 @@ export const useTextSelector = ( return; } - // On Android no proper events are fired to notify selection done, - // we make the popup show when the selection is changed - // note that selection may be initiated by a tts speak - if (isTouchStarted.current && osPlatform === 'android') { - makeSelection(sel, index, false); - } isUpToPopup.current = true; }; const isPointerInsideSelection = (selection: Selection, ev: PointerEvent) => { @@ -127,14 +121,18 @@ export const useTextSelector = ( const handlePointerdown = (e: PointerEvent) => { lastPointerType.current = e.pointerType; }; - const handlePointerup = (doc: Document, index: number, ev: PointerEvent) => { + const handlePointerup = (doc: Document, index: number, ev?: PointerEvent) => { // Available on iOS and Desktop, fired at touchend or mouseup - // Note that on Android, pointerup event is fired after an additional touch event + // Note that on Android, we mock pointer events with native touch events const sel = doc.getSelection() as Selection; - if (isValidSelection(sel) && isPointerInsideSelection(sel, ev)) { - if (osPlatform === 'ios' || appService?.isIOSApp) { + if (isValidSelection(sel)) { + const isPointerInside = ev && isPointerInsideSelection(sel, ev); + const isIOS = osPlatform === 'ios' || appService?.isIOSApp; + const isAndroid = appService?.isAndroidApp; + + if (isPointerInside && isIOS) { makeSelectionOnIOS(sel, index); - } else { + } else if (isPointerInside || isAndroid) { makeSelection(sel, index, true); } } @@ -228,6 +226,7 @@ export const useTextSelector = ( }, []); return { + isTextSelected, handleScroll, handleTouchStart, handleTouchEnd, diff --git a/apps/readest-app/src/app/reader/utils/annotatorUtil.ts b/apps/readest-app/src/app/reader/utils/annotatorUtil.ts new file mode 100644 index 00000000..5df2d248 --- /dev/null +++ b/apps/readest-app/src/app/reader/utils/annotatorUtil.ts @@ -0,0 +1,10 @@ +import { HIGHLIGHT_COLOR_HEX } from '@/services/constants'; +import { HighlightColor } from '@/types/book'; +import { SystemSettings } from '@/types/settings'; + +export const getHighlightColorHex = (settings: SystemSettings, color?: HighlightColor) => { + if (!color) return color; + + const customColors = settings.globalReadSettings.customHighlightColors; + return customColors?.[color] ?? HIGHLIGHT_COLOR_HEX[color]; +}; diff --git a/apps/readest-app/src/components/HighlighterIcon.tsx b/apps/readest-app/src/components/HighlighterIcon.tsx new file mode 100644 index 00000000..90a029c9 --- /dev/null +++ b/apps/readest-app/src/components/HighlighterIcon.tsx @@ -0,0 +1,35 @@ +import { IconBaseProps } from 'react-icons'; +import { GenIcon } from 'react-icons/lib'; + +export function HighlighterIcon({ + tipColor = '#FFD700', + tipStyle = {}, + ...props +}: IconBaseProps & { tipColor?: string; tipStyle?: React.CSSProperties }) { + return GenIcon({ + tag: 'svg', + attr: { + viewBox: '0 0 256 256', + fill: 'none', + }, + child: [ + { + tag: 'path', + attr: { + d: 'M253.66,106.34a8,8,0,0,0-11.32,0L192,156.69,107.31,72l50.35-50.34a8,8,0,1,0-11.32-11.32L96,60.69A16,16,0,0,0,93.18,79.5L72,100.69a16,16,0,0,0,0,22.62L76.69,128,136,187.31l4.69,4.69a16,16,0,0,0,22.62,0l21.18-21.18A16,16,0,0,0,203.31,168l50.35-50.34A8,8,0,0,0,253.66,106.34ZM152,180.69,83.31,112,104,91.31,172.69,160Z', + fill: 'currentColor', + }, + child: [], + }, + { + tag: 'path', + attr: { + d: 'M18.34,186.34c-4.209212,4.20621-2.516471,11.37196,3.13,13.25l72,24c0.815308,0.27382,1.669943,0.41232,2.53,0.41c2.12237,0.002,4.15842-0.84009,5.66-2.34L136,187.31c13.62143,13.61626-70.243446-70.24754-64-64l4.69,4.69z', + fill: tipColor, + style: tipStyle as unknown as string, + }, + child: [], + }, + ], + })(props); +} diff --git a/apps/readest-app/src/components/settings/ControlPanel.tsx b/apps/readest-app/src/components/settings/ControlPanel.tsx index 257507e2..6030566a 100644 --- a/apps/readest-app/src/components/settings/ControlPanel.tsx +++ b/apps/readest-app/src/components/settings/ControlPanel.tsx @@ -11,7 +11,9 @@ import { getStyles } from '@/utils/style'; import { getMaxInlineSize } from '@/utils/config'; import { saveSysSettings, saveViewSettings } from '@/helpers/settings'; import { SettingsPanelPanelProp } from './SettingsDialog'; +import { annotationToolQuickActions } from '@/app/reader/components/annotator/AnnotationTools'; import NumberInput from './NumberInput'; +import Select from '../Select'; const ControlPanel: React.FC = ({ bookKey, onRegisterReset }) => { const _ = useTranslation(); @@ -32,6 +34,13 @@ const ControlPanel: React.FC = ({ bookKey, onRegisterRes const [fullscreenClickArea, setFullscreenClickArea] = useState(viewSettings.fullscreenClickArea); const [swapClickArea, setSwapClickArea] = useState(viewSettings.swapClickArea); const [isDisableDoubleClick, setIsDisableDoubleClick] = useState(viewSettings.disableDoubleClick); + const [enableAnnotationQuickActions, setEnableAnnotationQuickActions] = useState( + viewSettings.enableAnnotationQuickActions, + ); + const [annotationQuickAction, setAnnotationQuickAction] = useState( + viewSettings.annotationQuickAction, + ); + const [copyToNotebook, setCopyToNotebook] = useState(viewSettings.copyToNotebook); const [animated, setAnimated] = useState(viewSettings.animated); const [isEink, setIsEink] = useState(viewSettings.isEink); const [autoScreenBrightness, setAutoScreenBrightness] = useState(settings.autoScreenBrightness); @@ -50,6 +59,10 @@ const ControlPanel: React.FC = ({ bookKey, onRegisterRes animated: setAnimated, isEink: setIsEink, allowScript: setAllowScript, + fullscreenClickArea: setFullscreenClickArea, + disableDoubleClick: setIsDisableDoubleClick, + enableAnnotationQuickActions: setEnableAnnotationQuickActions, + copyToNotebook: setCopyToNotebook, }); }; @@ -148,6 +161,42 @@ const ControlPanel: React.FC = ({ bookKey, onRegisterRes // eslint-disable-next-line react-hooks/exhaustive-deps }, [allowScript]); + useEffect(() => { + saveViewSettings( + envConfig, + bookKey, + 'enableAnnotationQuickActions', + enableAnnotationQuickActions, + false, + false, + ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [enableAnnotationQuickActions]); + + useEffect(() => { + saveViewSettings(envConfig, bookKey, 'copyToNotebook', copyToNotebook, false, false); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [copyToNotebook]); + + const getQuickActionOptions = () => { + return [ + { + value: '', + label: _('None'), + }, + ...annotationToolQuickActions.map((button) => ({ + value: button.type, + label: button.label, + })), + ]; + }; + + const handleSelectAnnotationQuickAction = (event: React.ChangeEvent) => { + const action = event.target.value as typeof annotationQuickAction; + setAnnotationQuickAction(action); + saveViewSettings(envConfig, bookKey, 'annotationQuickAction', action, false, true); + }; + return (
@@ -250,6 +299,41 @@ const ControlPanel: React.FC = ({ bookKey, onRegisterRes
+
+

{_('Annotation Tools')}

+
+
+
+ {_('Enable Quick Actions')} + setEnableAnnotationQuickActions(!enableAnnotationQuickActions)} + /> +
+
+ {_('Quick Action')} + setCopyToNotebook(!copyToNotebook)} + /> +
+
+
+
+

{_('Animation')}

diff --git a/apps/readest-app/src/helpers/shortcuts.ts b/apps/readest-app/src/helpers/shortcuts.ts index 2b547df7..16698777 100644 --- a/apps/readest-app/src/helpers/shortcuts.ts +++ b/apps/readest-app/src/helpers/shortcuts.ts @@ -16,6 +16,7 @@ const DEFAULT_SHORTCUTS = { onDictionarySelection: ['ctrl+d', 'cmd+d'], onWikipediaSelection: ['ctrl+w', 'cmd+w'], onReadAloudSelection: ['ctrl+r', 'cmd+r'], + onProofreadSelection: ['ctrl+p', 'cmd+p'], onOpenFontLayoutSettings: ['shift+f', 'ctrl+,', 'cmd+,'], onOpenBooks: ['ctrl+o'], onReloadPage: ['shift+r'], diff --git a/apps/readest-app/src/services/appService.ts b/apps/readest-app/src/services/appService.ts index 95e6225b..b77c8018 100644 --- a/apps/readest-app/src/services/appService.ts +++ b/apps/readest-app/src/services/appService.ts @@ -54,6 +54,7 @@ import { DEFAULT_FIXED_LAYOUT_VIEW_SETTINGS, SETTINGS_FILENAME, DEFAULT_MOBILE_SYSTEM_SETTINGS, + DEFAULT_ANNOTATOR_CONFIG, } from './constants'; import { fetch as tauriFetch } from '@tauri-apps/plugin-http'; import { getOSPlatform, getTargetLang, isCJKEnv, isContentURI, isValidURL } from '@/utils/misc'; @@ -199,6 +200,7 @@ export abstract class BaseAppService implements AppService { ...DEFAULT_VIEW_CONFIG, ...DEFAULT_TTS_CONFIG, ...DEFAULT_SCREEN_CONFIG, + ...DEFAULT_ANNOTATOR_CONFIG, ...{ ...DEFAULT_TRANSLATOR_CONFIG, translateTargetLang: getTargetLang() }, }; } diff --git a/apps/readest-app/src/services/constants.ts b/apps/readest-app/src/services/constants.ts index 2e12f70f..f9d06400 100644 --- a/apps/readest-app/src/services/constants.ts +++ b/apps/readest-app/src/services/constants.ts @@ -1,4 +1,5 @@ import { + AnnotatorConfig, BookFont, BookLanguage, BookLayout, @@ -245,6 +246,12 @@ export const DEFAULT_TRANSLATOR_CONFIG: TranslatorConfig = { ttsReadAloudText: 'both', }; +export const DEFAULT_ANNOTATOR_CONFIG: AnnotatorConfig = { + enableAnnotationQuickActions: true, + annotationQuickAction: null, + copyToNotebook: false, +}; + export const DEFAULT_SCREEN_CONFIG: ScreenConfig = { screenOrientation: 'auto', }; diff --git a/apps/readest-app/src/services/tts/TTSController.ts b/apps/readest-app/src/services/tts/TTSController.ts index 5edab8c0..0e261a10 100644 --- a/apps/readest-app/src/services/tts/TTSController.ts +++ b/apps/readest-app/src/services/tts/TTSController.ts @@ -155,7 +155,7 @@ export class TTSController extends EventTarget { return ssml; } - async #speak(ssml: string | undefined | Promise) { + async #speak(ssml: string | undefined | Promise, oneTime = false) { await this.stop(); this.#currentSpeakAbortController = new AbortController(); const { signal } = this.#currentSpeakAbortController; @@ -173,7 +173,7 @@ export class TTSController extends EventTarget { if (!ssml) { this.#nossmlCnt++; // FIXME: in case we are at the end of the book, need a better way to handle this - if (this.#nossmlCnt < 10 && this.state === 'playing') { + if (this.#nossmlCnt < 10 && this.state === 'playing' && !oneTime) { resolve(); await this.view.next(); await this.forward(); @@ -185,7 +185,7 @@ export class TTSController extends EventTarget { } const { plainText, marks } = parseSSMLMarks(ssml); - if (!plainText || marks.length === 0) { + if ((!plainText || marks.length === 0) && !oneTime) { resolve(); return await this.forward(); } else { @@ -202,7 +202,7 @@ export class TTSController extends EventTarget { lastCode = code; } - if (lastCode === 'end' && this.state === 'playing') { + if (lastCode === 'end' && this.state === 'playing' && !oneTime) { resolve(); await this.forward(); } @@ -224,9 +224,9 @@ export class TTSController extends EventTarget { await this.#currentSpeakPromise.catch((e) => this.error(e)); } - async speak(ssml: string | Promise) { + async speak(ssml: string | Promise, oneTime = false) { await this.initViewTTS(); - this.#speak(ssml).catch((e) => this.error(e)); + this.#speak(ssml, oneTime).catch((e) => this.error(e)); this.preloadNextSSML(); this.dispatchSpeakMark(); } diff --git a/apps/readest-app/src/store/deviceStore.ts b/apps/readest-app/src/store/deviceStore.ts index 218b6ca7..678aacb0 100644 --- a/apps/readest-app/src/store/deviceStore.ts +++ b/apps/readest-app/src/store/deviceStore.ts @@ -1,10 +1,12 @@ import { create } from 'zustand'; import { interceptKeys, getScreenBrightness, setScreenBrightness } from '@/utils/bridge'; import { eventDispatcher } from '@/utils/event'; +import { NativeTouchEventType } from '@/types/system'; declare global { interface Window { onNativeKeyDown?: (keyName: string) => void; + onNativeTouch?: (event: NativeTouchEventType) => void; } } @@ -29,6 +31,7 @@ type DeviceControlState = { releaseVolumeKeyInterception: () => void; acquireBackKeyInterception: () => void; releaseBackKeyInterception: () => void; + listenToNativeTouchEvents: () => void; }; export const useDeviceControlStore = create((set, get) => ({ @@ -77,6 +80,12 @@ export const useDeviceControlStore = create((set, get) => ({ } }, + listenToNativeTouchEvents: () => { + window.onNativeTouch = (event: NativeTouchEventType) => { + return eventDispatcher.dispatch('native-touch', event); + }; + }, + getScreenBrightness: async () => { const res = await getScreenBrightness(); return res.brightness; diff --git a/apps/readest-app/src/types/annotator.ts b/apps/readest-app/src/types/annotator.ts new file mode 100644 index 00000000..911b68fe --- /dev/null +++ b/apps/readest-app/src/types/annotator.ts @@ -0,0 +1,10 @@ +export type AnnotationToolType = + | 'copy' + | 'highlight' + | 'annotate' + | 'search' + | 'dictionary' + | 'wikipedia' + | 'translate' + | 'tts' + | 'proofread'; diff --git a/apps/readest-app/src/types/book.ts b/apps/readest-app/src/types/book.ts index 77e10246..349ee3ea 100644 --- a/apps/readest-app/src/types/book.ts +++ b/apps/readest-app/src/types/book.ts @@ -1,5 +1,6 @@ import { BookMetadata } from '@/libs/document'; import { TTSHighlightOptions } from '@/services/tts/types'; +import { AnnotationToolType } from './annotator'; export type BookFormat = 'EPUB' | 'PDF' | 'MOBI' | 'AZW' | 'AZW3' | 'CBZ' | 'FB2' | 'FBZ'; export type BookNoteType = 'bookmark' | 'annotation' | 'excerpt'; @@ -206,6 +207,12 @@ export interface TranslatorConfig { ttsReadAloudText: string; } +export interface AnnotatorConfig { + enableAnnotationQuickActions: boolean; + annotationQuickAction: AnnotationToolType | null; + copyToNotebook: boolean; +} + export interface ScreenConfig { screenOrientation: 'auto' | 'portrait' | 'landscape'; } @@ -239,7 +246,8 @@ export interface ViewSettings TTSConfig, TranslatorConfig, ScreenConfig, - ProofreadRulesConfig {} + ProofreadRulesConfig, + AnnotatorConfig {} export interface BookProgress { location: string; diff --git a/apps/readest-app/src/types/system.ts b/apps/readest-app/src/types/system.ts index b85a32f0..2ed9c047 100644 --- a/apps/readest-app/src/types/system.ts +++ b/apps/readest-app/src/types/system.ts @@ -34,6 +34,16 @@ export type FileInfo = { birthtime: Date | null; }; +export type NativeTouchEventType = { + type: 'touchstart' | 'touchcancel' | 'touchend'; + pointerId: number; + x: number; + y: number; + pressure: number; + pointerCount: number; + timestamp: number; +}; + export interface FileSystem { resolvePath(path: string, base: BaseDir): ResolvedPath; getURL(path: string): string;