feat(annotator): support quick actions for text selection, closes #2505 (#2813)

This commit is contained in:
Huang Xin
2025-12-30 10:32:01 +01:00
committed by GitHub
parent 58a5c1625c
commit a42897ec5c
47 changed files with 1036 additions and 122 deletions
@@ -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": "تدقيق النص بعد التحديد"
}
@@ -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": "নির্বাচনের পরে টেক্সট প্রুফরিড করুন"
}
@@ -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": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་ཞིབ་བཤེར་བྱེད་"
}
@@ -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"
}
@@ -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": "Διόρθωση κειμένου μετά την επιλογή"
}
@@ -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"
}
@@ -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": "بازبینی متن پس از انتخاب"
}
@@ -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 laction rapide",
"Enable Quick Action on Selection": "Activer laction rapide lors de la sélection",
"None": "Aucune",
"Annotation Tools": "Outils dannotation",
"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"
}
@@ -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": "चयन के बाद पाठ को प्रूफरीड करें"
}
@@ -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"
}
@@ -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"
}
@@ -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": "選択後のテキストを校正する"
}
@@ -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": "선택 후 텍스트 교정"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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": "Корректировать текст после выделения"
}
@@ -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": "තේරීමෙන් පසු පෙළ සංශෝධනය කරන්න"
}
@@ -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"
}
@@ -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": "தேர்வுக்குப் பிறகு உரையை திருத்துக"
}
@@ -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": "ตรวจทานข้อความหลังการเลือก"
}
@@ -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"
}
@@ -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": "Вичитати текст після виділення"
}
@@ -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"
}
@@ -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": "校对选中文本"
}
@@ -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": "校對選取後的文字"
}
@@ -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
@@ -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<HeaderBarProps> = ({
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<HeaderBarProps> = ({
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<HeaderBarProps> = ({
</div>
<BookmarkToggler bookKey={bookKey} />
<TranslationToggler bookKey={bookKey} />
{enableAnnotationQuickActions && (
<Dropdown
label={
annotationQuickAction
? _('Disable Quick Action')
: _('Enable Quick Action on Selection')
}
className='exclude-title-bar-mousedown dropdown-bottom'
buttonClassName={clsx(
'btn btn-ghost h-8 min-h-8 w-8 p-0',
viewSettings?.annotationQuickAction && 'bg-base-300/50',
)}
toggleButton={
annotationQuickAction === 'highlight' || annotationQuickAction === null ? (
<HighlighterIcon
size={iconSize16}
tipColor={annotationQuickAction === null ? '#8F8F8F' : highlightHexColor}
tipStyle={{
opacity: annotationQuickAction === null ? 0.3 : 0.5,
mixBlendMode: isDarkMode ? 'screen' : 'multiply',
}}
/>
) : (
<AnnotationToolQuickActionIcon size={iconSize16} />
)
}
onToggle={handleToggleDropdown}
>
<QuickActionMenu
selectedAction={viewSettings.annotationQuickAction}
onActionSelect={handleAnnotationQuickActionSelect}
/>
</Dropdown>
)}
</div>
<div
@@ -3,7 +3,7 @@ import React from 'react';
import { Position } from '@/utils/sel';
import { BookNote, HighlightColor, HighlightStyle } from '@/types/book';
import Popup from '@/components/Popup';
import PopupButton from './PopupButton';
import AnnotationToolButton from './AnnotationToolButton';
import AnnotationNotes from './AnnotationNotes';
import HighlightOptions from './HighlightOptions';
@@ -69,7 +69,7 @@ const AnnotationPopup: React.FC<AnnotationPopupProps> = ({
{buttons.map((button, index) => {
if (button.visible === false) return null;
return (
<PopupButton
<AnnotationToolButton
key={index}
showTooltip={!highlightOptionsVisible}
tooltipText={button.tooltipText}
@@ -1,7 +1,7 @@
import clsx from 'clsx';
import React, { useState } from 'react';
interface PopupButtonProps {
interface AnnotationToolButtonProps {
showTooltip: boolean;
tooltipText: string;
disabled?: boolean;
@@ -9,7 +9,7 @@ interface PopupButtonProps {
onClick: () => void;
}
const PopupButton: React.FC<PopupButtonProps> = ({
const AnnotationToolButton: React.FC<AnnotationToolButtonProps> = ({
showTooltip,
tooltipText,
disabled,
@@ -40,4 +40,4 @@ const PopupButton: React.FC<PopupButtonProps> = ({
);
};
export default PopupButton;
export default AnnotationToolButton;
@@ -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<T extends AnnotationToolType>(
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,
);
@@ -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 (
<div ref={containerRef} role='toolbar' tabIndex={-1}>
@@ -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}
@@ -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<QuickActionMenuProps> = ({
selectedAction,
onActionSelect,
setIsDropdownOpen,
}) => {
const _ = useTranslation();
const handleActionClick = (action: AnnotationToolType) => {
onActionSelect(action);
};
return (
<Menu
className={clsx(
'annotation-quick-action-menu dropdown-content z-20 mt-1 border',
'bgcolor-base-200 border-base-200 shadow-2xl',
)}
style={{
maxWidth: `${window.innerWidth - 40}px`,
}}
onCancel={() => setIsDropdownOpen?.(false)}
>
{annotationToolQuickActions.map((button) => (
<MenuItem
key={button.type}
label={_(button.label)}
tooltip={_(button.tooltip)}
Icon={selectedAction === button.type ? MdCheck : button.Icon}
onClick={() => handleActionClick(button.type)}
/>
))}
</Menu>
);
};
export default QuickActionMenu;
@@ -286,7 +286,7 @@ const TTSControl: React.FC<TTSControlProps> = ({ 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<TTSControlProps> = ({ 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<TTSControlProps> = ({ bookKey, gridInsets }) => {
ttsController.setLang(lang);
ttsController.setRate(viewSettings.ttsRate);
ttsController.speak(ssml);
ttsController.speak(ssml, oneTime);
ttsController.setTargetLang(getTTSTargetLang() || '');
}
setTtsClientsInitialized(true);
@@ -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,
@@ -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];
};
@@ -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);
}
@@ -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<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset }) => {
const _ = useTranslation();
@@ -32,6 +34,13 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ 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<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
animated: setAnimated,
isEink: setIsEink,
allowScript: setAllowScript,
fullscreenClickArea: setFullscreenClickArea,
disableDoubleClick: setIsDisableDoubleClick,
enableAnnotationQuickActions: setEnableAnnotationQuickActions,
copyToNotebook: setCopyToNotebook,
});
};
@@ -148,6 +161,42 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ 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<HTMLSelectElement>) => {
const action = event.target.value as typeof annotationQuickAction;
setAnnotationQuickAction(action);
saveViewSettings(envConfig, bookKey, 'annotationQuickAction', action, false, true);
};
return (
<div className='my-4 w-full space-y-6'>
<div className='w-full'>
@@ -250,6 +299,41 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
</div>
</div>
<div className='w-full'>
<h2 className='mb-2 font-medium'>{_('Annotation Tools')}</h2>
<div className='card border-base-200 bg-base-100 border shadow'>
<div className='divide-base-200 divide-y'>
<div className='config-item'>
<span className=''>{_('Enable Quick Actions')}</span>
<input
type='checkbox'
className='toggle'
checked={enableAnnotationQuickActions}
onChange={() => setEnableAnnotationQuickActions(!enableAnnotationQuickActions)}
/>
</div>
<div className='config-item'>
<span className=''>{_('Quick Action')}</span>
<Select
value={annotationQuickAction || ''}
onChange={handleSelectAnnotationQuickAction}
options={getQuickActionOptions()}
disabled={!enableAnnotationQuickActions}
/>
</div>
<div className='config-item'>
<span className=''>{_('Copy to Notebook')}</span>
<input
type='checkbox'
className='toggle'
checked={copyToNotebook}
onChange={() => setCopyToNotebook(!copyToNotebook)}
/>
</div>
</div>
</div>
</div>
<div className='w-full'>
<h2 className='mb-2 font-medium'>{_('Animation')}</h2>
<div className='card border-base-200 bg-base-100 border shadow'>
@@ -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'],
@@ -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() },
};
}
@@ -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',
};
@@ -155,7 +155,7 @@ export class TTSController extends EventTarget {
return ssml;
}
async #speak(ssml: string | undefined | Promise<string>) {
async #speak(ssml: string | undefined | Promise<string>, 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<string>) {
async speak(ssml: string | Promise<string>, 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();
}
@@ -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<DeviceControlState>((set, get) => ({
@@ -77,6 +80,12 @@ export const useDeviceControlStore = create<DeviceControlState>((set, get) => ({
}
},
listenToNativeTouchEvents: () => {
window.onNativeTouch = (event: NativeTouchEventType) => {
return eventDispatcher.dispatch('native-touch', event);
};
},
getScreenBrightness: async () => {
const res = await getScreenBrightness();
return res.brightness;
+10
View File
@@ -0,0 +1,10 @@
export type AnnotationToolType =
| 'copy'
| 'highlight'
| 'annotate'
| 'search'
| 'dictionary'
| 'wikipedia'
| 'translate'
| 'tts'
| 'proofread';
+9 -1
View File
@@ -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;
+10
View File
@@ -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;