diff --git a/apps/readest-app/package.json b/apps/readest-app/package.json index c4a3542d..4f1f3540 100644 --- a/apps/readest-app/package.json +++ b/apps/readest-app/package.json @@ -109,6 +109,7 @@ "dompurify": "^3.3.0", "foliate-js": "workspace:*", "franc-min": "^6.2.0", + "fzf": "^0.5.2", "google-auth-library": "^10.5.0", "googleapis": "^164.1.0", "highlight.js": "^11.11.1", diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index d7cb8e65..bb960581 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -982,5 +982,21 @@ "Reading Ruler": "مسطرة القراءة", "Enable Reading Ruler": "تفعيل مسطرة القراءة", "Lines to Highlight": "الأسطر المراد تمييزها", - "Ruler Color": "لون المسطرة" + "Ruler Color": "لون المسطرة", + "Command Palette": "لوحة الأوامر", + "Search settings and actions...": "البحث في الإعدادات والإجراءات...", + "No results found for": "لم يتم العثور على نتائج لـ", + "Type to search settings and actions": "اكتب للبحث في الإعدادات والإجراءات", + "Recent": "الأخير", + "navigate": "التنقل", + "select": "اختيار", + "close": "إغلاق", + "Search Settings": "البحث في الإعدادات", + "Page Margins": "هوامش الصفحة", + "AI Provider": "مزود الذكاء الاصطناعي", + "Ollama URL": "رابط Ollama", + "Ollama Model": "نموذج Ollama", + "AI Gateway Model": "نموذج بوابة الذكاء الاصطناعي", + "Actions": "الإجراءات", + "Navigation": "التنقل" } diff --git a/apps/readest-app/public/locales/bn/translation.json b/apps/readest-app/public/locales/bn/translation.json index 40fc8b17..7bb6b65d 100644 --- a/apps/readest-app/public/locales/bn/translation.json +++ b/apps/readest-app/public/locales/bn/translation.json @@ -942,5 +942,21 @@ "Reading Ruler": "রিডিং রুলার", "Enable Reading Ruler": "রিডিং রুলার সক্ষম করুন", "Lines to Highlight": "হাইলাইট করার জন্য লাইন", - "Ruler Color": "রুলারের রঙ" + "Ruler Color": "রুলারের রঙ", + "Command Palette": "কমান্ড প্যালেট", + "Search settings and actions...": "সেটিংস এবং অ্যাকশন খুঁজুন...", + "No results found for": "এর জন্য কোনো ফলাফল পাওয়া যায়নি", + "Type to search settings and actions": "সেটিংস এবং অ্যাকশন খুঁজতে টাইপ করুন", + "Recent": "সাম্প্রতিক", + "navigate": "নেভিগেট", + "select": "নির্বাচন করুন", + "close": "বন্ধ করুন", + "Search Settings": "সেটিংস খুঁজুন", + "Page Margins": "পৃষ্ঠার মার্জিন", + "AI Provider": "AI প্রদানকারী", + "Ollama URL": "Ollama URL", + "Ollama Model": "Ollama মডেল", + "AI Gateway Model": "AI গেটওয়ে মডেল", + "Actions": "অ্যাকশন", + "Navigation": "নেভিগেশন" } diff --git a/apps/readest-app/public/locales/bo/translation.json b/apps/readest-app/public/locales/bo/translation.json index bd623ed0..3a905551 100644 --- a/apps/readest-app/public/locales/bo/translation.json +++ b/apps/readest-app/public/locales/bo/translation.json @@ -932,5 +932,21 @@ "Reading Ruler": "ལྟ་ཀློག་ཐིག་ཤིང་།", "Enable Reading Ruler": "ལྟ་ཀློག་ཐིག་ཤིང་སྤྱོད་པ།", "Lines to Highlight": "མངོན་གསལ་དུ་གཏོང་དགོས་པའི་ཐིག་ཕྲེང་།", - "Ruler Color": "ཐིག་ཤིང་ගི་ཚོན་མདོག" + "Ruler Color": "ཐིག་ཤིང་ගི་ཚོན་མདོག", + "Command Palette": "བཀོད་འདོམས་པང་ལེབ།", + "Search settings and actions...": "སྒྲིག་བཀོད་དང་བྱ་འགུལ་འཚོལ་བ།...", + "No results found for": "འབྲས་བུ་མ་རྙེད་པ།", + "Type to search settings and actions": "ཡིག་གཟུགས་ནང་འཇུག་བྱས་ཏེ་སྒྲིག་བཀོད་དང་བྱ་འགུལ་འཚོལ་བ།", + "Recent": "ཉེ་ཆར།", + "navigate": "འགྲུལ་བཞུད།", + "select": "འདེམས་པ།", + "close": "ཁ་རྒྱག་པ།", + "Search Settings": "སྒྲིག་བཀོད་འཚོལ་བ།", + "Page Margins": "ཤོག་ངོས་མཐའ་འགྲམ།", + "AI Provider": "ནུས་པའི་རིག་རྩལ་མཁོ་སྤྲོད་པ།", + "Ollama URL": "Ollama URL", + "Ollama Model": "Ollama དཔེ་གཞི།", + "AI Gateway Model": "AI Gateway དཔེ་གཞི།", + "Actions": "བྱ་འགུལ།", + "Navigation": "འགྲུལ་བཞུད།" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index b4199e6b..9492bf12 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -942,5 +942,21 @@ "Reading Ruler": "Lese-Lineal", "Enable Reading Ruler": "Lese-Lineal aktivieren", "Lines to Highlight": "Hervorzuhebende Zeilen", - "Ruler Color": "Linealfarbe" + "Ruler Color": "Linealfarbe", + "Command Palette": "Befehlspalette", + "Search settings and actions...": "Einstellungen und Aktionen suchen...", + "No results found for": "Keine Ergebnisse gefunden für", + "Type to search settings and actions": "Tippen, um Einstellungen und Aktionen zu suchen", + "Recent": "Zuletzt verwendet", + "navigate": "navigieren", + "select": "auswählen", + "close": "schließen", + "Search Settings": "Einstellungen suchen", + "Page Margins": "Seitenränder", + "AI Provider": "KI-Anbieter", + "Ollama URL": "Ollama-URL", + "Ollama Model": "Ollama-Modell", + "AI Gateway Model": "AI Gateway-Modell", + "Actions": "Aktionen", + "Navigation": "Navigation" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index add36e63..790802e8 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -942,5 +942,21 @@ "Reading Ruler": "Χάρακας ανάγνωσης", "Enable Reading Ruler": "Ενεργοποίηση χάρακα ανάγνωσης", "Lines to Highlight": "Γραμμές για επισήμανση", - "Ruler Color": "Χρώμα χάρακα" + "Ruler Color": "Χρώμα χάρακα", + "Command Palette": "Παλέτα εντολών", + "Search settings and actions...": "Αναζήτηση ρυθμίσεων και ενεργειών...", + "No results found for": "Δεν βρέθηκαν αποτελέσματα για", + "Type to search settings and actions": "Πληκτρολογήστε για αναζήτηση ρυθμίσεων και ενεργειών", + "Recent": "Πρόσφατα", + "navigate": "πλοήγηση", + "select": "επιλογή", + "close": "κλείσιμο", + "Search Settings": "Αναζήτηση ρυθμίσεων", + "Page Margins": "Περιθώρια σελίδας", + "AI Provider": "Πάροχος AI", + "Ollama URL": "URL Ollama", + "Ollama Model": "Μοντέλο Ollama", + "AI Gateway Model": "Μοντέλο AI Gateway", + "Actions": "Ενέργειες", + "Navigation": "Πλοήγηση" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index 195714e9..c0cc6a5d 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -952,5 +952,21 @@ "Reading Ruler": "Regla de lectura", "Enable Reading Ruler": "Habilitar regla de lectura", "Lines to Highlight": "Líneas a resaltar", - "Ruler Color": "Color de la regla" + "Ruler Color": "Color de la regla", + "Command Palette": "Paleta de comandos", + "Search settings and actions...": "Buscar ajustes y acciones...", + "No results found for": "No se encontraron resultados para", + "Type to search settings and actions": "Escribe para buscar ajustes y acciones", + "Recent": "Reciente", + "navigate": "navegar", + "select": "seleccionar", + "close": "cerrar", + "Search Settings": "Buscar ajustes", + "Page Margins": "Márgenes de página", + "AI Provider": "Proveedor de IA", + "Ollama URL": "URL de Ollama", + "Ollama Model": "Modelo de Ollama", + "AI Gateway Model": "Modelo de AI Gateway", + "Actions": "Acciones", + "Navigation": "Navegación" } diff --git a/apps/readest-app/public/locales/fa/translation.json b/apps/readest-app/public/locales/fa/translation.json index 8271c6fb..d2b37371 100644 --- a/apps/readest-app/public/locales/fa/translation.json +++ b/apps/readest-app/public/locales/fa/translation.json @@ -942,5 +942,21 @@ "Reading Ruler": "خط‌کش مطالعه", "Enable Reading Ruler": "فعال‌سازی خط‌کش مطالعه", "Lines to Highlight": "خطوط برای تمایز", - "Ruler Color": "رنگ خط‌کش" + "Ruler Color": "رنگ خط‌کش", + "Command Palette": "پالت فرمان", + "Search settings and actions...": "جستجوی تنظیمات و عملیات...", + "No results found for": "نتیجه‌ای برای این مورد یافت نشد", + "Type to search settings and actions": "برای جستجوی تنظیمات و عملیات تایپ کنید", + "Recent": "اخیر", + "navigate": "ناوبری", + "select": "انتخاب", + "close": "بستن", + "Search Settings": "جستجوی تنظیمات", + "Page Margins": "حاشیه‌های صفحه", + "AI Provider": "ارائه دهنده هوش مصنوعی", + "Ollama URL": "آدرس Ollama", + "Ollama Model": "مدل Ollama", + "AI Gateway Model": "مدل AI Gateway", + "Actions": "عملیات", + "Navigation": "ناوبری" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index 6a5e5c99..b908dd5b 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -952,5 +952,21 @@ "Reading Ruler": "Règle de lecture", "Enable Reading Ruler": "Activer la règle de lecture", "Lines to Highlight": "Lignes à mettre en évidence", - "Ruler Color": "Couleur de la règle" + "Ruler Color": "Couleur de la règle", + "Command Palette": "Palette de commandes", + "Search settings and actions...": "Rechercher des paramètres et des actions...", + "No results found for": "Aucun résultat trouvé pour", + "Type to search settings and actions": "Saisissez pour rechercher des paramètres et des actions", + "Recent": "Récent", + "navigate": "naviguer", + "select": "sélectionner", + "close": "fermer", + "Search Settings": "Rechercher dans les paramètres", + "Page Margins": "Marges de page", + "AI Provider": "Fournisseur d'IA", + "Ollama URL": "URL Ollama", + "Ollama Model": "Modèle Ollama", + "AI Gateway Model": "Modèle AI Gateway", + "Actions": "Actions", + "Navigation": "Navigation" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index 0fc8d921..37126d9b 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -942,5 +942,21 @@ "Reading Ruler": "पठन रूलर", "Enable Reading Ruler": "पठन रूलर सक्षम करें", "Lines to Highlight": "हाइलाइट करने के लिए पंक्तियाँ", - "Ruler Color": "रूलर का रंग" + "Ruler Color": "रूलर का रंग", + "Command Palette": "कमांड पैलेट", + "Search settings and actions...": "सेटिंग और क्रियाएं खोजें...", + "No results found for": "के लिए कोई परिणाम नहीं मिला", + "Type to search settings and actions": "सेटिंग और क्रियाएं खोजने के लिए टाइप करें", + "Recent": "हालिया", + "navigate": "नेविगेट", + "select": "चुनें", + "close": "बंद करें", + "Search Settings": "सेटिंग खोजें", + "Page Margins": "पेज मार्जिन", + "AI Provider": "AI प्रदाता", + "Ollama URL": "Ollama URL", + "Ollama Model": "Ollama मॉडल", + "AI Gateway Model": "AI गेटवे मॉडल", + "Actions": "क्रियाएं", + "Navigation": "नेविगेशन" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index 65c6403e..062dd570 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -932,5 +932,21 @@ "Reading Ruler": "Penggaris Membaca", "Enable Reading Ruler": "Aktifkan Penggaris Membaca", "Lines to Highlight": "Baris untuk Disorot", - "Ruler Color": "Warna Penggaris" + "Ruler Color": "Warna Penggaris", + "Command Palette": "Palet Perintah", + "Search settings and actions...": "Cari pengaturan dan tindakan...", + "No results found for": "Tidak ada hasil ditemukan untuk", + "Type to search settings and actions": "Ketik untuk mencari pengaturan dan tindakan", + "Recent": "Terbaru", + "navigate": "navigasi", + "select": "pilih", + "close": "tutup", + "Search Settings": "Cari Pengaturan", + "Page Margins": "Margin Halaman", + "AI Provider": "Penyedia AI", + "Ollama URL": "URL Ollama", + "Ollama Model": "Model Ollama", + "AI Gateway Model": "Model AI Gateway", + "Actions": "Tindakan", + "Navigation": "Navigasi" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index fd1156ca..4a0f5084 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -952,5 +952,21 @@ "Reading Ruler": "Righello di lettura", "Enable Reading Ruler": "Abilita righello di lettura", "Lines to Highlight": "Righe da evidenziare", - "Ruler Color": "Colore del righello" + "Ruler Color": "Colore del righello", + "Command Palette": "Tavolozza dei comandi", + "Search settings and actions...": "Cerca impostazioni e azioni...", + "No results found for": "Nessun risultato trovato per", + "Type to search settings and actions": "Digita per cercare impostazioni e azioni", + "Recent": "Recenti", + "navigate": "naviga", + "select": "seleziona", + "close": "chiudi", + "Search Settings": "Cerca impostazioni", + "Page Margins": "Margini della pagina", + "AI Provider": "Fornitore AI", + "Ollama URL": "URL Ollama", + "Ollama Model": "Modello Ollama", + "AI Gateway Model": "Modello AI Gateway", + "Actions": "Azioni", + "Navigation": "Navigazione" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index da056a05..82a62e4c 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -932,5 +932,21 @@ "Reading Ruler": "リーディングルーラー", "Enable Reading Ruler": "リーディングルーラーを有効にする", "Lines to Highlight": "ハイライトする行数", - "Ruler Color": "ルーラーの色" + "Ruler Color": "ルーラーの色", + "Command Palette": "コマンドパレット", + "Search settings and actions...": "設定とアクションを検索...", + "No results found for": "の結果が見つかりませんでした", + "Type to search settings and actions": "入力して設定とアクションを検索", + "Recent": "最近使った項目", + "navigate": "ナビゲート", + "select": "選択", + "close": "閉じる", + "Search Settings": "設定を検索", + "Page Margins": "ページ余白", + "AI Provider": "AIプロバイダー", + "Ollama URL": "OllamaのURL", + "Ollama Model": "Ollamaモデル", + "AI Gateway Model": "AI Gatewayモデル", + "Actions": "アクション", + "Navigation": "ナビゲーション" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index fedd2135..e83ed806 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -932,5 +932,21 @@ "Reading Ruler": "읽기 자", "Enable Reading Ruler": "읽기 자 활성화", "Lines to Highlight": "강조할 줄 수", - "Ruler Color": "자 색상" + "Ruler Color": "자 색상", + "Command Palette": "명령 팔레트", + "Search settings and actions...": "설정 및 작업 검색...", + "No results found for": "에 대한 결과를 찾을 수 없음", + "Type to search settings and actions": "입력하여 설정 및 작업 검색", + "Recent": "최근", + "navigate": "탐색", + "select": "선택", + "close": "닫기", + "Search Settings": "설정 검색", + "Page Margins": "페이지 여백", + "AI Provider": "AI 제공처", + "Ollama URL": "Ollama URL", + "Ollama Model": "Ollama 모델", + "AI Gateway Model": "AI 게이트웨이 모델", + "Actions": "작업", + "Navigation": "탐색" } diff --git a/apps/readest-app/public/locales/ms/translation.json b/apps/readest-app/public/locales/ms/translation.json index 3432c2b0..cc2e3635 100644 --- a/apps/readest-app/public/locales/ms/translation.json +++ b/apps/readest-app/public/locales/ms/translation.json @@ -932,5 +932,21 @@ "Reading Ruler": "Pembaris Membaca", "Enable Reading Ruler": "Dayakan Pembaris Membaca", "Lines to Highlight": "Baris untuk Diserlahkan", - "Ruler Color": "Warna Pembaris" + "Ruler Color": "Warna Pembaris", + "Command Palette": "Palet Perintah", + "Search settings and actions...": "Cari tetapan dan tindakan...", + "No results found for": "Tiada hasil dijumpai untuk", + "Type to search settings and actions": "Taip untuk mencari tetapan dan tindakan", + "Recent": "Baru-baru ini", + "navigate": "navigasi", + "select": "pilih", + "close": "tutup", + "Search Settings": "Cari Tetapan", + "Page Margins": "Margin Halaman", + "AI Provider": "Penyedia AI", + "Ollama URL": "URL Ollama", + "Ollama Model": "Model Ollama", + "AI Gateway Model": "Model AI Gateway", + "Actions": "Tindakan", + "Navigation": "Navigasi" } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index 2f82c1a9..352c5a5f 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -942,5 +942,21 @@ "Reading Ruler": "Leesliniaal", "Enable Reading Ruler": "Leesliniaal inschakelen", "Lines to Highlight": "Regels om te markeren", - "Ruler Color": "Kleur van liniaal" + "Ruler Color": "Kleur van liniaal", + "Command Palette": "Opdrachtenpalet", + "Search settings and actions...": "Zoek instellingen en acties...", + "No results found for": "Geen resultaten gevonden voor", + "Type to search settings and actions": "Typ om instellingen en acties te zoeken", + "Recent": "Recent", + "navigate": "navigeren", + "select": "selecteren", + "close": "sluiten", + "Search Settings": "Instellingen zoeken", + "Page Margins": "Paginamarges", + "AI Provider": "AI-provider", + "Ollama URL": "Ollama-URL", + "Ollama Model": "Ollama-model", + "AI Gateway Model": "AI Gateway-model", + "Actions": "Acties", + "Navigation": "Navigatie" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index d985f0e1..cb373441 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -962,5 +962,21 @@ "Reading Ruler": "Linijka do czytania", "Enable Reading Ruler": "Włącz linijkę do czytania", "Lines to Highlight": "Linie do wyróżnienia", - "Ruler Color": "Kolor linijki" + "Ruler Color": "Kolor linijki", + "Command Palette": "Paleta poleceń", + "Search settings and actions...": "Szukaj ustawień i akcji...", + "No results found for": "Brak wyników dla", + "Type to search settings and actions": "Wpisz, aby szukać ustawień i akcji", + "Recent": "Ostatnie", + "navigate": "nawiguj", + "select": "wybierz", + "close": "zamknij", + "Search Settings": "Szukaj ustawień", + "Page Margins": "Marginesy strony", + "AI Provider": "Dostawca AI", + "Ollama URL": "URL Ollama", + "Ollama Model": "Model Ollama", + "AI Gateway Model": "Model AI Gateway", + "Actions": "Akcje", + "Navigation": "Nawigacja" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index 18c99d07..e71a54cc 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -952,5 +952,21 @@ "Reading Ruler": "Régua de leitura", "Enable Reading Ruler": "Ativar régua de leitura", "Lines to Highlight": "Linhas para destacar", - "Ruler Color": "Cor da régua" + "Ruler Color": "Cor da régua", + "Command Palette": "Paleta de Comandos", + "Search settings and actions...": "Pesquisar definições e ações...", + "No results found for": "Nenhum resultado encontrado para", + "Type to search settings and actions": "Digite para pesquisar definições e ações", + "Recent": "Recente", + "navigate": "navegar", + "select": "selecionar", + "close": "fechar", + "Search Settings": "Pesquisar Definições", + "Page Margins": "Margens da Página", + "AI Provider": "Fornecedor de IA", + "Ollama URL": "URL do Ollama", + "Ollama Model": "Modelo do Ollama", + "AI Gateway Model": "Modelo de AI Gateway", + "Actions": "Ações", + "Navigation": "Navegação" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index cd173aa7..d3e30234 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -962,5 +962,21 @@ "Reading Ruler": "Линейка для чтения", "Enable Reading Ruler": "Включить линейку для чтения", "Lines to Highlight": "Строк для выделения", - "Ruler Color": "Цвет линейки" + "Ruler Color": "Цвет линейки", + "Command Palette": "Палитра команд", + "Search settings and actions...": "Поиск настроек и действий...", + "No results found for": "Результатов не найдено для", + "Type to search settings and actions": "Введите для поиска настроек и действий", + "Recent": "Недавние", + "navigate": "навигация", + "select": "выбрать", + "close": "закрыть", + "Search Settings": "Поиск настроек", + "Page Margins": "Поля страницы", + "AI Provider": "Провайдер ИИ", + "Ollama URL": "URL Ollama", + "Ollama Model": "Модель Ollama", + "AI Gateway Model": "Модель AI Gateway", + "Actions": "Действия", + "Navigation": "Навигация" } diff --git a/apps/readest-app/public/locales/si/translation.json b/apps/readest-app/public/locales/si/translation.json index fa04cadb..725f2197 100644 --- a/apps/readest-app/public/locales/si/translation.json +++ b/apps/readest-app/public/locales/si/translation.json @@ -942,5 +942,21 @@ "Reading Ruler": "කියවීමේ රූල", "Enable Reading Ruler": "කියවීමේ රූල සක්‍රීය කරන්න", "Lines to Highlight": "මතුකර පෙන්විය යුතු පේළි", - "Ruler Color": "රූලේ වර්ණය" + "Ruler Color": "රූලේ වර්ණය", + "Command Palette": "විධාන පුවරුව", + "Search settings and actions...": "සැකසුම් සහ ක්‍රියා සොයන්න...", + "No results found for": "සඳහා ප්‍රතිඵල හමු නොවීය", + "Type to search settings and actions": "සැකසුම් සහ ක්‍රියා සෙවීමට ටයිප් කරන්න", + "Recent": "මෑත කාලීන", + "navigate": "සංචාලනය", + "select": "තෝරන්න", + "close": "වසා දමන්න", + "Search Settings": "සැකසුම් සොයන්න", + "Page Margins": "පිටු මායිම්", + "AI Provider": "AI සපයන්නා", + "Ollama URL": "Ollama URL", + "Ollama Model": "Ollama ආකෘතිය", + "AI Gateway Model": "AI ਗੇਟਵੇ ਆਕෘතිය", + "Actions": "ක්‍රියා", + "Navigation": "සංචාලනය" } diff --git a/apps/readest-app/public/locales/sv/translation.json b/apps/readest-app/public/locales/sv/translation.json index fada91e2..6aa1dd9b 100644 --- a/apps/readest-app/public/locales/sv/translation.json +++ b/apps/readest-app/public/locales/sv/translation.json @@ -942,5 +942,21 @@ "Reading Ruler": "Läs-linjal", "Enable Reading Ruler": "Aktivera läs-linjal", "Lines to Highlight": "Rader att markera", - "Ruler Color": "Linjalens färg" + "Ruler Color": "Linjalens färg", + "Command Palette": "Kommandopalett", + "Search settings and actions...": "Sök inställningar och åtgärder...", + "No results found for": "Inga resultat hittades för", + "Type to search settings and actions": "Skriv för att söka inställningar och åtgärder", + "Recent": "Senaste", + "navigate": "navigera", + "select": "välj", + "close": "stäng", + "Search Settings": "Sök inställningar", + "Page Margins": "Sidmarginaler", + "AI Provider": "AI-leverantör", + "Ollama URL": "Ollama-URL", + "Ollama Model": "Ollama-modell", + "AI Gateway Model": "AI Gateway-modell", + "Actions": "Åtgärder", + "Navigation": "Navigering" } diff --git a/apps/readest-app/public/locales/ta/translation.json b/apps/readest-app/public/locales/ta/translation.json index 76192f7a..af2d4097 100644 --- a/apps/readest-app/public/locales/ta/translation.json +++ b/apps/readest-app/public/locales/ta/translation.json @@ -942,5 +942,21 @@ "Reading Ruler": "வாசிப்பு அளவுகோல்", "Enable Reading Ruler": "வாசிப்பு அளவுகோலை இயக்கு", "Lines to Highlight": "சிறப்பித்துக் காட்ட வேண்டிய வரிகள்", - "Ruler Color": "அளவுகோல் வண்ணம்" + "Ruler Color": "அளவுகோல் வண்ணம்", + "Command Palette": "கட்டளை தட்டு", + "Search settings and actions...": "அமைப்புகள் மற்றும் செயல்களைத் தேடுங்கள்...", + "No results found for": "இதற்கு முடிவுகள் எதுவும் கிடைக்கவில்லை", + "Type to search settings and actions": "அமைப்புகள் மற்றும் செயல்களைத் தேட தட்டச்சு செய்க", + "Recent": "சமீபத்திய", + "navigate": "வழிசெலுத்து", + "select": "தேர்ந்தெடு", + "close": "மூடு", + "Search Settings": "அமைப்புகளைத் தேடு", + "Page Margins": "பக்க விளிம்புகள்", + "AI Provider": "AI வழங்குநர்", + "Ollama URL": "Ollama URL", + "Ollama Model": "Ollama மாதிரி", + "AI Gateway Model": "AI கேட்வே மாதிரி", + "Actions": "செயல்கள்", + "Navigation": "வழிசெலுத்தல்" } diff --git a/apps/readest-app/public/locales/th/translation.json b/apps/readest-app/public/locales/th/translation.json index 1ceef02f..97e54544 100644 --- a/apps/readest-app/public/locales/th/translation.json +++ b/apps/readest-app/public/locales/th/translation.json @@ -932,5 +932,21 @@ "Reading Ruler": "ไม้บรรทัดการอ่าน", "Enable Reading Ruler": "เปิดใช้งานไม้บรรทัดการอ่าน", "Lines to Highlight": "บรรทัดที่ต้องการเน้น", - "Ruler Color": "สีไม้บรรทัด" + "Ruler Color": "สีไม้บรรทัด", + "Command Palette": "พาเลทคำสั่ง", + "Search settings and actions...": "ค้นหาการตั้งค่าและคำสั่ง...", + "No results found for": "ไม่พบผลลัพธ์สำหรับ", + "Type to search settings and actions": "พิมพ์เพื่อค้นหาการตั้งค่าและคำสั่ง", + "Recent": "ล่าสุด", + "navigate": "นำทาง", + "select": "เลือก", + "close": "ปิด", + "Search Settings": "ค้นหาการตั้งค่า", + "Page Margins": "ระยะขอบหน้า", + "AI Provider": "ผู้ให้บริการ AI", + "Ollama URL": "Ollama URL", + "Ollama Model": "โมเดล Ollama", + "AI Gateway Model": "โมเดล AI Gateway", + "Actions": "การกระทำ", + "Navigation": "การนำทาง" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index 8dbd838b..a2a5a2a8 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -942,5 +942,21 @@ "Reading Ruler": "Okuma Cetveli", "Enable Reading Ruler": "Okuma Cetvelini Etkinleştir", "Lines to Highlight": "Vurgulanacak Satırlar", - "Ruler Color": "Cetvel Rengi" + "Ruler Color": "Cetvel Rengi", + "Command Palette": "Komut Paleti", + "Search settings and actions...": "Ayarları ve eylemleri ara...", + "No results found for": "Şunun için sonuç bulunamadı", + "Type to search settings and actions": "Ayarları ve eylemleri aramak için yazın", + "Recent": "Son kullanılanlar", + "navigate": "gezin", + "select": "seç", + "close": "kapat", + "Search Settings": "Ayarları Ara", + "Page Margins": "Sayfa Kenar Boşlukları", + "AI Provider": "Yapay Zeka Sağlayıcısı", + "Ollama URL": "Ollama URL", + "Ollama Model": "Ollama Modeli", + "AI Gateway Model": "AI Gateway Modeli", + "Actions": "Eylemler", + "Navigation": "Gezين" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index bc8f9994..21292a26 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -962,5 +962,21 @@ "Reading Ruler": "Лінійка для читання", "Enable Reading Ruler": "Увімкнути лінійку для читання", "Lines to Highlight": "Рядки для виділення", - "Ruler Color": "Коліර් лінійки" + "Ruler Color": "Коліර් лінійки", + "Command Palette": "Палітра команд", + "Search settings and actions...": "Пошук налаштувань та дій...", + "No results found for": "Результатів не знайдено для", + "Type to search settings and actions": "Введіть для пошуку налаштувань та дій", + "Recent": "Нещодавні", + "navigate": "навігація", + "select": "вибрати", + "close": "закрити", + "Search Settings": "Пошук налаштувань", + "Page Margins": "Поля сторінки", + "AI Provider": "Провайдер ШІ", + "Ollama URL": "URL Ollama", + "Ollama Model": "Модель Ollama", + "AI Gateway Model": "Модель AI Gateway", + "Actions": "Дії", + "Navigation": "Навігація" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index 7a0d9e9b..3028d1b8 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -932,5 +932,21 @@ "Reading Ruler": "Thước đọc", "Enable Reading Ruler": "Bật thước đọc", "Lines to Highlight": "Số dòng cần làm nổi bật", - "Ruler Color": "Màu thước" + "Ruler Color": "Màu thước", + "Command Palette": "Bảng lệnh", + "Search settings and actions...": "Tìm kiếm cài đặt và hành động...", + "No results found for": "Không tìm thấy kết quả cho", + "Type to search settings and actions": "Nhập để tìm kiếm cài đặt và hành động", + "Recent": "Gần đây", + "navigate": "điều hướng", + "select": "chọn", + "close": "đóng", + "Search Settings": "Tìm kiếm cài đặt", + "Page Margins": "Lề trang", + "AI Provider": "Nhà cung cấp AI", + "Ollama URL": "URL Ollama", + "Ollama Model": "Mô hình Ollama", + "AI Gateway Model": "Mô hình AI Gateway", + "Actions": "Hành động", + "Navigation": "Điều hướng" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index 75610902..b87c6bd5 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -932,5 +932,21 @@ "Reading Ruler": "阅读尺", "Enable Reading Ruler": "启用阅读尺", "Lines to Highlight": "突出显示行数", - "Ruler Color": "尺子颜色" + "Ruler Color": "尺子颜色", + "Command Palette": "命令面板", + "Search settings and actions...": "搜索设置和操作...", + "No results found for": "未找到结果:", + "Type to search settings and actions": "输入以搜索设置和操作", + "Recent": "最近", + "navigate": "导航", + "select": "选择", + "close": "关闭", + "Search Settings": "搜索设置", + "Page Margins": "页边距", + "AI Provider": "AI 服务商", + "Ollama URL": "Ollama URL", + "Ollama Model": "Ollama 模型", + "AI Gateway Model": "AI Gateway 模型", + "Actions": "操作", + "Navigation": "导航" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index 3fbeada6..738c5ea6 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -932,5 +932,21 @@ "Reading Ruler": "閱讀尺", "Enable Reading Ruler": "啟用閱讀尺", "Lines to Highlight": "突出顯示行數", - "Ruler Color": "尺子顏色" + "Ruler Color": "尺子顏色", + "Command Palette": "命令面板", + "Search settings and actions...": "搜尋設定和操作...", + "No results found for": "未找到結果:", + "Type to search settings and actions": "輸入以搜尋設定和操作", + "Recent": "最近", + "navigate": "導覽", + "select": "選擇", + "close": "關閉", + "Search Settings": "搜尋設定", + "Page Margins": "頁邊距", + "AI Provider": "AI 服務商", + "Ollama URL": "Ollama URL", + "Ollama Model": "Ollama 模型", + "AI Gateway Model": "AI Gateway 模型", + "Actions": "操作", + "Navigation": "導覽" } diff --git a/apps/readest-app/src/app/reader/hooks/useBookShortcuts.ts b/apps/readest-app/src/app/reader/hooks/useBookShortcuts.ts index e4630ac1..70a86033 100644 --- a/apps/readest-app/src/app/reader/hooks/useBookShortcuts.ts +++ b/apps/readest-app/src/app/reader/hooks/useBookShortcuts.ts @@ -5,11 +5,12 @@ import { isTauriAppPlatform } from '@/services/environment'; import { useSidebarStore } from '@/store/sidebarStore'; import { useSettingsStore } from '@/store/settingsStore'; import { useBookDataStore } from '@/store/bookDataStore'; -import { getStyles } from '@/utils/style'; +import { useCommandPalette } from '@/components/command-palette'; import { tauriHandleClose, tauriHandleToggleFullScreen, tauriQuitApp } from '@/utils/window'; import { eventDispatcher } from '@/utils/event'; import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL, ZOOM_STEP } from '@/services/constants'; import { viewPagination } from './usePagination'; +import { getStyles } from '@/utils/style'; import useShortcuts from '@/hooks/useShortcuts'; import useBooksManager from './useBooksManager'; @@ -25,6 +26,7 @@ const useBookShortcuts = ({ sideBarBookKey, bookKeys }: UseBookShortcutsProps) = const { getBookData } = useBookDataStore(); const { toggleNotebook } = useNotebookStore(); const { getNextBookKey } = useBooksManager(); + const { open: openCommandPalette } = useCommandPalette(); const viewSettings = getViewSettings(sideBarBookKey ?? ''); const fontSize = viewSettings?.defaultFontSize ?? 16; const lineHeight = viewSettings?.lineHeight ?? 1.6; @@ -254,6 +256,7 @@ const useBookShortcuts = ({ sideBarBookKey, bookKeys }: UseBookShortcutsProps) = onZoomIn: zoomIn, onZoomOut: zoomOut, onResetZoom: resetZoom, + onOpenCommandPalette: openCommandPalette, }, [sideBarBookKey, bookKeys], ); diff --git a/apps/readest-app/src/components/Providers.tsx b/apps/readest-app/src/components/Providers.tsx index fd89c3fb..3594ca89 100644 --- a/apps/readest-app/src/components/Providers.tsx +++ b/apps/readest-app/src/components/Providers.tsx @@ -16,6 +16,7 @@ import { useBackgroundTexture } from '@/hooks/useBackgroundTexture'; import { useEinkMode } from '@/hooks/useEinkMode'; import { getLocale } from '@/utils/misc'; import { getDirFromUILanguage } from '@/utils/rtl'; +import { CommandPaletteProvider, CommandPalette } from '@/components/command-palette'; const Providers = ({ children }: { children: React.ReactNode }) => { const { envConfig, appService } = useEnv(); @@ -67,7 +68,12 @@ const Providers = ({ children }: { children: React.ReactNode }) => { - {children} + + + {children} + + + diff --git a/apps/readest-app/src/components/command-palette/CommandPalette.tsx b/apps/readest-app/src/components/command-palette/CommandPalette.tsx new file mode 100644 index 00000000..cebeac72 --- /dev/null +++ b/apps/readest-app/src/components/command-palette/CommandPalette.tsx @@ -0,0 +1,315 @@ +'use client'; + +import clsx from 'clsx'; +import React, { useEffect, useRef, useCallback, useMemo, useState } from 'react'; +import { FiSearch } from 'react-icons/fi'; +import { MdClose } from 'react-icons/md'; +import { useTranslation } from '@/hooks/useTranslation'; +import { useCommandPalette } from './CommandPaletteProvider'; +import { CommandSearchResult, getCategoryLabel, CommandCategory } from '@/services/commandRegistry'; +import HighlightChars from './HighlightChars'; + +const CommandPalette: React.FC = () => { + const _ = useTranslation(); + const { isOpen, close, query, setQuery, results, groupedResults, recentItems, executeCommand } = + useCommandPalette(); + + const inputRef = useRef(null); + const listRef = useRef(null); + const [selectedIndex, setSelectedIndex] = useState(0); + const previousActiveElementRef = useRef(null); + + // flatten results for keyboard navigation + const flattenedResults = useMemo(() => { + if (query.trim()) { + return results; + } + // show recent items when no query + return recentItems.map((item) => ({ + item, + score: 0, + positions: new Set(), + highlightIndices: new Set(), + })); + }, [query, results, recentItems]); + + // reset selection when results change - legitimate derived state reset + useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect -- resetting UI state on data change is intended + setSelectedIndex(0); + }, [flattenedResults.length]); + + // focus input and reset selection on open + useEffect(() => { + if (isOpen) { + previousActiveElementRef.current = document.activeElement as HTMLElement; + // eslint-disable-next-line react-hooks/set-state-in-effect -- resetting UI state on modal open is intended + setSelectedIndex(0); + setTimeout(() => { + inputRef.current?.focus(); + }, 50); + } else { + if (previousActiveElementRef.current) { + previousActiveElementRef.current.focus(); + previousActiveElementRef.current = null; + } + } + }, [isOpen]); + + // scroll selected item into view + useEffect(() => { + if (!listRef.current) return; + const selectedItem = listRef.current.querySelector('[data-selected="true"]'); + if (selectedItem) { + selectedItem.scrollIntoView({ block: 'nearest' }); + } + }, [selectedIndex]); + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + switch (e.key) { + case 'ArrowDown': + e.preventDefault(); + setSelectedIndex((prev) => Math.min(prev + 1, flattenedResults.length - 1)); + break; + case 'ArrowUp': + e.preventDefault(); + setSelectedIndex((prev) => Math.max(prev - 1, 0)); + break; + case 'Enter': + e.preventDefault(); + { + const selected = flattenedResults[selectedIndex]; + if (selected) { + executeCommand(selected.item); + } + } + break; + case 'Escape': + e.preventDefault(); + close(); + break; + case 'Tab': + // trap focus within the palette + e.preventDefault(); + break; + } + }, + [flattenedResults, selectedIndex, executeCommand, close], + ); + + const handleItemClick = useCallback( + (result: CommandSearchResult) => { + executeCommand(result.item); + }, + [executeCommand], + ); + + const handleBackdropClick = useCallback( + (e: React.MouseEvent) => { + if (e.target === e.currentTarget) { + close(); + } + }, + [close], + ); + + if (!isOpen) return null; + + const showEmptyState = query.trim() && flattenedResults.length === 0; + const showRecentState = !query.trim() && recentItems.length > 0; + const showNoRecentState = !query.trim() && recentItems.length === 0; + + // group results by category for display + const orderedCategories: CommandCategory[] = ['settings', 'actions', 'navigation']; + + return ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions +
+ {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */} +
+ {/* search input */} +
+ + setQuery(e.target.value)} + spellCheck={false} + autoComplete='off' + autoCorrect='off' + autoCapitalize='off' + /> + {query && ( + + )} +
+ + {/* results list */} +
+ {showEmptyState && ( +
+ {_('No results found for')} "{query}" +
+ )} + + {showNoRecentState && ( +
+ {_('Type to search settings and actions')} +
+ )} + + {showRecentState && ( +
+
+ {_('Recent')} +
+ {flattenedResults.map((result, index) => ( + handleItemClick(result)} + onMouseEnter={() => setSelectedIndex(index)} + _={_} + /> + ))} +
+ )} + + {query.trim() && flattenedResults.length > 0 && ( + <> + {orderedCategories.map((category) => { + const categoryResults = groupedResults[category]; + if (categoryResults.length === 0) return null; + + const startIndex = flattenedResults.findIndex((r) => r.item.category === category); + + return ( +
+
+ {getCategoryLabel(_, category)} +
+ {categoryResults.map((result, catIndex) => { + const globalIndex = startIndex + catIndex; + return ( + handleItemClick(result)} + onMouseEnter={() => setSelectedIndex(globalIndex)} + _={_} + /> + ); + })} +
+ ); + })} + + )} +
+ + {/* footer with keyboard hints */} +
+
+ + ↑↓ {_('navigate')} + + + {_('select')} + + + esc {_('close')} + +
+
+
+
+ ); +}; + +interface CommandResultItemProps { + result: CommandSearchResult; + isSelected: boolean; + onClick: () => void; + onMouseEnter: () => void; + _: (key: string) => string; +} + +const CommandResultItem: React.FC = ({ + result, + isSelected, + onClick, + onMouseEnter, + _, +}) => { + const { item, highlightIndices, matchContext } = result; + const Icon = item.icon; + + return ( + + ); +}; + +export default CommandPalette; diff --git a/apps/readest-app/src/components/command-palette/CommandPaletteProvider.tsx b/apps/readest-app/src/components/command-palette/CommandPaletteProvider.tsx new file mode 100644 index 00000000..df83771c --- /dev/null +++ b/apps/readest-app/src/components/command-palette/CommandPaletteProvider.tsx @@ -0,0 +1,239 @@ +'use client'; + +import React, { createContext, useContext, useState, useCallback, useEffect, useMemo } from 'react'; +import { useTranslation } from '@/hooks/useTranslation'; +import { useSettingsStore } from '@/store/settingsStore'; +import { useThemeStore } from '@/store/themeStore'; +import { useEnv } from '@/context/EnvContext'; +import { isTauriAppPlatform } from '@/services/environment'; +import { tauriHandleSetAlwaysOnTop, tauriHandleToggleFullScreen } from '@/utils/window'; +import { setAboutDialogVisible } from '@/components/AboutWindow'; +import { saveSysSettings } from '@/helpers/settings'; +import { SettingsPanelType } from '@/components/settings/SettingsDialog'; +import { + CommandItem, + buildCommandRegistry, + searchCommands, + CommandSearchResult, + groupResultsByCategory, + trackCommandUsage, + getRecentCommands, + CommandCategory, +} from '@/services/commandRegistry'; + +interface CommandPaletteContextValue { + isOpen: boolean; + open: () => void; + close: () => void; + toggle: () => void; + query: string; + setQuery: (query: string) => void; + results: CommandSearchResult[]; + groupedResults: Record; + recentItems: CommandItem[]; + executeCommand: (item: CommandItem) => void; + commandItems: CommandItem[]; +} + +const CommandPaletteContext = createContext(null); + +export const useCommandPalette = (): CommandPaletteContextValue => { + const context = useContext(CommandPaletteContext); + if (!context) { + throw new Error('useCommandPalette must be used within CommandPaletteProvider'); + } + return context; +}; + +interface CommandPaletteProviderProps { + children: React.ReactNode; +} + +export const CommandPaletteProvider: React.FC = ({ children }) => { + const _ = useTranslation(); + const { envConfig, appService } = useEnv(); + const { themeMode, setThemeMode } = useThemeStore(); + const { settings, setSettingsDialogOpen, setActiveSettingsItemId } = useSettingsStore(); + + const [isOpen, setIsOpen] = useState(false); + const [query, setQuery] = useState(''); + + const isDesktop = isTauriAppPlatform() && !appService?.isMobile; + + // action handlers + const toggleTheme = useCallback(() => { + const nextMode = themeMode === 'auto' ? 'light' : themeMode === 'light' ? 'dark' : 'auto'; + setThemeMode(nextMode); + }, [themeMode, setThemeMode]); + + const toggleFullscreen = useCallback(() => { + tauriHandleToggleFullScreen(); + }, []); + + const toggleAlwaysOnTop = useCallback(() => { + const newValue = !settings.alwaysOnTop; + saveSysSettings(envConfig, 'alwaysOnTop', newValue); + tauriHandleSetAlwaysOnTop(newValue); + }, [envConfig, settings.alwaysOnTop]); + + const toggleScreenWakeLock = useCallback(() => { + const newValue = !settings.screenWakeLock; + saveSysSettings(envConfig, 'screenWakeLock', newValue); + }, [envConfig, settings.screenWakeLock]); + + const toggleAutoUpload = useCallback(() => { + const newValue = !settings.autoUpload; + saveSysSettings(envConfig, 'autoUpload', newValue); + }, [envConfig, settings.autoUpload]); + + const reloadPage = useCallback(() => { + window.location.reload(); + }, []); + + const toggleOpenLastBooks = useCallback(() => { + const newValue = !settings.openLastBooks; + saveSysSettings(envConfig, 'openLastBooks', newValue); + }, [envConfig, settings.openLastBooks]); + + const showAbout = useCallback(() => { + setAboutDialogVisible(true); + }, []); + + const toggleTelemetry = useCallback(() => { + const newValue = !settings.telemetryEnabled; + saveSysSettings(envConfig, 'telemetryEnabled', newValue); + }, [envConfig, settings.telemetryEnabled]); + + const openSettingsPanel = useCallback( + (_panel: SettingsPanelType, itemId?: string) => { + // panel is encoded in itemId (e.g., 'settings.font.defaultFontSize') + // SettingsDialog will parse this to determine which panel to show + if (itemId) { + setActiveSettingsItemId(itemId); + } + setSettingsDialogOpen(true); + }, + [setSettingsDialogOpen, setActiveSettingsItemId], + ); + + // build command registry + const commandItems = useMemo( + () => + buildCommandRegistry({ + _, + openSettingsPanel, + toggleTheme, + toggleFullscreen, + toggleAlwaysOnTop, + toggleScreenWakeLock, + toggleAutoUpload, + reloadPage, + toggleOpenLastBooks, + showAbout, + toggleTelemetry, + isDesktop, + }), + [ + _, + openSettingsPanel, + toggleTheme, + toggleFullscreen, + toggleAlwaysOnTop, + toggleScreenWakeLock, + toggleAutoUpload, + reloadPage, + toggleOpenLastBooks, + showAbout, + toggleTelemetry, + isDesktop, + ], + ); + + // search results + const results = useMemo(() => searchCommands(query, commandItems), [query, commandItems]); + const groupedResults = useMemo(() => groupResultsByCategory(results), [results]); + + // recent items + const recentItems = useMemo(() => getRecentCommands(commandItems, 5), [commandItems]); + + // palette controls + const open = useCallback(() => { + setIsOpen(true); + setQuery(''); + }, []); + + const close = useCallback(() => { + setIsOpen(false); + setQuery(''); + }, []); + + const toggle = useCallback(() => { + if (isOpen) { + close(); + } else { + open(); + } + }, [isOpen, open, close]); + + // execute command + const executeCommand = useCallback( + (item: CommandItem) => { + trackCommandUsage(item.id); + close(); + // slight delay to allow modal to close before action + requestAnimationFrame(() => { + item.action(); + }); + }, + [close], + ); + + // keyboard shortcut handler (Ctrl/Cmd+Shift+P) + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + const isCmdOrCtrl = e.metaKey || e.ctrlKey; + if (isCmdOrCtrl && e.shiftKey && e.key.toLowerCase() === 'p') { + e.preventDefault(); + e.stopPropagation(); + setSettingsDialogOpen(false); + toggle(); + } + }; + + window.addEventListener('keydown', handleKeyDown, { capture: true }); + return () => window.removeEventListener('keydown', handleKeyDown, { capture: true }); + }, [toggle, setSettingsDialogOpen]); + + const value = useMemo( + () => ({ + isOpen, + open, + close, + toggle, + query, + setQuery, + results, + groupedResults, + recentItems, + executeCommand, + commandItems, + }), + [ + isOpen, + open, + close, + toggle, + query, + setQuery, + results, + groupedResults, + recentItems, + executeCommand, + commandItems, + ], + ); + + return {children}; +}; + +export default CommandPaletteProvider; diff --git a/apps/readest-app/src/components/command-palette/HighlightChars.tsx b/apps/readest-app/src/components/command-palette/HighlightChars.tsx new file mode 100644 index 00000000..0e8c4393 --- /dev/null +++ b/apps/readest-app/src/components/command-palette/HighlightChars.tsx @@ -0,0 +1,35 @@ +import React from 'react'; + +interface HighlightCharsProps { + str: string; + indices: Set; + highlightClassName?: string; +} + +const HighlightChars: React.FC = ({ + str, + indices, + highlightClassName = 'bg-primary/30 text-primary rounded-sm', +}) => { + if (indices.size === 0) { + return <>{str}; + } + + const chars = str.normalize().split(''); + + return ( + <> + {chars.map((char, i) => + indices.has(i) ? ( + + {char} + + ) : ( + {char} + ), + )} + + ); +}; + +export default HighlightChars; diff --git a/apps/readest-app/src/components/command-palette/index.ts b/apps/readest-app/src/components/command-palette/index.ts new file mode 100644 index 00000000..881a55bc --- /dev/null +++ b/apps/readest-app/src/components/command-palette/index.ts @@ -0,0 +1,3 @@ +export { default as CommandPalette } from './CommandPalette'; +export { CommandPaletteProvider, useCommandPalette } from './CommandPaletteProvider'; +export { default as HighlightChars } from './HighlightChars'; diff --git a/apps/readest-app/src/components/settings/ColorPanel.tsx b/apps/readest-app/src/components/settings/ColorPanel.tsx index 7b47214e..e63b328e 100644 --- a/apps/readest-app/src/components/settings/ColorPanel.tsx +++ b/apps/readest-app/src/components/settings/ColorPanel.tsx @@ -304,9 +304,16 @@ const ColorPanel: React.FC = ({ bookKey, onRegisterReset /> ) : ( <> - + -
+

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

= ({ bookKey, onRegisterReset />
-
+

{_('Override Book Color')}

= ({ bookKey, onRegisterReset onThemeColorChange={setThemeColor} onEditTheme={handleEditTheme} onCreateTheme={() => setShowCustomThemeEditor(true)} + data-setting-id='settings.color.themeColor' /> = ({ bookKey, onRegisterReset onSizeChange={setBackgroundSize} onImportImage={handleImportImage} onDeleteTexture={handleDeleteCustomTexture} + data-setting-id='settings.color.backgroundTexture' /> = ({ bookKey, onRegisterReset userHighlightColors={userHighlightColors} onChange={handleHighlightColorsChange} onUserColorsChange={handleUserHighlightColorsChange} + data-setting-id='settings.color.highlightColors' /> = ({ bookKey, onRegisterReset onStyleChange={handleTTSStyleChange} onColorChange={handleTTSColorChange} onCustomColorsChange={handleCustomTtsColorsChange} + data-setting-id='settings.color.ttsHighlightStyle' /> = ({ bookKey, onRegisterReset onLinesChange={setReadingRulerLines} onOpacityChange={setReadingRulerOpacity} onColorChange={setReadingRulerColor} + data-setting-id='settings.color.readingRuler' /> = ({ bookKey, onRegisterReset codeLanguage={codeLanguage} onToggle={setcodeHighlighting} onLanguageChange={setCodeLanguage} + data-setting-id='settings.color.codeHighlighting' /> )} diff --git a/apps/readest-app/src/components/settings/ControlPanel.tsx b/apps/readest-app/src/components/settings/ControlPanel.tsx index 578b6c9b..00818929 100644 --- a/apps/readest-app/src/components/settings/ControlPanel.tsx +++ b/apps/readest-app/src/components/settings/ControlPanel.tsx @@ -205,7 +205,7 @@ const ControlPanel: React.FC = ({ bookKey, onRegisterRes return (
-
+

{_('Scroll')}

@@ -219,7 +219,7 @@ const ControlPanel: React.FC = ({ bookKey, onRegisterRes onChange={() => setScrolledMode(!isScrolledMode)} />
-
+
{_('Continuous Scroll')} = ({ bookKey, onRegisterRes min={0} max={200} step={10} + data-setting-id='settings.control.overlapPixels' />
-
+

{_('Pagination')}

@@ -257,7 +258,7 @@ const ControlPanel: React.FC = ({ bookKey, onRegisterRes onChange={() => setIsDisableClick(!isDisableClick)} />
-
+
{appService?.isMobileApp ? _('Tap Both Sides') : _('Click Both Sides')} @@ -269,7 +270,7 @@ const ControlPanel: React.FC = ({ bookKey, onRegisterRes onChange={() => setFullscreenClickArea(!fullscreenClickArea)} />
-
+
{appService?.isMobileApp ? _('Swap Tap Sides') : _('Swap Click Sides')} @@ -281,7 +282,7 @@ const ControlPanel: React.FC = ({ bookKey, onRegisterRes onChange={() => setSwapClickArea(!swapClickArea)} />
-
+
{appService?.isMobileApp ? _('Disable Double Tap') : _('Disable Double Click')} @@ -307,7 +308,7 @@ const ControlPanel: React.FC = ({ bookKey, onRegisterRes
-
+

{_('Annotation Tools')}

@@ -320,7 +321,7 @@ const ControlPanel: React.FC = ({ bookKey, onRegisterRes onChange={() => setEnableAnnotationQuickActions(!enableAnnotationQuickActions)} />
-
+
{_('Quick Action')} = ({ bookKey, onRegisterRes
-
+

{_('Animation')}

@@ -360,7 +361,7 @@ const ControlPanel: React.FC = ({ bookKey, onRegisterRes
{(appService?.isMobileApp || appService?.appPlatform === 'web') && ( -
+

{_('Device')}

@@ -376,7 +377,7 @@ const ControlPanel: React.FC = ({ bookKey, onRegisterRes
)} {(appService?.isAndroidApp || appService?.appPlatform === 'web') && ( -
+
{_('Color E-Ink Mode')} = ({ bookKey, onRegisterRes
)} -
+

{_('Security')}

diff --git a/apps/readest-app/src/components/settings/FontPanel.tsx b/apps/readest-app/src/components/settings/FontPanel.tsx index 50e92b40..6ecf2280 100644 --- a/apps/readest-app/src/components/settings/FontPanel.tsx +++ b/apps/readest-app/src/components/settings/FontPanel.tsx @@ -51,6 +51,7 @@ interface FontFaceProps { moreOptions?: string[]; selected: string; onSelect: (option: string) => void; + 'data-setting-id'?: string; } const handleFontFaceFont = (option: string, family: string) => { @@ -69,10 +70,11 @@ const FontFace = ({ moreOptions, selected, onSelect, + 'data-setting-id': settingId, }: FontFaceProps) => { const _ = useTranslation(); return ( -
+
{label} = ({ bookKey, onRegisterReset return (
-
+

{_('Override Book Font')}

= ({ bookKey, onRegisterReset onChange={setDefaultFontSize} min={minFontSize} max={120} + data-setting-id='settings.font.defaultFontSize' /> = ({ bookKey, onRegisterReset onChange={setMinFontSize} min={1} max={120} + data-setting-id='settings.font.minimumFontSize' />
-
+

{_('Font Weight')}

@@ -336,7 +343,7 @@ const FontPanel: React.FC = ({ bookKey, onRegisterReset

{_('Font Family')}

-
+
{_('Default Font')} = ({ bookKey, onRegisterReset options={CJKFonts} selected={defaultCJKFont} onSelect={setDefaultCJKFont} + data-setting-id='settings.font.cjkFont' /> )}
@@ -385,6 +393,7 @@ const FontPanel: React.FC = ({ bookKey, onRegisterReset moreOptions={sysFonts} selected={serifFont} onSelect={setSerifFont} + data-setting-id='settings.font.serifFont' /> = ({ bookKey, onRegisterReset moreOptions={sysFonts} selected={sansSerifFont} onSelect={setSansSerifFont} + data-setting-id='settings.font.sansSerifFont' /> = ({ bookKey, onRegisterReset moreOptions={sysFonts} selected={monospaceFont} onSelect={setMonospaceFont} + data-setting-id='settings.font.monospaceFont' />
diff --git a/apps/readest-app/src/components/settings/LangPanel.tsx b/apps/readest-app/src/components/settings/LangPanel.tsx index f65c111d..397b308a 100644 --- a/apps/readest-app/src/components/settings/LangPanel.tsx +++ b/apps/readest-app/src/components/settings/LangPanel.tsx @@ -242,7 +242,7 @@ const LangPanel: React.FC = ({ bookKey, onRegisterReset return (
-
+

{_('Language')}

@@ -258,7 +258,7 @@ const LangPanel: React.FC = ({ bookKey, onRegisterReset
-
+

{_('Translation')}

@@ -283,7 +283,7 @@ const LangPanel: React.FC = ({ bookKey, onRegisterReset />
-
+
{_('TTS Text')} = ({ bookKey, onRegisterReset />
-
+
{_('Translate To')} = ({ bookKey, onRegisterRese />
{mightBeRTLBook && ( -
+

{_('Writing Mode')}