From 1d87dcdb7fb0deceeb75f8dbcc620bf13ed8d029 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Wed, 29 Oct 2025 17:17:36 +0800 Subject: [PATCH] feat: add options to customize the style of highlighted text of current sentence of TTS (#2358) --- .../public/locales/ar/translation.json | 10 +- .../public/locales/bn/translation.json | 10 +- .../public/locales/bo/translation.json | 10 +- .../public/locales/de/translation.json | 10 +- .../public/locales/el/translation.json | 10 +- .../public/locales/es/translation.json | 10 +- .../public/locales/fa/translation.json | 10 +- .../public/locales/fr/translation.json | 10 +- .../public/locales/hi/translation.json | 10 +- .../public/locales/id/translation.json | 10 +- .../public/locales/it/translation.json | 10 +- .../public/locales/ja/translation.json | 10 +- .../public/locales/ko/translation.json | 10 +- .../public/locales/nl/translation.json | 10 +- .../public/locales/pl/translation.json | 10 +- .../public/locales/pt/translation.json | 10 +- .../public/locales/ru/translation.json | 10 +- .../public/locales/si/translation.json | 10 +- .../public/locales/sv/translation.json | 10 +- .../public/locales/ta/translation.json | 10 +- .../public/locales/th/translation.json | 10 +- .../public/locales/tr/translation.json | 10 +- .../public/locales/uk/translation.json | 10 +- .../public/locales/vi/translation.json | 10 +- .../public/locales/zh-CN/translation.json | 10 +- .../public/locales/zh-TW/translation.json | 10 +- .../app/reader/components/tts/TTSControl.tsx | 9 +- .../src/components/settings/ColorPanel.tsx | 352 +++++------------- .../color/BackgroundTextureSelector.tsx | 145 ++++++++ .../color/CodeHighlightingSettings.tsx | 54 +++ .../settings/{ => color}/ColorInput.tsx | 14 +- .../settings/color/HighlightColorsEditor.tsx | 54 +++ .../color/TTSHighlightStyleEditor.tsx | 147 ++++++++ .../settings/color/ThemeColorSelector.tsx | 87 +++++ .../settings/{ => color}/ThemeEditor.tsx | 0 .../settings/color/ThemeModeSelector.tsx | 44 +++ apps/readest-app/src/services/constants.ts | 2 + .../src/services/tts/TTSController.ts | 15 +- apps/readest-app/src/services/tts/types.ts | 2 +- apps/readest-app/src/types/book.ts | 2 + apps/readest-app/src/types/settings.ts | 1 + 41 files changed, 887 insertions(+), 301 deletions(-) create mode 100644 apps/readest-app/src/components/settings/color/BackgroundTextureSelector.tsx create mode 100644 apps/readest-app/src/components/settings/color/CodeHighlightingSettings.tsx rename apps/readest-app/src/components/settings/{ => color}/ColorInput.tsx (92%) create mode 100644 apps/readest-app/src/components/settings/color/HighlightColorsEditor.tsx create mode 100644 apps/readest-app/src/components/settings/color/TTSHighlightStyleEditor.tsx create mode 100644 apps/readest-app/src/components/settings/color/ThemeColorSelector.tsx rename apps/readest-app/src/components/settings/{ => color}/ThemeEditor.tsx (100%) create mode 100644 apps/readest-app/src/components/settings/color/ThemeModeSelector.tsx diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index e377979e..5d145079 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -651,5 +651,13 @@ "Purchase Successful!": "تمت عملية الشراء بنجاح!", "lifetime": "مدى الحياة", "Thank you for your purchase! Your payment has been processed successfully.": "شكرًا لشرائك! تم معالجة الدفع بنجاح.", - "Order ID:": "معرف الطلب:" + "Order ID:": "معرف الطلب:", + "TTS Highlighting": "تمييز تحويل النص إلى كلام", + "Style": "النمط", + "Underline": "تسطير", + "Strikethrough": "يتوسطه خط", + "Squiggly": "متعرج", + "Outline": "مخطط", + "Save Current Color": "حفظ اللون الحالي", + "Quick Colors": "ألوان سريعة" } diff --git a/apps/readest-app/public/locales/bn/translation.json b/apps/readest-app/public/locales/bn/translation.json index dd185a6a..28cc17dc 100644 --- a/apps/readest-app/public/locales/bn/translation.json +++ b/apps/readest-app/public/locales/bn/translation.json @@ -635,5 +635,13 @@ "Purchase Successful!": "ক্রয় সফল!", "lifetime": "জীবনকাল", "Thank you for your purchase! Your payment has been processed successfully.": "আপনার ক্রয়ের জন্য ধন্যবাদ! আপনার পেমেন্ট সফলভাবে প্রক্রিয়া করা হয়েছে।", - "Order ID:": "অর্ডার আইডি:" + "Order ID:": "অর্ডার আইডি:", + "TTS Highlighting": "টিটিএস হাইলাইটিং", + "Style": "শৈলী", + "Underline": "আন্ডারলাইন", + "Strikethrough": "কাটা লেখা", + "Squiggly": "ঢেউখেলানো দাগ", + "Outline": "বাহিরের রেখা", + "Save Current Color": "বর্তমান রঙ সংরক্ষণ করুন", + "Quick Colors": "দ্রুত রংসমূহ" } diff --git a/apps/readest-app/public/locales/bo/translation.json b/apps/readest-app/public/locales/bo/translation.json index 2fb5fbf5..6fe34f15 100644 --- a/apps/readest-app/public/locales/bo/translation.json +++ b/apps/readest-app/public/locales/bo/translation.json @@ -631,5 +631,13 @@ "Purchase Successful!": "ক্রয় সফল!", "lifetime": "জীবনকাল", "Thank you for your purchase! Your payment has been processed successfully.": "আপনার ক্রয়ের জন্য ধন্যবাদ! আপনার পেমেন্ট সফলভাবে প্রক্রিয়াকৃত হয়েছে।", - "Order ID:": "অর্ডার আইডি:" + "Order ID:": "অর্ডার আইডি:", + "TTS Highlighting": "ཡིག་འབྲུ་སྦྱོར་བའི་གསལ་བཤད།", + "Style": "བཟོ་ལྟ།", + "Underline": "འོག་ཐིག", + "Strikethrough": "འཐེན་ཐིག", + "Squiggly": "འཁྱོག་ཐིག", + "Outline": "མཐའ་ཐིག", + "Save Current Color": "མིག་སྔའི་ཚོན་གཏན་འཁེལ་བྱེད།", + "Quick Colors": "མྱུར་ཚོན།" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index c1cee685..384e5a36 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -635,5 +635,13 @@ "Purchase Successful!": "Kauf erfolgreich!", "lifetime": "lebenslang", "Thank you for your purchase! Your payment has been processed successfully.": "Danke für Ihren Kauf! Ihre Zahlung wurde erfolgreich verarbeitet.", - "Order ID:": "Bestell-ID:" + "Order ID:": "Bestell-ID:", + "TTS Highlighting": "TTS-Hervorhebung", + "Style": "Stil", + "Underline": "Unterstreichen", + "Strikethrough": "Durchgestrichen", + "Squiggly": "Wellig", + "Outline": "Umriss", + "Save Current Color": "Aktuelle Farbe speichern", + "Quick Colors": "Schnellfarben" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index 7f9dfe0f..deee21e4 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -635,5 +635,13 @@ "Purchase Successful!": "Η αγορά ήταν επιτυχής!", "lifetime": "διαρκής", "Thank you for your purchase! Your payment has been processed successfully.": "Σας ευχαριστούμε για την αγορά σας! Η πληρωμή σας έχει επεξεργαστεί με επιτυχία.", - "Order ID:": "Αριθμός παραγγελίας:" + "Order ID:": "Αριθμός παραγγελίας:", + "TTS Highlighting": "Επισήμανση TTS", + "Style": "Στυλ", + "Underline": "Υπογράμμιση", + "Strikethrough": "Διαγραμμένο", + "Squiggly": "Κυματιστό", + "Outline": "Περίγραμμα", + "Save Current Color": "Αποθήκευση τρέχουσας χρώματος", + "Quick Colors": "Γρήγορα χρώματα" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index 6983ef3d..e24dbaa4 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -639,5 +639,13 @@ "Purchase Successful!": "¡Compra Exitosa!", "lifetime": "de por vida", "Thank you for your purchase! Your payment has been processed successfully.": "¡Gracias por tu compra! Tu pago se ha procesado correctamente.", - "Order ID:": "ID de pedido:" + "Order ID:": "ID de pedido:", + "TTS Highlighting": "TTS Resaltado", + "Style": "Estilo", + "Underline": "Subrayado", + "Strikethrough": "Tachado", + "Squiggly": "Ondulado", + "Outline": "Contorno", + "Save Current Color": "Guardar Color Actual", + "Quick Colors": "Colores Rápidos" } diff --git a/apps/readest-app/public/locales/fa/translation.json b/apps/readest-app/public/locales/fa/translation.json index 0add8ef5..60fcdfa4 100644 --- a/apps/readest-app/public/locales/fa/translation.json +++ b/apps/readest-app/public/locales/fa/translation.json @@ -635,5 +635,13 @@ "Purchase Successful!": "خرید با موفقیت انجام شد!", "lifetime": "مادام‌العمر", "Thank you for your purchase! Your payment has been processed successfully.": "از خرید شما متشکریم! پرداخت شما با موفقیت انجام شد.", - "Order ID:": "شناسه سفارش:" + "Order ID:": "شناسه سفارش:", + "TTS Highlighting": "هایلایت TTS", + "Style": "سبک", + "Underline": "خط زیرین", + "Strikethrough": "خط‌خورده", + "Squiggly": "خط‌موج‌دار", + "Outline": "خط‌کشی", + "Save Current Color": "ذخیره رنگ فعلی", + "Quick Colors": "رنگ‌های سریع" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index ed7cd311..acb9536e 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -639,5 +639,13 @@ "Purchase Successful!": "Achat Réussi!", "lifetime": "à vie", "Thank you for your purchase! Your payment has been processed successfully.": "Merci pour votre achat ! Votre paiement a été traité avec succès.", - "Order ID:": "ID de commande :" + "Order ID:": "ID de commande :", + "TTS Highlighting": "Surbrillance TTS", + "Style": "Style", + "Underline": "Souligner", + "Strikethrough": "Barré", + "Squiggly": "Ondulé", + "Outline": "Contour", + "Save Current Color": "Enregistrer la Couleur Actuelle", + "Quick Colors": "Couleurs Rapides" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index 6168a896..fae48402 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -635,5 +635,13 @@ "Purchase Successful!": "खरीदारी सफल!", "lifetime": "जीवनकाल", "Thank you for your purchase! Your payment has been processed successfully.": "आपकी खरीदारी के लिए धन्यवाद! आपका भुगतान सफलतापूर्वक संसाधित हो गया है।", - "Order ID:": "ऑर्डर आईडी:" + "Order ID:": "ऑर्डर आईडी:", + "TTS Highlighting": "TTS हाइलाइटिंग", + "Style": "शैली", + "Underline": "रेखांकित", + "Strikethrough": "रेखांकित करें", + "Squiggly": "स्क्विगली", + "Outline": "आउटलाइन", + "Save Current Color": "वर्तमान रंग सहेजें", + "Quick Colors": "त्वरित रंग" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index 2d2cef6d..b3166224 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -631,5 +631,13 @@ "Purchase Successful!": "Pembelian Berhasil!", "lifetime": "seumur hidup", "Thank you for your purchase! Your payment has been processed successfully.": "Terima kasih atas pembelian Anda! Pembayaran Anda telah berhasil diproses.", - "Order ID:": "ID Pesanan:" + "Order ID:": "ID Pesanan:", + "TTS Highlighting": "TTS Menyorot", + "Style": "Gaya", + "Underline": "Garisan Bawah", + "Strikethrough": "Garis Tengah", + "Squiggly": "Bergelombang", + "Outline": "Garis Besar", + "Save Current Color": "Simpan Warna Saat Ini", + "Quick Colors": "Warna Cepat" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index d0887b4a..487f9431 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -639,5 +639,13 @@ "Purchase Successful!": "Acquisto Riuscito!", "lifetime": "a vita", "Thank you for your purchase! Your payment has been processed successfully.": "Grazie per il tuo acquisto! Il pagamento è stato elaborato con successo.", - "Order ID:": "ID Ordine:" + "Order ID:": "ID Ordine:", + "TTS Highlighting": "Evidenziazione TTS", + "Style": "Stile", + "Underline": "Sottolineato", + "Strikethrough": "Barrato", + "Squiggly": "Ondulato", + "Outline": "Contorno", + "Save Current Color": "Salva Colore Corrente", + "Quick Colors": "Colori Veloci" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index 26a90942..51a6acf0 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -631,5 +631,13 @@ "Purchase Successful!": "購入成功!", "lifetime": "ライフタイム", "Thank you for your purchase! Your payment has been processed successfully.": "ご購入ありがとうございます!お支払いが正常に処理されました。", - "Order ID:": "購入ID:" + "Order ID:": "購入ID:", + "TTS Highlighting": "TTSハイライト", + "Style": "スタイル", + "Underline": "下線", + "Strikethrough": "取り消し線", + "Squiggly": "波線", + "Outline": "アウトライン", + "Save Current Color": "現在の色を保存", + "Quick Colors": "クイックカラー" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index de6b5771..63357445 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -631,5 +631,13 @@ "Purchase Successful!": "구매 성공!", "lifetime": "평생", "Thank you for your purchase! Your payment has been processed successfully.": "구매해주셔서 감사합니다! 결제가 성공적으로 처리되었습니다.", - "Order ID:": "주문 ID:" + "Order ID:": "주문 ID:", + "TTS Highlighting": "TTS 하이라이팅", + "Style": "스타일", + "Underline": "밑줄", + "Strikethrough": "취소선", + "Squiggly": "물결선", + "Outline": "윤곽선", + "Save Current Color": "현재 색상 저장", + "Quick Colors": "빠른 색상" } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index 86a1be0f..c18c87a9 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -635,5 +635,13 @@ "Purchase Successful!": "Aankoop Succesvol!", "lifetime": "levenslang", "Thank you for your purchase! Your payment has been processed successfully.": "Bedankt voor je aankoop! Je betaling is succesvol verwerkt.", - "Order ID:": "Bestel-ID:" + "Order ID:": "Bestel-ID:", + "TTS Highlighting": "TTS-markering", + "Style": "Stijl", + "Underline": "Onderstrepen", + "Strikethrough": "Doorhalen", + "Squiggly": "Kronkelig", + "Outline": "Omtrek", + "Save Current Color": "Huidige kleur opslaan", + "Quick Colors": "Snelle kleuren" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index 815724b6..7bfeee5d 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -643,5 +643,13 @@ "Purchase Successful!": "Zakup Udany!", "lifetime": "dożywotnio", "Thank you for your purchase! Your payment has been processed successfully.": "Dziękujemy za zakup! Twoja płatność została pomyślnie przetworzona.", - "Order ID:": "ID zamówienia:" + "Order ID:": "ID zamówienia:", + "TTS Highlighting": "Wyróżnianie TTS", + "Style": "Styl", + "Underline": "Podkreślenie", + "Strikethrough": "Przekreślenie", + "Squiggly": "Falista linia", + "Outline": "Obrys", + "Save Current Color": "Zapisz bieżący kolor", + "Quick Colors": "Szybkie kolory" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index 3bf3d827..34d81b7d 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -639,5 +639,13 @@ "Purchase Successful!": "Compra Bem-Sucedida!", "lifetime": "vitalício", "Thank you for your purchase! Your payment has been processed successfully.": "Obrigado pela sua compra! Seu pagamento foi processado com sucesso.", - "Order ID:": "ID do Pedido:" + "Order ID:": "ID do Pedido:", + "TTS Highlighting": "Destaque TTS", + "Style": "Estilo", + "Underline": "Sublinhado", + "Strikethrough": "Tachado", + "Squiggly": "Ondulado", + "Outline": "Contorno", + "Save Current Color": "Salvar cor atual", + "Quick Colors": "Cores rápidas" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index 0907b1b7..6b8e0145 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -643,5 +643,13 @@ "Purchase Successful!": "Покупка Успешна!", "lifetime": "пожизненный", "Thank you for your purchase! Your payment has been processed successfully.": "Спасибо за вашу покупку! Ваш платеж был успешно обработан.", - "Order ID:": "ID заказа:" + "Order ID:": "ID заказа:", + "TTS Highlighting": "Выделение TTS", + "Style": "Стиль", + "Underline": "Подчёркивание", + "Strikethrough": "Зачёркивание", + "Squiggly": "Волнистая линия", + "Outline": "Контур", + "Save Current Color": "Сохранить текущий цвет", + "Quick Colors": "Быстрые цвета" } diff --git a/apps/readest-app/public/locales/si/translation.json b/apps/readest-app/public/locales/si/translation.json index 92a39a5e..431080d4 100644 --- a/apps/readest-app/public/locales/si/translation.json +++ b/apps/readest-app/public/locales/si/translation.json @@ -635,5 +635,13 @@ "Purchase Successful!": "මිලදී ගැනීම සාර්ථකයි!", "lifetime": "පොදුකාලීන", "Thank you for your purchase! Your payment has been processed successfully.": "ඔබේ මිලදී ගැනීමට ස්තූතියි! ඔබේ ගෙවීම සාර්ථකව සැකසිණි.", - "Order ID:": "ඇණවුම් හැඳුනුම්පත:" + "Order ID:": "ඇණවුම් හැඳුනුම්පත:", + "TTS Highlighting": "TTS උද්දීපනය", + "Style": "රූපය", + "Underline": "ඇටය", + "Strikethrough": "ඇඳුම", + "Squiggly": "වැලි රේඛාව", + "Outline": "පරිසීමාව", + "Save Current Color": "දැන් ඇති වර්ණය සුරකින්න", + "Quick Colors": "ක්ෂණික වර්ණ" } diff --git a/apps/readest-app/public/locales/sv/translation.json b/apps/readest-app/public/locales/sv/translation.json index b8483216..48e82b14 100644 --- a/apps/readest-app/public/locales/sv/translation.json +++ b/apps/readest-app/public/locales/sv/translation.json @@ -635,5 +635,13 @@ "Purchase Successful!": "Köp lyckades!", "lifetime": "livstid", "Thank you for your purchase! Your payment has been processed successfully.": "Tack för ditt köp! Din betalning har behandlats.", - "Order ID:": "Order-ID:" + "Order ID:": "Order-ID:", + "TTS Highlighting": "TTS-markering", + "Style": "Stil", + "Underline": "Understrykning", + "Strikethrough": "Genomstrykning", + "Squiggly": "Vågig linje", + "Outline": "Kontur", + "Save Current Color": "Spara aktuell färg", + "Quick Colors": "Snabba färger" } diff --git a/apps/readest-app/public/locales/ta/translation.json b/apps/readest-app/public/locales/ta/translation.json index abf13194..f5312836 100644 --- a/apps/readest-app/public/locales/ta/translation.json +++ b/apps/readest-app/public/locales/ta/translation.json @@ -635,5 +635,13 @@ "Purchase Successful!": "கொள்முதல் வெற்றிகரமாக முடிந்தது!", "lifetime": "ஆயுள்திருத்தம்", "Thank you for your purchase! Your payment has been processed successfully.": "உங்கள் வாங்குதலுக்கு நன்றி! உங்கள் கட்டணம் வெற்றிகரமாக செயல்படுத்தப்பட்டது.", - "Order ID:": "ஆர்டர் ஐடி:" + "Order ID:": "ஆர்டர் ஐடி:", + "TTS Highlighting": "TTS சிறப்பித்தல்", + "Style": "பாணி", + "Underline": "அடிக்கோடு", + "Strikethrough": "குறிக்கோடு", + "Squiggly": "சுருள் கோடு", + "Outline": "வட்டம்", + "Save Current Color": "தற்போதைய நிறத்தை சேமிக்கவும்", + "Quick Colors": "விரைவு நிறங்கள்" } diff --git a/apps/readest-app/public/locales/th/translation.json b/apps/readest-app/public/locales/th/translation.json index 743ff710..cbc4e2cb 100644 --- a/apps/readest-app/public/locales/th/translation.json +++ b/apps/readest-app/public/locales/th/translation.json @@ -631,5 +631,13 @@ "Purchase Successful!": "การซื้อสำเร็จ!", "lifetime": "ตลอดชีพ", "Thank you for your purchase! Your payment has been processed successfully.": "ขอบคุณสำหรับการซื้อ! การชำระเงินของคุณได้รับการประมวลผลเรียบร้อยแล้ว", - "Order ID:": "รหัสคำสั่งซื้อ:" + "Order ID:": "รหัสคำสั่งซื้อ:", + "TTS Highlighting": "การไฮไลต์ TTS", + "Style": "สไตล์", + "Underline": "ขีดเส้นใต้", + "Strikethrough": "ขีดฆ่า", + "Squiggly": "เส้นหยัก", + "Outline": "เส้นขอบ", + "Save Current Color": "บันทึกสีปัจจุบัน", + "Quick Colors": "สีด่วน" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index 95d411fb..36b02b6f 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -635,5 +635,13 @@ "Purchase Successful!": "Satın Alma Başarılı!", "lifetime": "ömür boyu", "Thank you for your purchase! Your payment has been processed successfully.": "Satın alma işleminiz için teşekkürler! Ödemeniz başarıyla işlendi.", - "Order ID:": "Sipariş Kimliği:" + "Order ID:": "Sipariş Kimliği:", + "TTS Highlighting": "TTS Vurgulama", + "Style": "Stil", + "Underline": "Altı Çizili", + "Strikethrough": "Üstü Çizili", + "Squiggly": "Dalgalı Çizgi", + "Outline": "Kontur", + "Save Current Color": "Mevcut Rengi Kaydet", + "Quick Colors": "Hızlı Renkler" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index 812687f5..042818ee 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -643,5 +643,13 @@ "Purchase Successful!": "Покупка успішна!", "lifetime": "довічний", "Thank you for your purchase! Your payment has been processed successfully.": "Дякуємо за вашу покупку! Ваш платіж успішно оброблено.", - "Order ID:": "ID замовлення:" + "Order ID:": "ID замовлення:", + "TTS Highlighting": "Підсвічування TTS", + "Style": "Стиль", + "Underline": "Підкреслення", + "Strikethrough": "Закреслення", + "Squiggly": "Хвиляста лінія", + "Outline": "Контур", + "Save Current Color": "Зберегти поточний колір", + "Quick Colors": "Швидкі кольори" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index d5400f61..a7fddf15 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -631,5 +631,13 @@ "Purchase Successful!": "Mua hàng thành công!", "lifetime": "trọn đời", "Thank you for your purchase! Your payment has been processed successfully.": "Cảm ơn bạn đã mua hàng! Thanh toán của bạn đã được xử lý thành công.", - "Order ID:": "Mã đơn hàng:" + "Order ID:": "Mã đơn hàng:", + "TTS Highlighting": "Đánh dấu TTS", + "Style": "Kiểu", + "Underline": "Gạch chân", + "Strikethrough": "Gạch ngang", + "Squiggly": "Gạch ngoằn ngoèo", + "Outline": "Đường viền", + "Save Current Color": "Lưu màu hiện tại", + "Quick Colors": "Màu nhanh" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index 38ed5379..64d512a7 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -631,5 +631,13 @@ "Purchase Successful!": "购买成功!", "lifetime": "永久", "Thank you for your purchase! Your payment has been processed successfully.": "感谢您的购买!您的付款已成功处理。", - "Order ID:": "订单号:" + "Order ID:": "订单号:", + "TTS Highlighting": "TTS 高亮", + "Style": "样式", + "Underline": "下划线", + "Strikethrough": "删除线", + "Squiggly": "波浪线", + "Outline": "轮廓", + "Save Current Color": "保存当前颜色", + "Quick Colors": "快速颜色" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index 6f0719e9..ebb97eeb 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -631,5 +631,13 @@ "Purchase Successful!": "購買成功!", "lifetime": "永久", "Thank you for your purchase! Your payment has been processed successfully.": "感謝您的購買!您的付款已成功處理。", - "Order ID:": "訂單編號:" + "Order ID:": "訂單編號:", + "TTS Highlighting": "TTS 高亮", + "Style": "樣式", + "Underline": "底線", + "Strikethrough": "刪除線", + "Squiggly": "波浪線", + "Outline": "輪廓", + "Save Current Color": "儲存目前顏色", + "Quick Colors": "快速顏色" } 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 931875ee..eb4b6326 100644 --- a/apps/readest-app/src/app/reader/components/tts/TTSControl.tsx +++ b/apps/readest-app/src/app/reader/components/tts/TTSControl.tsx @@ -323,7 +323,7 @@ const TTSControl: React.FC = ({ bookKey, gridInsets }) => { setShowIndicator(true); const ttsController = new TTSController(appService, view); await ttsController.init(); - await ttsController.initViewTTS(); + await ttsController.initViewTTS(viewSettings.ttsHighlightOptions); const ssml = view.tts?.from(ttsFromRange); if (ssml) { const lang = parseSSMLLang(ssml, primaryLang) || 'en'; @@ -625,6 +625,13 @@ const TTSControl: React.FC = ({ bookKey, gridInsets }) => { }; }, [hoveredBookKey]); + useEffect(() => { + const ttsHighlightOptions = viewSettings?.ttsHighlightOptions; + if (ttsControllerRef.current && ttsHighlightOptions) { + ttsControllerRef.current.initViewTTS(ttsHighlightOptions); + } + }, [viewSettings?.ttsHighlightOptions]); + return ( <> {showPanel && } diff --git a/apps/readest-app/src/components/settings/ColorPanel.tsx b/apps/readest-app/src/components/settings/ColorPanel.tsx index 25901593..cf99a4b9 100644 --- a/apps/readest-app/src/components/settings/ColorPanel.tsx +++ b/apps/readest-app/src/components/settings/ColorPanel.tsx @@ -1,9 +1,4 @@ import React, { useState, useEffect } from 'react'; -import { MdOutlineLightMode, MdOutlineDarkMode, MdClose, MdAdd } from 'react-icons/md'; -import { MdRadioButtonUnchecked, MdRadioButtonChecked } from 'react-icons/md'; -import { CgColorPicker } from 'react-icons/cg'; -import { TbSunMoon } from 'react-icons/tb'; -import { PiPlus } from 'react-icons/pi'; import { applyCustomTheme, CustomTheme, @@ -17,48 +12,55 @@ import { useThemeStore } from '@/store/themeStore'; import { useReaderStore } from '@/store/readerStore'; import { useTranslation } from '@/hooks/useTranslation'; import { useSettingsStore } from '@/store/settingsStore'; -import { useResponsiveSize } from '@/hooks/useResponsiveSize'; import { useResetViewSettings } from '@/hooks/useResetSettings'; import { useCustomTextureStore } from '@/store/customTextureStore'; import { saveViewSettings } from '@/helpers/settings'; -import { CODE_LANGUAGES, CodeLanguage, manageSyntaxHighlighting } from '@/utils/highlightjs'; +import { manageSyntaxHighlighting } from '@/utils/highlightjs'; import { SettingsPanelPanelProp } from './SettingsDialog'; import { useFileSelector } from '@/hooks/useFileSelector'; import { PREDEFINED_TEXTURES } from '@/styles/textures'; -import { HighlightColor } from '@/types/book'; import { HIGHLIGHT_COLOR_HEX } from '@/services/constants'; -import Select from '@/components/Select'; -import ThemeEditor from './ThemeEditor'; -import ColorInput from './ColorInput'; +import ThemeEditor from './color/ThemeEditor'; +import ThemeModeSelector from './color/ThemeModeSelector'; +import ThemeColorSelector from './color/ThemeColorSelector'; +import BackgroundTextureSelector from './color/BackgroundTextureSelector'; +import HighlightColorsEditor from './color/HighlightColorsEditor'; +import TTSHighlightStyleEditor, { TTSHighlightStyle } from './color/TTSHighlightStyleEditor'; +import CodeHighlightingSettings from './color/CodeHighlightingSettings'; const ColorPanel: React.FC = ({ bookKey, onRegisterReset }) => { const _ = useTranslation(); const { themeMode, themeColor, isDarkMode, setThemeMode, setThemeColor, saveCustomTheme } = useThemeStore(); const { envConfig, appService } = useEnv(); - const { settings, setSettings } = useSettingsStore(); + const { settings, setSettings, saveSettings } = useSettingsStore(); const { getView, getViewSettings } = useReaderStore(); const viewSettings = getViewSettings(bookKey) || settings.globalViewSettings; + const [invertImgColorInDark, setInvertImgColorInDark] = useState( viewSettings.invertImgColorInDark, ); - - const iconSize16 = useResponsiveSize(16); - const iconSize24 = useResponsiveSize(24); const [editTheme, setEditTheme] = useState(null); const [customThemes, setCustomThemes] = useState([]); const [showCustomThemeEditor, setShowCustomThemeEditor] = useState(false); const [overrideColor, setOverrideColor] = useState(viewSettings.overrideColor); const [codeHighlighting, setcodeHighlighting] = useState(viewSettings.codeHighlighting); const [codeLanguage, setCodeLanguage] = useState(viewSettings.codeLanguage); - const [selectedTextureId, setSelectedTextureId] = useState(viewSettings.backgroundTextureId); const [backgroundOpacity, setBackgroundOpacity] = useState(viewSettings.backgroundOpacity); const [backgroundSize, setBackgroundSize] = useState(viewSettings.backgroundSize); - + const [ttsHighlightStyle, setTtsHighlightStyle] = useState( + viewSettings.ttsHighlightOptions.style, + ); + const [ttsHighlightColor, setTtsHighlightColor] = useState( + viewSettings.ttsHighlightOptions.color, + ); const [customHighlightColors, setCustomHighlightColors] = useState( settings.globalReadSettings.customHighlightColors, ); + const [customTtsHighlightColors, setCustomTtsHighlightColors] = useState( + settings.globalReadSettings.customTtsHighlightColors || [], + ); const { textures: customTextures, @@ -70,7 +72,6 @@ const ColorPanel: React.FC = ({ bookKey, onRegisterReset saveCustomTextures, } = useCustomTextureStore(); const resetToDefaults = useResetViewSettings(); - const { selectFiles } = useFileSelector(appService, _); const handleReset = () => { @@ -110,7 +111,7 @@ const ColorPanel: React.FC = ({ bookKey, onRegisterReset }, [overrideColor]); useEffect(() => { - let update = false; // check if we need to update syntax highlighting + let update = false; if (codeHighlighting !== viewSettings.codeHighlighting) { saveViewSettings(envConfig, bookKey, 'codeHighlighting', codeHighlighting); update = true; @@ -172,7 +173,6 @@ const ColorPanel: React.FC = ({ bookKey, onRegisterReset const handleSaveCustomTheme = (customTheme: CustomTheme) => { applyCustomTheme(customTheme); saveCustomTheme(envConfig, settings, customTheme); - setSettings({ ...settings }); setThemeColor(customTheme.name); setShowCustomThemeEditor(false); @@ -180,7 +180,6 @@ const ColorPanel: React.FC = ({ bookKey, onRegisterReset const handleDeleteCustomTheme = (customTheme: CustomTheme) => { saveCustomTheme(envConfig, settings, customTheme, true); - setSettings({ ...settings }); setThemeColor('default'); setShowCustomThemeEditor(false); @@ -224,7 +223,35 @@ const ColorPanel: React.FC = ({ bookKey, onRegisterReset saveCustomTextures(envConfig); }; - const allTextures = [...PREDEFINED_TEXTURES, ...customTextures.filter((t) => !t.deletedAt)]; + const handleHighlightColorsChange = (colors: typeof customHighlightColors) => { + setCustomHighlightColors(colors); + settings.globalReadSettings.customHighlightColors = colors; + setSettings(settings); + saveSettings(envConfig, settings); + }; + + const handleTTSStyleChange = (style: TTSHighlightStyle) => { + setTtsHighlightStyle(style); + saveViewSettings(envConfig, bookKey, 'ttsHighlightOptions', { + style, + color: ttsHighlightColor, + }); + }; + + const handleTTSColorChange = (color: string) => { + setTtsHighlightColor(color); + saveViewSettings(envConfig, bookKey, 'ttsHighlightOptions', { + style: ttsHighlightStyle, + color, + }); + }; + + const handleCustomTtsColorsChange = (colors: string[]) => { + setCustomTtsHighlightColors(colors); + settings.globalReadSettings.customTtsHighlightColors = colors; + setSettings(settings); + saveSettings(envConfig, settings); + }; return (
@@ -237,32 +264,7 @@ const ColorPanel: React.FC = ({ bookKey, onRegisterReset /> ) : ( <> -
-

{_('Theme Mode')}

-
- - - -
-
+

{_('Invert Image In Dark Mode')}

@@ -285,226 +287,48 @@ const ColorPanel: React.FC = ({ bookKey, onRegisterReset />
-
-

{_('Theme Color')}

-
- {themes.concat(customThemes).map(({ name, label, colors, isCustomizale }) => ( - - )} - - ))} - -
-
+ setShowCustomThemeEditor(true)} + /> -
-

{_('Background Image')}

-
- {allTextures.map((texture) => ( - - )} - - ))} + !t.deletedAt)} + selectedTextureId={selectedTextureId} + backgroundOpacity={backgroundOpacity} + backgroundSize={backgroundSize} + onTextureSelect={setSelectedTextureId} + onOpacityChange={setBackgroundOpacity} + onSizeChange={setBackgroundSize} + onImportImage={handleImportImage} + onDeleteTexture={handleDeleteCustomTexture} + /> - {/* Custom Image Upload */} -
- -
-
+ - {/* Background Image Settings */} - {selectedTextureId !== 'none' && ( -
-
- {_('Opacity')} -
- setBackgroundOpacity(parseFloat(e.target.value))} - className='range range-sm w-32' - /> - - {Math.round(backgroundOpacity * 100)}% - -
-
+ -
- {_('Size')} - setcodeHighlighting(!codeHighlighting)} - /> -
- -
- {_('Code Language')} - onOpacityChange(parseFloat(e.target.value))} + className='range range-sm w-32' + /> + + {Math.round(backgroundOpacity * 100)}% + +
+
+ +
+ {_('Size')} + onToggle(!codeHighlighting)} + /> +
+ +
+ {_('Code Language')} + onStyleChange(e.target.value as TTSHighlightStyle)} + options={[ + { value: 'highlight', label: _('Highlight') }, + { value: 'underline', label: _('Underline') }, + { value: 'strikethrough', label: _('Strikethrough') }, + { value: 'squiggly', label: _('Squiggly') }, + { value: 'outline', label: _('Outline') }, + ]} + /> +
+ +
+ {_('Color')} +
+
+ + +
+
+ +
+
+ {_('Quick Colors')} +
+ +
+ {defaultQuickColors.map(({ color: quickColor }) => ( + +
+ ))} +
+
+
+
+ + ); +}; + +export default TTSHighlightStyleEditor; diff --git a/apps/readest-app/src/components/settings/color/ThemeColorSelector.tsx b/apps/readest-app/src/components/settings/color/ThemeColorSelector.tsx new file mode 100644 index 00000000..5c6e1dde --- /dev/null +++ b/apps/readest-app/src/components/settings/color/ThemeColorSelector.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import { CgColorPicker } from 'react-icons/cg'; +import { MdRadioButtonUnchecked, MdRadioButtonChecked } from 'react-icons/md'; +import { PiPlus } from 'react-icons/pi'; +import { Theme } from '@/styles/themes'; +import { useTranslation } from '@/hooks/useTranslation'; +import { useResponsiveSize } from '@/hooks/useResponsiveSize'; + +interface ThemeColorSelectorProps { + themes: Theme[]; + themeColor: string; + isDarkMode: boolean; + onThemeColorChange: (name: string) => void; + onEditTheme: (name: string) => void; + onCreateTheme: () => void; +} + +const ThemeColorSelector: React.FC = ({ + themes, + themeColor, + isDarkMode, + onThemeColorChange, + onEditTheme, + onCreateTheme, +}) => { + const _ = useTranslation(); + const iconSize16 = useResponsiveSize(16); + const iconSize24 = useResponsiveSize(24); + + return ( +
+

{_('Theme Color')}

+
+ {themes.map(({ name, label, colors, isCustomizale }) => ( + + )} + + ))} + +
+
+ ); +}; + +export default ThemeColorSelector; diff --git a/apps/readest-app/src/components/settings/ThemeEditor.tsx b/apps/readest-app/src/components/settings/color/ThemeEditor.tsx similarity index 100% rename from apps/readest-app/src/components/settings/ThemeEditor.tsx rename to apps/readest-app/src/components/settings/color/ThemeEditor.tsx diff --git a/apps/readest-app/src/components/settings/color/ThemeModeSelector.tsx b/apps/readest-app/src/components/settings/color/ThemeModeSelector.tsx new file mode 100644 index 00000000..7d50a472 --- /dev/null +++ b/apps/readest-app/src/components/settings/color/ThemeModeSelector.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { MdOutlineLightMode, MdOutlineDarkMode } from 'react-icons/md'; +import { TbSunMoon } from 'react-icons/tb'; +import { useTranslation } from '@/hooks/useTranslation'; + +interface ThemeModeSelectorProps { + themeMode: 'auto' | 'light' | 'dark'; + onThemeModeChange: (mode: 'auto' | 'light' | 'dark') => void; +} + +const ThemeModeSelector: React.FC = ({ themeMode, onThemeModeChange }) => { + const _ = useTranslation(); + + return ( +
+

{_('Theme Mode')}

+
+ + + +
+
+ ); +}; + +export default ThemeModeSelector; diff --git a/apps/readest-app/src/services/constants.ts b/apps/readest-app/src/services/constants.ts index d614a04e..76fd00cc 100644 --- a/apps/readest-app/src/services/constants.ts +++ b/apps/readest-app/src/services/constants.ts @@ -104,6 +104,7 @@ export const DEFAULT_READSETTINGS: ReadSettings = { squiggly: 'blue', }, customHighlightColors: HIGHLIGHT_COLOR_HEX, + customTtsHighlightColors: [], }; export const DEFAULT_MOBILE_READSETTINGS: Partial = { @@ -219,6 +220,7 @@ export const DEFAULT_TTS_CONFIG: TTSConfig = { ttsVoice: '', ttsLocation: '', showTTSBar: false, + ttsHighlightOptions: { style: 'highlight', color: '#808080' }, }; export const DEFAULT_TRANSLATOR_CONFIG: TranslatorConfig = { diff --git a/apps/readest-app/src/services/tts/TTSController.ts b/apps/readest-app/src/services/tts/TTSController.ts index 1a50533b..04f16725 100644 --- a/apps/readest-app/src/services/tts/TTSController.ts +++ b/apps/readest-app/src/services/tts/TTSController.ts @@ -41,6 +41,8 @@ export class TTSController extends EventTarget { ttsNativeVoices: TTSVoice[] = []; ttsTargetLang: string = ''; + options: TTSHighlightOptions = { style: 'highlight', color: 'gray' }; + constructor(appService: AppService | null, view: FoliateView) { super(); this.ttsWebClient = new WebSpeechClient(this); @@ -80,10 +82,10 @@ export class TTSController extends EventTarget { this.ttsEdgeVoices = await this.ttsEdgeClient.getAllVoices(); } - #getHighlighter(options: TTSHighlightOptions) { + #getHighlighter() { return (range: Range) => { const { overlayer } = this.view.renderer.getContents()[0] as { overlayer: Overlayer }; - const { style, color } = options; + const { style, color } = this.options; overlayer?.remove(HIGHLIGHT_KEY); overlayer?.add(HIGHLIGHT_KEY, range, Overlayer[style], { color }); }; @@ -94,20 +96,23 @@ export class TTSController extends EventTarget { overlayer?.remove(HIGHLIGHT_KEY); } - async initViewTTS() { + async initViewTTS(options?: TTSHighlightOptions) { + if (options) { + this.options.style = options.style; + this.options.color = options.color; + } let granularity: TTSGranularity = this.view.language.isCJK ? 'sentence' : 'word'; const supportedGranularities = this.ttsClient.getGranularities(); if (!supportedGranularities.includes(granularity)) { granularity = supportedGranularities[0]!; } - const highlightOptions: TTSHighlightOptions = { style: 'highlight', color: 'gray' }; await this.view.initTTS( granularity, createRejectFilter({ tags: ['rt', 'sup'], contents: [{ tag: 'a', content: /^\d+$/ }], }), - this.#getHighlighter(highlightOptions), + this.#getHighlighter(), ); } diff --git a/apps/readest-app/src/services/tts/types.ts b/apps/readest-app/src/services/tts/types.ts index 5f64bf4c..41ac342f 100644 --- a/apps/readest-app/src/services/tts/types.ts +++ b/apps/readest-app/src/services/tts/types.ts @@ -1,7 +1,7 @@ export type TTSGranularity = 'sentence' | 'word'; export type TTSHighlightOptions = { - style: 'highlight' | 'underline' | 'squiggly' | 'outline'; + style: 'highlight' | 'underline' | 'strikethrough' | 'squiggly' | 'outline'; color: string; }; diff --git a/apps/readest-app/src/types/book.ts b/apps/readest-app/src/types/book.ts index 08fa9dbb..39d194c1 100644 --- a/apps/readest-app/src/types/book.ts +++ b/apps/readest-app/src/types/book.ts @@ -1,4 +1,5 @@ import { BookMetadata } from '@/libs/document'; +import { TTSHighlightOptions } from '@/services/tts/types'; export type BookFormat = 'EPUB' | 'PDF' | 'MOBI' | 'AZW' | 'AZW3' | 'CBZ' | 'FB2' | 'FBZ'; export type BookNoteType = 'bookmark' | 'annotation' | 'excerpt'; @@ -175,6 +176,7 @@ export interface TTSConfig { ttsVoice: string; ttsLocation: string; showTTSBar: boolean; + ttsHighlightOptions: TTSHighlightOptions; } export interface TranslatorConfig { diff --git a/apps/readest-app/src/types/settings.ts b/apps/readest-app/src/types/settings.ts index d5fc9d1d..e6602939 100644 --- a/apps/readest-app/src/types/settings.ts +++ b/apps/readest-app/src/types/settings.ts @@ -23,6 +23,7 @@ export interface ReadSettings { highlightStyle: HighlightStyle; highlightStyles: Record; customHighlightColors: Record; + customTtsHighlightColors: string[]; customThemes: CustomTheme[]; }