forked from akai/readest
a11y: add screen reader support (#2040)
Tested with VoiceOver on iOS and macOS
This commit is contained in:
@@ -81,7 +81,6 @@
|
||||
"Search...": "بحث...",
|
||||
"Select Book": "تحديد الكتاب",
|
||||
"Select Books": "تحديد الكتب",
|
||||
"Select Multiple Books": "تحديد عدة كتب",
|
||||
"Sepia": "بني داكن",
|
||||
"Serif Font": "خط مذيل (Serif)",
|
||||
"Show Book Details": "عرض تفاصيل الكتاب",
|
||||
@@ -541,8 +540,23 @@
|
||||
"Separate Cover Page": "صفحة غلاف منفصلة",
|
||||
"Resize Notebook": "تغيير حجم المفكرة",
|
||||
"Resize Sidebar": "تغيير حجم الشريط الجانبي",
|
||||
"Menu": "القائمة",
|
||||
"Dismiss": "رفض",
|
||||
"Get Help from the Readest Community": "الحصول على المساعدة من مجتمع ريديست",
|
||||
"Remove cover image": "إزالة صورة الغلاف"
|
||||
"Remove cover image": "إزالة صورة الغلاف",
|
||||
"Bookshelf": "رف الكتب",
|
||||
"View Menu": "عرض القائمة",
|
||||
"Settings Menu": "إعدادات القائمة",
|
||||
"View account details and quota": "عرض تفاصيل الحساب والحصة المخصصة",
|
||||
"Library Header": "رأس المكتبة",
|
||||
"Books Grid": "شبكة الكتب",
|
||||
"Book Content": "محتوى الكتاب",
|
||||
"Footer Bar": "شريط التذييل",
|
||||
"Header Bar": "شريط الرأس",
|
||||
"View Options": "خيارات العرض",
|
||||
"Progress Information": "معلومات التقدم",
|
||||
"Book Menu": "قائمة الكتاب",
|
||||
"Search Options": "خيارات البحث",
|
||||
"Close": "إغلاق",
|
||||
"Delete Book Options": "خيارات حذف الكتاب",
|
||||
"ON": "تشغيل",
|
||||
"OFF": "إيقاف"
|
||||
}
|
||||
|
||||
@@ -66,7 +66,6 @@
|
||||
"Search Books...": "বই খুঁজুন...",
|
||||
"Clear Search": "খোঁজ পরিষ্কার করুন",
|
||||
"Import Books": "বই আমদানি করুন",
|
||||
"Select Multiple Books": "একাধিক বই নির্বাচন করুন",
|
||||
"Select Books": "বই নির্বাচন করুন",
|
||||
"Deselect": "অনির্বাচিত করুন",
|
||||
"Select All": "সব নির্বাচন করুন",
|
||||
@@ -525,8 +524,23 @@
|
||||
"Separate Cover Page": "আলাদা কভার পৃষ্ঠা",
|
||||
"Resize Notebook": "নোটবুকের আকার পরিবর্তন",
|
||||
"Resize Sidebar": "সাইডবারের আকার পরিবর্তন",
|
||||
"Menu": "মেনু",
|
||||
"Dismiss": "বাতিল",
|
||||
"Get Help from the Readest Community": "রিডেস্ট কমিউনিটি থেকে সাহায্য নিন",
|
||||
"Remove cover image": "মলাট ছবি সরান"
|
||||
"Remove cover image": "মলাট ছবি সরান",
|
||||
"Bookshelf": "বইয়ের তাক",
|
||||
"View Menu": "মেনু দেখুন",
|
||||
"Settings Menu": "সেটিংস মেনু",
|
||||
"View account details and quota": "অ্যাকাউন্টের বিবরণ এবং কোটার তথ্য দেখুন",
|
||||
"Library Header": "লাইব্রেরি হেডার",
|
||||
"Books Grid": "বইয়ের গ্রিড",
|
||||
"Book Content": "বইয়ের বিষয়বস্তু",
|
||||
"Footer Bar": "ফুটার বার",
|
||||
"Header Bar": "হেডার বার",
|
||||
"View Options": "দেখার অপশন",
|
||||
"Progress Information": "অগ্রগতির তথ্য",
|
||||
"Book Menu": "বইয়ের মেনু",
|
||||
"Search Options": "অনুসন্ধানের অপশন",
|
||||
"Close": "বন্ধ করুন",
|
||||
"Delete Book Options": "বই মুছে ফেলার অপশন",
|
||||
"ON": "চালু",
|
||||
"OFF": "বন্ধ"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "འཚོལ་བཤེར།...",
|
||||
"Select Book": "དཔེ་དེབ་འདེམས་པ།",
|
||||
"Select Books": "དཔེ་དེབ་འདེམས་པ།",
|
||||
"Select Multiple Books": "དཔེ་དེབ་མང་པོ་འདེམས་པ།",
|
||||
"Sepia": "རྙིང་པའི་ཉམས་འགྱུར།",
|
||||
"Serif Font": "མཐའ་ཐིག་ཡོད་པའི་ཡིག་གཟུགས།",
|
||||
"Show Book Details": "དཔེ་དེབ་ཀྱི་གནས་ཚུལ་རྒྱས་པ་མངོན་པ།",
|
||||
@@ -521,8 +520,23 @@
|
||||
"Separate Cover Page": "སྟོང་ངོས་གི་ཤོག་དཀར་པ།",
|
||||
"Resize Notebook": "དེབ་སྣོད་གཅོད་སྒྲིག",
|
||||
"Resize Sidebar": "གཡས་སྒོ་གཅོད་སྒྲིག",
|
||||
"Menu": "སྒེའུ་ཁུང་།",
|
||||
"Dismiss": "བརྟེན་སྒྲིག",
|
||||
"Get Help from the Readest Community": "Readest མཛའ་བརྩེའི་རོགས་རམ་ལོངས་སུ་རོགས་རམ་ཐོབ།",
|
||||
"Remove cover image": "སྟོང་ངོས་ཀྱི་པར་རིས་ཕྱིར་འཐེན།"
|
||||
"Remove cover image": "སྟོང་ངོས་ཀྱི་པར་རིས་ཕྱིར་འཐེན།",
|
||||
"Bookshelf": "དེབ་སྡེབས་",
|
||||
"View Menu": "མ་ལག་བལྟ་བ",
|
||||
"Settings Menu": "སྒྲིག་སྟངས་མ་ལག",
|
||||
"View account details and quota": "རྩིས་ཐོའི་རྒྱུ་ཆ་དང་ཚད་བརྡ་བལྟ་བ",
|
||||
"Library Header": "དེབ་མཛོད་མགོ་ཡིག",
|
||||
"Books Grid": "དེབ་ཀྱི་དཔེ་རིས",
|
||||
"Book Content": "དེབ་ནང་དོན",
|
||||
"Footer Bar": "མཇུག་སྒམ་ཕྱོགས",
|
||||
"Header Bar": "མགོ་སྒམ་ཕྱོགས",
|
||||
"View Options": "བལྟ་བའི་གདམ་ཁ",
|
||||
"Progress Information": "རྒྱུན་ལས་བརྡ་དོན",
|
||||
"Book Menu": "དེབ་ཀྱི་མ་ལག",
|
||||
"Search Options": "འཚོལ་བའི་གདམ་ཁ",
|
||||
"Close": "ཁ་རྒྱག",
|
||||
"Delete Book Options": "དེབ་བཏོན་གཏང་གི་གདམ་ཁ",
|
||||
"ON": "འབྱུང་ཁུངས",
|
||||
"OFF": "བརྗེ་བ"
|
||||
}
|
||||
|
||||
@@ -81,7 +81,6 @@
|
||||
"Search...": "Suchen...",
|
||||
"Select Book": "Buch auswählen",
|
||||
"Select Books": "Bücher auswählen",
|
||||
"Select Multiple Books": "Mehrere Bücher auswählen",
|
||||
"Sepia": "Sepia",
|
||||
"Serif Font": "Serifenschrift",
|
||||
"Show Book Details": "Buchdetails anzeigen",
|
||||
@@ -525,8 +524,23 @@
|
||||
"Separate Cover Page": "Titelseite separat anzeigen",
|
||||
"Resize Notebook": "Notizbuchgröße ändern",
|
||||
"Resize Sidebar": "Seitenleisten-Größe ändern",
|
||||
"Menu": "Menü",
|
||||
"Dismiss": "Schließen",
|
||||
"Get Help from the Readest Community": "Hilfe von der Readest-Community erhalten",
|
||||
"Remove cover image": "Coverbild entfernen"
|
||||
"Remove cover image": "Coverbild entfernen",
|
||||
"Bookshelf": "Bücherregal",
|
||||
"View Menu": "Menü anzeigen",
|
||||
"Settings Menu": "Einstellungsmenü",
|
||||
"View account details and quota": "Kontodetails und Kontingent anzeigen",
|
||||
"Library Header": "Bibliothekskopf",
|
||||
"Books Grid": "Bücherübersicht",
|
||||
"Book Content": "Buchinhalt",
|
||||
"Footer Bar": "Fußzeile",
|
||||
"Header Bar": "Kopfzeile",
|
||||
"View Options": "Ansichtsoptionen",
|
||||
"Progress Information": "Fortschrittsinformationen",
|
||||
"Book Menu": "Buchmenü",
|
||||
"Search Options": "Suchoptionen",
|
||||
"Close": "Schließen",
|
||||
"Delete Book Options": "Buch-Löschoptionen",
|
||||
"ON": "EIN",
|
||||
"OFF": "AUS"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Αναζήτηση...",
|
||||
"Select Book": "Επιλογή βιβλίου",
|
||||
"Select Books": "Επιλογή βιβλίων",
|
||||
"Select Multiple Books": "Επιλογή πολλαπλών βιβλίων",
|
||||
"Sepia": "Σέπια",
|
||||
"Serif Font": "Γραμματοσειρά Serif",
|
||||
"Show Book Details": "Εμφάνιση λεπτομερειών βιβλίου",
|
||||
@@ -525,8 +524,23 @@
|
||||
"Separate Cover Page": "Ξεχωριστή σελίδα εξωφύλλου",
|
||||
"Resize Notebook": "Αλλαγή μεγέθους σημειωματάριου",
|
||||
"Resize Sidebar": "Αλλαγή μεγέθους πλαϊνής γραμμής",
|
||||
"Menu": "Μενού",
|
||||
"Dismiss": "Απόρριψη",
|
||||
"Get Help from the Readest Community": "Λάβετε βοήθεια από την κοινότητα Readest",
|
||||
"Remove cover image": "Αφαίρεση εικόνας εξωφύλλου"
|
||||
"Remove cover image": "Αφαίρεση εικόνας εξωφύλλου",
|
||||
"Bookshelf": "Ράφι βιβλίων",
|
||||
"View Menu": "Προβολή μενού",
|
||||
"Settings Menu": "Μενού ρυθμίσεων",
|
||||
"View account details and quota": "Προβολή στοιχείων λογαριασμού και ορίου",
|
||||
"Library Header": "Κεφαλίδα βιβλιοθήκης",
|
||||
"Books Grid": "Πλέγμα βιβλίων",
|
||||
"Book Content": "Περιεχόμενο βιβλίου",
|
||||
"Footer Bar": "Γραμμή υποσέλιδου",
|
||||
"Header Bar": "Γραμμή κεφαλίδας",
|
||||
"View Options": "Επιλογές προβολής",
|
||||
"Progress Information": "Πληροφορίες προόδου",
|
||||
"Book Menu": "Μενού βιβλίου",
|
||||
"Search Options": "Επιλογές αναζήτησης",
|
||||
"Close": "Κλείσιμο",
|
||||
"Delete Book Options": "Επιλογές διαγραφής βιβλίου",
|
||||
"ON": "ΕΝΕΡΓΟΠΟΙΗΣΗ",
|
||||
"OFF": "ΑΠΕΝΕΡΓΟΠΟΙΗΣΗ"
|
||||
}
|
||||
|
||||
@@ -109,7 +109,6 @@
|
||||
"Search...": "Buscar...",
|
||||
"Select Book": "Seleccionar libro",
|
||||
"Select Books": "Seleccionar libros",
|
||||
"Select Multiple Books": "Seleccionar varios libros",
|
||||
"Sepia": "Sepia",
|
||||
"Serif Font": "Fuente serif",
|
||||
"Show Book Details": "Mostrar detalles del libro",
|
||||
@@ -529,8 +528,23 @@
|
||||
"Separate Cover Page": "Portada separada",
|
||||
"Resize Notebook": "Redimensionar cuaderno",
|
||||
"Resize Sidebar": "Redimensionar barra lateral",
|
||||
"Menu": "Menú",
|
||||
"Dismiss": "Descartar",
|
||||
"Get Help from the Readest Community": "Obtén ayuda de la comunidad de Readest",
|
||||
"Remove cover image": "Eliminar imagen de portada"
|
||||
"Remove cover image": "Eliminar imagen de portada",
|
||||
"Bookshelf": "Estantería",
|
||||
"View Menu": "Menú de Vista",
|
||||
"Settings Menu": "Menú de Configuración",
|
||||
"View account details and quota": "Ver detalles de la cuenta y cuota",
|
||||
"Library Header": "Encabezado de Biblioteca",
|
||||
"Books Grid": "Cuadrícula de Libros",
|
||||
"Book Content": "Contenido del Libro",
|
||||
"Footer Bar": "Barra de Pie de Página",
|
||||
"Header Bar": "Barra de Encabezado",
|
||||
"View Options": "Opciones de Vista",
|
||||
"Progress Information": "Información de Progreso",
|
||||
"Book Menu": "Menú de Libro",
|
||||
"Search Options": "Opciones de Búsqueda",
|
||||
"Close": "Cerrar",
|
||||
"Delete Book Options": "Opciones de Eliminar Libro",
|
||||
"ON": "ENCENDIDO",
|
||||
"OFF": "APAGADO"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Rechercher...",
|
||||
"Select Book": "Sélectionner un livre",
|
||||
"Select Books": "Sélectionner des livres",
|
||||
"Select Multiple Books": "Sélectionner plusieurs livres",
|
||||
"Sepia": "Sépia",
|
||||
"Serif Font": "Police avec empattement",
|
||||
"Show Book Details": "Afficher les détails du livre",
|
||||
@@ -529,8 +528,23 @@
|
||||
"Separate Cover Page": "Page de couverture séparée",
|
||||
"Resize Notebook": "Redimensionner le Carnet",
|
||||
"Resize Sidebar": "Redimensionner la Barre Latérale",
|
||||
"Menu": "Menu",
|
||||
"Dismiss": "Fermer",
|
||||
"Get Help from the Readest Community": "Obtenir de l'aide de la communauté Readest",
|
||||
"Remove cover image": "Supprimer l'image de couverture"
|
||||
"Remove cover image": "Supprimer l'image de couverture",
|
||||
"Bookshelf": "Étagère à livres",
|
||||
"View Menu": "Menu d'affichage",
|
||||
"Settings Menu": "Menu des paramètres",
|
||||
"View account details and quota": "Voir les détails du compte et le quota",
|
||||
"Library Header": "En-tête de la bibliothèque",
|
||||
"Books Grid": "Grille de livres",
|
||||
"Book Content": "Contenu du livre",
|
||||
"Footer Bar": "Barre de pied de page",
|
||||
"Header Bar": "Barre d'en-tête",
|
||||
"View Options": "Options d'affichage",
|
||||
"Progress Information": "Informations sur la progression",
|
||||
"Book Menu": "Menu du livre",
|
||||
"Search Options": "Options de recherche",
|
||||
"Close": "Fermer",
|
||||
"Delete Book Options": "Options de suppression du livre",
|
||||
"ON": "ACTIVER",
|
||||
"OFF": "DÉSACTIVER"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "खोजें...",
|
||||
"Select Book": "पुस्तक चुनें",
|
||||
"Select Books": "पुस्तकें चुनें",
|
||||
"Select Multiple Books": "कई पुस्तकें चुनें",
|
||||
"Sepia": "सेपिया",
|
||||
"Serif Font": "सेरिफ फ़ॉन्ट",
|
||||
"Show Book Details": "पुस्तक विवरण दिखाएं",
|
||||
@@ -525,8 +524,23 @@
|
||||
"Separate Cover Page": "अलग कवर पृष्ठ",
|
||||
"Resize Notebook": "नोटबुक का आकार बदलें",
|
||||
"Resize Sidebar": "साइडबार का आकार बदलें",
|
||||
"Menu": "मेनू",
|
||||
"Dismiss": "खारिज करें",
|
||||
"Get Help from the Readest Community": "Readest समुदाय से मदद लें",
|
||||
"Remove cover image": "कवर इमेज हटाएं"
|
||||
"Remove cover image": "कवर इमेज हटाएं",
|
||||
"Bookshelf": "पुस्तक शेल्फ",
|
||||
"View Menu": "मेनू देखें",
|
||||
"Settings Menu": "सेटिंग्स मेनू",
|
||||
"View account details and quota": "खाता विवरण और कोटा देखें",
|
||||
"Library Header": "पुस्तकालय शीर्षक",
|
||||
"Books Grid": "पुस्तकों की ग्रिड",
|
||||
"Book Content": "पुस्तक की सामग्री",
|
||||
"Footer Bar": "फुटर बार",
|
||||
"Header Bar": "हेडर बार",
|
||||
"View Options": "देखने के विकल्प",
|
||||
"Progress Information": "प्रगति जानकारी",
|
||||
"Book Menu": "पुस्तक मेनू",
|
||||
"Search Options": "खोज विकल्प",
|
||||
"Close": "बंद करें",
|
||||
"Delete Book Options": "पुस्तक हटाने के विकल्प",
|
||||
"ON": "चालू",
|
||||
"OFF": "बंद"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Cari...",
|
||||
"Select Book": "Pilih Buku",
|
||||
"Select Books": "Pilih buku",
|
||||
"Select Multiple Books": "Pilih beberapa buku",
|
||||
"Sepia": "Sepia",
|
||||
"Serif Font": "Font Serif",
|
||||
"Show Book Details": "Tampilkan Detail Buku",
|
||||
@@ -521,8 +520,23 @@
|
||||
"Separate Cover Page": "Halaman Sampul Terpisah",
|
||||
"Resize Notebook": "Ubah Ukuran Buku Catatan",
|
||||
"Resize Sidebar": "Ubah Ukuran Bilah Samping",
|
||||
"Menu": "Menu",
|
||||
"Dismiss": "Tutup",
|
||||
"Get Help from the Readest Community": "Dapatkan Bantuan dari Komunitas Readest",
|
||||
"Remove cover image": "Hapus gambar sampul"
|
||||
"Remove cover image": "Hapus gambar sampul",
|
||||
"Bookshelf": "Rak Buku",
|
||||
"View Menu": "Lihat Menu",
|
||||
"Settings Menu": "Menu Pengaturan",
|
||||
"View account details and quota": "Lihat detail akun dan kuota",
|
||||
"Library Header": "Header Perpustakaan",
|
||||
"Books Grid": "Grid Buku",
|
||||
"Book Content": "Isi Buku",
|
||||
"Footer Bar": "Bilah Footer",
|
||||
"Header Bar": "Bilah Header",
|
||||
"View Options": "Opsi Tampilan",
|
||||
"Progress Information": "Informasi Kemajuan",
|
||||
"Book Menu": "Menu Buku",
|
||||
"Search Options": "Opsi Pencarian",
|
||||
"Close": "Tutup",
|
||||
"Delete Book Options": "Opsi Hapus Buku",
|
||||
"ON": "AKTIF",
|
||||
"OFF": "TIDAK AKTIF"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Cerca...",
|
||||
"Select Book": "Seleziona libro",
|
||||
"Select Books": "Seleziona libri",
|
||||
"Select Multiple Books": "Seleziona più libri",
|
||||
"Sepia": "Seppia",
|
||||
"Serif Font": "Font serif",
|
||||
"Show Book Details": "Mostra dettagli libro",
|
||||
@@ -529,8 +528,23 @@
|
||||
"Separate Cover Page": "Pagina di copertina separata",
|
||||
"Resize Notebook": "Ridimensiona quaderno",
|
||||
"Resize Sidebar": "Ridimensiona barra laterale",
|
||||
"Menu": "Menu",
|
||||
"Dismiss": "Chiudi",
|
||||
"Get Help from the Readest Community": "Ottieni aiuto dalla community di Readest",
|
||||
"Remove cover image": "Rimuovi immagine di copertina"
|
||||
"Remove cover image": "Rimuovi immagine di copertina",
|
||||
"Bookshelf": "Scaffale dei libri",
|
||||
"View Menu": "Visualizza menu",
|
||||
"Settings Menu": "Menu impostazioni",
|
||||
"View account details and quota": "Visualizza dettagli account e quota",
|
||||
"Library Header": "Intestazione biblioteca",
|
||||
"Books Grid": "Griglia dei libri",
|
||||
"Book Content": "Contenuto del libro",
|
||||
"Footer Bar": "Barra piè di pagina",
|
||||
"Header Bar": "Barra intestazione",
|
||||
"View Options": "Opzioni di visualizzazione",
|
||||
"Progress Information": "Informazioni sul progresso",
|
||||
"Book Menu": "Menu libro",
|
||||
"Search Options": "Opzioni di ricerca",
|
||||
"Close": "Chiudi",
|
||||
"Delete Book Options": "Opzioni di eliminazione libro",
|
||||
"ON": "ATTIVO",
|
||||
"OFF": "DISATTIVO"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "検索...",
|
||||
"Select Book": "書籍を選択",
|
||||
"Select Books": "書籍を選択",
|
||||
"Select Multiple Books": "複数の書籍を選択",
|
||||
"Sepia": "セピア",
|
||||
"Serif Font": "セリフ体",
|
||||
"Show Book Details": "書籍の詳細を表示",
|
||||
@@ -521,8 +520,23 @@
|
||||
"Separate Cover Page": "別の表紙ページ",
|
||||
"Resize Notebook": "ノートのサイズ変更",
|
||||
"Resize Sidebar": "サイドバーのサイズ変更",
|
||||
"Menu": "メニュー",
|
||||
"Dismiss": "閉じる",
|
||||
"Get Help from the Readest Community": "Readestコミュニティからヘルプを得る",
|
||||
"Remove cover image": "表紙画像を削除"
|
||||
"Remove cover image": "表紙画像を削除",
|
||||
"Bookshelf": "本棚",
|
||||
"View Menu": "メニューを表示",
|
||||
"Settings Menu": "設定メニュー",
|
||||
"View account details and quota": "アカウントの詳細とクォータを表示",
|
||||
"Library Header": "ライブラリヘッダー",
|
||||
"Books Grid": "本のグリッド",
|
||||
"Book Content": "本の内容",
|
||||
"Footer Bar": "フッターバー",
|
||||
"Header Bar": "ヘッダーバー",
|
||||
"View Options": "表示オプション",
|
||||
"Progress Information": "進捗情報",
|
||||
"Book Menu": "本のメニュー",
|
||||
"Search Options": "検索オプション",
|
||||
"Close": "閉じる",
|
||||
"Delete Book Options": "本の削除オプション",
|
||||
"ON": "オン",
|
||||
"OFF": "オフ"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "검색...",
|
||||
"Select Book": "책 선택",
|
||||
"Select Books": "책 선택",
|
||||
"Select Multiple Books": "여러 책 선택",
|
||||
"Sepia": "세피아",
|
||||
"Serif Font": "세리프체",
|
||||
"Show Book Details": "책 세부 정보 표시",
|
||||
@@ -521,8 +520,23 @@
|
||||
"Separate Cover Page": "별도의 표지 페이지",
|
||||
"Resize Notebook": "노트북 크기 조정",
|
||||
"Resize Sidebar": "사이드바 크기 조정",
|
||||
"Menu": "메뉴",
|
||||
"Dismiss": "닫기",
|
||||
"Get Help from the Readest Community": "Readest 커뮤니티에서 도움 받기",
|
||||
"Remove cover image": "표지 이미지 제거"
|
||||
"Remove cover image": "표지 이미지 제거",
|
||||
"Bookshelf": "책장",
|
||||
"View Menu": "메뉴 보기",
|
||||
"Settings Menu": "설정 메뉴",
|
||||
"View account details and quota": "계정 세부정보 및 할당량 보기",
|
||||
"Library Header": "라이브러리 헤더",
|
||||
"Books Grid": "도서 그리드",
|
||||
"Book Content": "도서 내용",
|
||||
"Footer Bar": "푸터 바",
|
||||
"Header Bar": "헤더 바",
|
||||
"View Options": "보기 옵션",
|
||||
"Progress Information": "진행 정보",
|
||||
"Book Menu": "도서 메뉴",
|
||||
"Search Options": "검색 옵션",
|
||||
"Close": "닫기",
|
||||
"Delete Book Options": "도서 삭제 옵션",
|
||||
"ON": "켜기",
|
||||
"OFF": "끄기"
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"Search Books...": "Boeken zoeken...",
|
||||
"Clear Search": "Zoekopdracht wissen",
|
||||
"Import Books": "Boeken importeren",
|
||||
"Select Multiple Books": "Meerdere boeken selecteren",
|
||||
"Select Books": "Boeken selecteren",
|
||||
"Logged in as {{userDisplayName}}": "Ingelogd als {{userDisplayName}}",
|
||||
"Logged in": "Ingelogd",
|
||||
@@ -525,8 +524,23 @@
|
||||
"Separate Cover Page": "Afzonderlijke omslagpagina",
|
||||
"Resize Notebook": "Formaat Notitieblok Aanpassen",
|
||||
"Resize Sidebar": "Formaat Zijbalk Aanpassen",
|
||||
"Menu": "Menu",
|
||||
"Dismiss": "Sluiten",
|
||||
"Get Help from the Readest Community": "Hulp krijgen van de Readest Community",
|
||||
"Remove cover image": "Omslagafbeelding verwijderen"
|
||||
"Remove cover image": "Omslagafbeelding verwijderen",
|
||||
"Bookshelf": "Boekenkast",
|
||||
"View Menu": "Menu bekijken",
|
||||
"Settings Menu": "Instellingenmenu",
|
||||
"View account details and quota": "Accountgegevens en quotum bekijken",
|
||||
"Library Header": "Bibliotheekkop",
|
||||
"Books Grid": "Boekrooster",
|
||||
"Book Content": "Boekinhoud",
|
||||
"Footer Bar": "Voettekstbalk",
|
||||
"Header Bar": "Koptekstbalk",
|
||||
"View Options": "Weergaveopties",
|
||||
"Progress Information": "Voortgangsinformatie",
|
||||
"Book Menu": "Boekmenu",
|
||||
"Search Options": "Zoekopties",
|
||||
"Close": "Sluiten",
|
||||
"Delete Book Options": "Boekverwijderopties",
|
||||
"ON": "AAN",
|
||||
"OFF": "UIT"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Szukaj...",
|
||||
"Select Book": "Wybierz książkę",
|
||||
"Select Books": "Wybierz książki",
|
||||
"Select Multiple Books": "Wybierz wiele książek",
|
||||
"Sepia": "Sepia",
|
||||
"Serif Font": "Czcionka szeryfowa",
|
||||
"Show Book Details": "Pokaż szczegóły książki",
|
||||
@@ -533,8 +532,23 @@
|
||||
"Separate Cover Page": "Osobna strona okładki",
|
||||
"Resize Notebook": "Zmień rozmiar notatnika",
|
||||
"Resize Sidebar": "Zmień rozmiar paska bocznego",
|
||||
"Menu": "Menu",
|
||||
"Dismiss": "Odrzuć",
|
||||
"Get Help from the Readest Community": "Uzyskaj pomoc od społeczności Readest",
|
||||
"Remove cover image": "Usuń obraz okładki"
|
||||
"Remove cover image": "Usuń obraz okładki",
|
||||
"Bookshelf": "Półka na książki",
|
||||
"View Menu": "Pokaż menu",
|
||||
"Settings Menu": "Menu ustawień",
|
||||
"View account details and quota": "Zobacz szczegóły konta i limit",
|
||||
"Library Header": "Nagłówek biblioteki",
|
||||
"Books Grid": "Siatka książek",
|
||||
"Book Content": "Zawartość książki",
|
||||
"Footer Bar": "Pasek stopki",
|
||||
"Header Bar": "Pasek nagłówka",
|
||||
"View Options": "Opcje widoku",
|
||||
"Progress Information": "Informacje o postępie",
|
||||
"Book Menu": "Menu książki",
|
||||
"Search Options": "Opcje wyszukiwania",
|
||||
"Close": "Zamknij",
|
||||
"Delete Book Options": "Opcje usuwania książki",
|
||||
"ON": "WŁĄCZONE",
|
||||
"OFF": "WYŁĄCZONE"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Pesquisar...",
|
||||
"Select Book": "Selecionar Livro",
|
||||
"Select Books": "Selecionar livros",
|
||||
"Select Multiple Books": "Selecionar múltiplos livros",
|
||||
"Sepia": "Sépia",
|
||||
"Serif Font": "Fonte Serif",
|
||||
"Show Book Details": "Mostrar Detalhes do Livro",
|
||||
@@ -529,8 +528,23 @@
|
||||
"Separate Cover Page": "Páginas de Capa Separadas",
|
||||
"Resize Notebook": "Redimensionar Caderno",
|
||||
"Resize Sidebar": "Redimensionar Barra Lateral",
|
||||
"Menu": "Menu",
|
||||
"Dismiss": "Dispensar",
|
||||
"Get Help from the Readest Community": "Obter ajuda da comunidade Readest",
|
||||
"Remove cover image": "Remover imagem da capa"
|
||||
"Remove cover image": "Remover imagem da capa",
|
||||
"Bookshelf": "Estante de Livros",
|
||||
"View Menu": "Ver Menu",
|
||||
"Settings Menu": "Menu de Configurações",
|
||||
"View account details and quota": "Ver detalhes da conta e cota",
|
||||
"Library Header": "Cabeçalho da Biblioteca",
|
||||
"Books Grid": "Grade de Livros",
|
||||
"Book Content": "Conteúdo do Livro",
|
||||
"Footer Bar": "Barra de Rodapé",
|
||||
"Header Bar": "Barra de Cabeçalho",
|
||||
"View Options": "Opções de Visualização",
|
||||
"Progress Information": "Informações de Progresso",
|
||||
"Book Menu": "Menu do Livro",
|
||||
"Search Options": "Opções de Pesquisa",
|
||||
"Close": "Fechar",
|
||||
"Delete Book Options": "Opções de Exclusão de Livro",
|
||||
"ON": "LIGADO",
|
||||
"OFF": "DESLIGADO"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Поиск...",
|
||||
"Select Book": "Выбрать книгу",
|
||||
"Select Books": "Выбрать книги",
|
||||
"Select Multiple Books": "Выбрать несколько книг",
|
||||
"Sepia": "Сепия",
|
||||
"Serif Font": "Шрифт с засечками",
|
||||
"Show Book Details": "Показать детали книги",
|
||||
@@ -533,8 +532,23 @@
|
||||
"Separate Cover Page": "Отдельная страница обложки",
|
||||
"Resize Notebook": "Изменить размер блокнота",
|
||||
"Resize Sidebar": "Изменить размер боковой панели",
|
||||
"Menu": "Меню",
|
||||
"Dismiss": "Закрыть",
|
||||
"Get Help from the Readest Community": "Получить помощь от сообщества Readest",
|
||||
"Remove cover image": "Удалить изображение обложки"
|
||||
"Remove cover image": "Удалить изображение обложки",
|
||||
"Bookshelf": "Книжная полка",
|
||||
"View Menu": "Просмотр меню",
|
||||
"Settings Menu": "Меню настроек",
|
||||
"View account details and quota": "Просмотр данных аккаунта и квоты",
|
||||
"Library Header": "Заголовок библиотеки",
|
||||
"Books Grid": "Сетка книг",
|
||||
"Book Content": "Содержание книги",
|
||||
"Footer Bar": "Нижняя панель",
|
||||
"Header Bar": "Верхняя панель",
|
||||
"View Options": "Параметры просмотра",
|
||||
"Progress Information": "Информация о прогрессе",
|
||||
"Book Menu": "Меню книги",
|
||||
"Search Options": "Параметры поиска",
|
||||
"Close": "Закрыть",
|
||||
"Delete Book Options": "Параметры удаления книги",
|
||||
"ON": "ВКЛ",
|
||||
"OFF": "ВЫКЛ"
|
||||
}
|
||||
|
||||
@@ -66,7 +66,6 @@
|
||||
"Search Books...": "පොත් සොයන්න...",
|
||||
"Clear Search": "සෙවීම මකන්න",
|
||||
"Import Books": "පොත් ආයාත කරන්න",
|
||||
"Select Multiple Books": "පොත් කිහිපයක් තෝරන්න",
|
||||
"Select Books": "පොත් තෝරන්න",
|
||||
"Deselect": "අනිශ්චිත කරන්න",
|
||||
"Select All": "සියල්ල තෝරන්න",
|
||||
@@ -525,8 +524,23 @@
|
||||
"Separate Cover Page": "වෙන් වූ ආවරණ පිටුව",
|
||||
"Resize Notebook": "සටහන් පොතේ ප්රමාණය වෙනස් කරන්න",
|
||||
"Resize Sidebar": "පැති තීරුවේ ප්රමාණය වෙනස් කරන්න",
|
||||
"Menu": "මෙනුව",
|
||||
"Dismiss": "අත්හැරීම",
|
||||
"Get Help from the Readest Community": "Readest ප්රජාවෙන් උදව් ලබා ගන්න",
|
||||
"Remove cover image": "ආවරණ රූපය ඉවත් කරන්න"
|
||||
"Remove cover image": "ආවරණ රූපය ඉවත් කරන්න",
|
||||
"Bookshelf": "පොත් රාක්කය",
|
||||
"View Menu": "මෙනුව බලන්න",
|
||||
"Settings Menu": "සැකසුම් මෙනුව",
|
||||
"View account details and quota": "ගිණුම් විස්තර සහ කොටස බලන්න",
|
||||
"Library Header": "පුස්තකාල ශීර්ෂය",
|
||||
"Books Grid": "පොත් ජාලය",
|
||||
"Book Content": "පොතේ අන්තර්ගතය",
|
||||
"Footer Bar": "පාදක තීරුව",
|
||||
"Header Bar": "ශීර්ෂ තීරුව",
|
||||
"View Options": "දැක්ම විකල්ප",
|
||||
"Progress Information": "ප්රගති තොරතුරු",
|
||||
"Book Menu": "පොත් මෙනුව",
|
||||
"Search Options": "සෙවුම් විකල්ප",
|
||||
"Close": "වසන්න",
|
||||
"Delete Book Options": "පොත මකන විකල්ප",
|
||||
"ON": "ඇක්ටිව්",
|
||||
"OFF": "ඩිසැක්ටිව්"
|
||||
}
|
||||
|
||||
@@ -66,7 +66,6 @@
|
||||
"Search Books...": "புத்தகங்களைத் தேடவும்...",
|
||||
"Clear Search": "தேடலை அழிக்கவும்",
|
||||
"Import Books": "புத்தகங்களை இறக்குமதி செய்யவும்",
|
||||
"Select Multiple Books": "பல புத்தகங்களை தேர்வு செய்யவும்",
|
||||
"Select Books": "புத்தகங்களை தேர்வு செய்யவும்",
|
||||
"Deselect": "தேர்வுநீக்கவும்",
|
||||
"Select All": "அனைத்தையும் தேர்வு செய்யவும்",
|
||||
@@ -525,8 +524,23 @@
|
||||
"Separate Cover Page": "தனித்த அட்டை பக்கம்",
|
||||
"Resize Notebook": "குறிப்பேட்டை மீட்டமைக்கவும்",
|
||||
"Resize Sidebar": "பக்கப்பட்டியை மீட்டமைக்கவும்",
|
||||
"Menu": "மெனு",
|
||||
"Dismiss": "நிராகரிக்கவும்",
|
||||
"Get Help from the Readest Community": "Readest சமூகத்திலிருந்து உதவி பெறவும்",
|
||||
"Remove cover image": "அட்டை படத்தை நீக்கவும்"
|
||||
"Remove cover image": "அட்டை படத்தை நீக்கவும்",
|
||||
"Bookshelf": "புத்தக அலமாரி",
|
||||
"View Menu": "மெனுவை பார்க்கவும்",
|
||||
"Settings Menu": "அமைப்புகள் மெனு",
|
||||
"View account details and quota": "கணக்கு விவரங்கள் மற்றும் ஒதுக்கீட்டை பார்க்கவும்",
|
||||
"Library Header": "நூலக தலைப்பு",
|
||||
"Books Grid": "புத்தக கட்டமைப்பு",
|
||||
"Book Content": "புத்தக உள்ளடக்கம்",
|
||||
"Footer Bar": "அடிக்குறிப்பு பட்டை",
|
||||
"Header Bar": "தலைப்பு பட்டை",
|
||||
"View Options": "காணும் விருப்பங்கள்",
|
||||
"Progress Information": "முன்னேற்ற தகவல்",
|
||||
"Book Menu": "புத்தக மெனு",
|
||||
"Search Options": "தேடல் விருப்பங்கள்",
|
||||
"Close": "மூடு",
|
||||
"Delete Book Options": "புத்தகத்தை நீக்கும் விருப்பங்கள்",
|
||||
"ON": "செயல்படுத்தவும்",
|
||||
"OFF": "நிறுத்தவும்"
|
||||
}
|
||||
|
||||
@@ -63,7 +63,6 @@
|
||||
"Search Books...": "ค้นหาหนังสือ...",
|
||||
"Clear Search": "ล้างการค้นหา",
|
||||
"Import Books": "นำเข้าหนังสือ",
|
||||
"Select Multiple Books": "เลือกหลายเล่ม",
|
||||
"Select Books": "เลือกหนังสือ",
|
||||
"Deselect": "ยกเลิกเลือก",
|
||||
"Select All": "เลือกทั้งหมด",
|
||||
@@ -521,8 +520,23 @@
|
||||
"Separate Cover Page": "หน้าปกแยกต่างหาก",
|
||||
"Resize Notebook": "ปรับขนาดสมุดบันทึก",
|
||||
"Resize Sidebar": "ปรับขนาดแถบข้าง",
|
||||
"Menu": "เมนู",
|
||||
"Dismiss": "ปิด",
|
||||
"Get Help from the Readest Community": "รับความช่วยเหลือจากชุมชน Readest",
|
||||
"Remove cover image": "ลบรูปปก"
|
||||
"Remove cover image": "ลบรูปปก",
|
||||
"Bookshelf": "ชั้นวางหนังสือ",
|
||||
"View Menu": "ดูเมนู",
|
||||
"Settings Menu": "เมนูการตั้งค่า",
|
||||
"View account details and quota": "ดูรายละเอียดบัญชีและโควต้า",
|
||||
"Library Header": "ส่วนหัวของห้องสมุด",
|
||||
"Books Grid": "ตารางหนังสือ",
|
||||
"Book Content": "เนื้อหาหนังสือ",
|
||||
"Footer Bar": "แถบด้านล่าง",
|
||||
"Header Bar": "แถบส่วนหัว",
|
||||
"View Options": "ตัวเลือกการดู",
|
||||
"Progress Information": "ข้อมูลความคืบหน้า",
|
||||
"Book Menu": "เมนูหนังสือ",
|
||||
"Search Options": "ตัวเลือกการค้นหา",
|
||||
"Close": "ปิด",
|
||||
"Delete Book Options": "ตัวเลือกการลบหนังสือ",
|
||||
"ON": "เปิด",
|
||||
"OFF": "ปิด"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Ara...",
|
||||
"Select Book": "Kitap Seç",
|
||||
"Select Books": "Kitapları seç",
|
||||
"Select Multiple Books": "Birden fazla kitap seç",
|
||||
"Sepia": "Sepya",
|
||||
"Serif Font": "Serif Yazı Tipi",
|
||||
"Show Book Details": "Kitap Detaylarını Göster",
|
||||
@@ -525,8 +524,23 @@
|
||||
"Separate Cover Page": "Ayrı Kapak Sayfası",
|
||||
"Resize Notebook": "Defteri Yeniden Boyutlandır",
|
||||
"Resize Sidebar": "Kenar Çubuğunu Yeniden Boyutlandır",
|
||||
"Menu": "Menü",
|
||||
"Dismiss": "Kapat",
|
||||
"Get Help from the Readest Community": "Readest Topluluğundan Yardım Alın",
|
||||
"Remove cover image": "Kapak resmini kaldır"
|
||||
"Remove cover image": "Kapak resmini kaldır",
|
||||
"Bookshelf": "Kitaplık",
|
||||
"View Menu": "Menüyü Görüntüle",
|
||||
"Settings Menu": "Ayarlar Menüsü",
|
||||
"View account details and quota": "Hesap detaylarını ve kotayı görüntüle",
|
||||
"Library Header": "Kütüphane Başlığı",
|
||||
"Books Grid": "Kitap Izgarası",
|
||||
"Book Content": "Kitap İçeriği",
|
||||
"Footer Bar": "Alt Çubuk",
|
||||
"Header Bar": "Üst Çubuk",
|
||||
"View Options": "Görünüm Seçenekleri",
|
||||
"Progress Information": "İlerleme Bilgisi",
|
||||
"Book Menu": "Kitap Menüsü",
|
||||
"Search Options": "Arama Seçenekleri",
|
||||
"Close": "Kapat",
|
||||
"Delete Book Options": "Kitap Silme Seçenekleri",
|
||||
"ON": "AÇIK",
|
||||
"OFF": "KAPALI"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Пошук...",
|
||||
"Select Book": "Вибрати книгу",
|
||||
"Select Books": "Вибрати книги",
|
||||
"Select Multiple Books": "Вибрати кілька книг",
|
||||
"Sepia": "Сепія",
|
||||
"Serif Font": "Шрифт із засічками",
|
||||
"Show Book Details": "Показати деталі книги",
|
||||
@@ -533,8 +532,23 @@
|
||||
"Separate Cover Page": "Окрема сторінка обкладинки",
|
||||
"Resize Notebook": "Змінити розмір нотаток",
|
||||
"Resize Sidebar": "Змінити розмір бічної панелі",
|
||||
"Menu": "Меню",
|
||||
"Dismiss": "Відхилити",
|
||||
"Get Help from the Readest Community": "Отримати допомогу від спільноти Readest",
|
||||
"Remove cover image": "Видалити зображення обкладинки"
|
||||
"Remove cover image": "Видалити зображення обкладинки",
|
||||
"Bookshelf": "Книжкова полиця",
|
||||
"View Menu": "Переглянути меню",
|
||||
"Settings Menu": "Меню налаштувань",
|
||||
"View account details and quota": "Переглянути деталі акаунта та квоту",
|
||||
"Library Header": "Заголовок бібліотеки",
|
||||
"Books Grid": "Сітка книг",
|
||||
"Book Content": "Зміст книги",
|
||||
"Footer Bar": "Нижня панель",
|
||||
"Header Bar": "Верхня панель",
|
||||
"View Options": "Параметри перегляду",
|
||||
"Progress Information": "Інформація про прогрес",
|
||||
"Book Menu": "Меню книги",
|
||||
"Search Options": "Параметри пошуку",
|
||||
"Close": "Закрити",
|
||||
"Delete Book Options": "Параметри видалення книги",
|
||||
"ON": "УВІМКНЕННЯ",
|
||||
"OFF": "ВИМКНЕННЯ"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Tìm kiếm...",
|
||||
"Select Book": "Chọn sách",
|
||||
"Select Books": "Chọn sách",
|
||||
"Select Multiple Books": "Chọn nhiều sách",
|
||||
"Sepia": "Nâu cổ",
|
||||
"Serif Font": "Phông chữ có chân",
|
||||
"Show Book Details": "Hiển thị chi tiết sách",
|
||||
@@ -521,8 +520,23 @@
|
||||
"Separate Cover Page": "Trang Bìa Riêng",
|
||||
"Resize Notebook": "Thay Đổi Kích Thước Sổ Ghi Chép",
|
||||
"Resize Sidebar": "Thay Đổi Kích Thước Thanh Bên",
|
||||
"Menu": "Menu",
|
||||
"Dismiss": "Đóng",
|
||||
"Get Help from the Readest Community": "Nhận Trợ Giúp từ Cộng Đồng Readest",
|
||||
"Remove cover image": "Xóa ảnh bìa"
|
||||
"Remove cover image": "Xóa ảnh bìa",
|
||||
"Bookshelf": "Kệ sách",
|
||||
"View Menu": "Xem menu",
|
||||
"Settings Menu": "Menu cài đặt",
|
||||
"View account details and quota": "Xem chi tiết tài khoản và hạn mức",
|
||||
"Library Header": "Tiêu đề thư viện",
|
||||
"Books Grid": "Lưới sách",
|
||||
"Book Content": "Nội dung sách",
|
||||
"Footer Bar": "Thanh chân trang",
|
||||
"Header Bar": "Thanh đầu trang",
|
||||
"View Options": "Tùy chọn xem",
|
||||
"Progress Information": "Thông tin tiến độ",
|
||||
"Book Menu": "Menu sách",
|
||||
"Search Options": "Tùy chọn tìm kiếm",
|
||||
"Close": "Đóng",
|
||||
"Delete Book Options": "Tùy chọn xóa sách",
|
||||
"ON": "BẬT",
|
||||
"OFF": "TẮT"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "搜索...",
|
||||
"Select Book": "选择书籍",
|
||||
"Select Books": "选择书籍",
|
||||
"Select Multiple Books": "选择多本书籍",
|
||||
"Sepia": "旧韵",
|
||||
"Serif Font": "衬线字体",
|
||||
"Show Book Details": "显示书籍详情",
|
||||
@@ -521,8 +520,23 @@
|
||||
"Separate Cover Page": "单独封面页",
|
||||
"Resize Notebook": "调整笔记本大小",
|
||||
"Resize Sidebar": "调整侧边栏大小",
|
||||
"Menu": "菜单",
|
||||
"Dismiss": "关闭",
|
||||
"Get Help from the Readest Community": "从 Readest 社区获取帮助",
|
||||
"Remove cover image": "移除封面图片"
|
||||
"Remove cover image": "移除封面图片",
|
||||
"Bookshelf": "书架",
|
||||
"View Menu": "查看菜单",
|
||||
"Settings Menu": "设置菜单",
|
||||
"View account details and quota": "查看账户详情和配额",
|
||||
"Library Header": "图书馆标题",
|
||||
"Books Grid": "图书网格",
|
||||
"Book Content": "书籍内容",
|
||||
"Footer Bar": "底部栏",
|
||||
"Header Bar": "顶部栏",
|
||||
"View Options": "查看选项",
|
||||
"Progress Information": "进度信息",
|
||||
"Book Menu": "书籍菜单",
|
||||
"Search Options": "搜索选项",
|
||||
"Close": "关闭",
|
||||
"Delete Book Options": "删除书籍选项",
|
||||
"ON": "开启",
|
||||
"OFF": "关闭"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "搜索...",
|
||||
"Select Book": "選擇書籍",
|
||||
"Select Books": "選擇書籍",
|
||||
"Select Multiple Books": "選擇多本書籍",
|
||||
"Sepia": "舊韻",
|
||||
"Serif Font": "襯線字體",
|
||||
"Show Book Details": "顯示書籍詳情",
|
||||
@@ -521,8 +520,23 @@
|
||||
"Separate Cover Page": "單獨封面頁",
|
||||
"Resize Notebook": "調整筆記本大小",
|
||||
"Resize Sidebar": "調整側邊欄大小",
|
||||
"Menu": "選單",
|
||||
"Dismiss": "關閉",
|
||||
"Get Help from the Readest Community": "從 Readest 社群獲取幫助",
|
||||
"Remove cover image": "移除封面圖片"
|
||||
"Remove cover image": "移除封面圖片",
|
||||
"Bookshelf": "書架",
|
||||
"View Menu": "檢視選單",
|
||||
"Settings Menu": "設定選單",
|
||||
"View account details and quota": "檢視帳戶詳細資料與配額",
|
||||
"Library Header": "圖書館標題",
|
||||
"Books Grid": "書籍網格",
|
||||
"Book Content": "書籍內容",
|
||||
"Footer Bar": "頁腳欄",
|
||||
"Header Bar": "標頭欄",
|
||||
"View Options": "檢視選項",
|
||||
"Progress Information": "進度資訊",
|
||||
"Book Menu": "書籍選單",
|
||||
"Search Options": "搜尋選項",
|
||||
"Close": "關閉",
|
||||
"Delete Book Options": "刪除書籍選項",
|
||||
"ON": "開啟",
|
||||
"OFF": "關閉"
|
||||
}
|
||||
|
||||
@@ -363,6 +363,7 @@ export default function AuthPage() {
|
||||
)}
|
||||
>
|
||||
<button
|
||||
aria-label={_('Go Back')}
|
||||
onClick={handleGoBack}
|
||||
className={clsx('btn btn-ghost h-12 min-h-12 w-12 p-0 sm:h-8 sm:min-h-8 sm:w-8')}
|
||||
>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Book } from '@/types/book';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import { LibraryCoverFitType, LibraryViewModeType } from '@/types/settings';
|
||||
import { navigateToLogin } from '@/utils/nav';
|
||||
@@ -40,6 +41,7 @@ const BookItem: React.FC<BookItemProps> = ({
|
||||
handleBookDownload,
|
||||
showBookDetailsModal,
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const router = useRouter();
|
||||
const { user } = useAuth();
|
||||
const { appService } = useEnv();
|
||||
@@ -115,6 +117,7 @@ const BookItem: React.FC<BookItemProps> = ({
|
||||
<div className='flex items-center justify-center gap-x-2'>
|
||||
{!appService?.isMobile && (
|
||||
<button
|
||||
aria-label={_('Show Book Details')}
|
||||
className='show-detail-button -m-2 p-2 sm:opacity-0 sm:group-hover:opacity-100'
|
||||
onPointerDown={(e) => stopEvent(e)}
|
||||
onPointerUp={(e) => stopEvent(e)}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Book, BooksGroup } from '@/types/book';
|
||||
import { LibraryCoverFitType, LibraryViewModeType } from '@/types/settings';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useAutoFocus } from '@/hooks/useAutoFocus';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useLibraryStore } from '@/store/libraryStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
@@ -76,6 +77,8 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
const { setSelectedBooks, getSelectedBooks, toggleSelectedBook } = useLibraryStore();
|
||||
const allBookshelfItems = generateBookshelfItems(libraryBooks);
|
||||
|
||||
const autofocusRef = useAutoFocus<HTMLDivElement>();
|
||||
|
||||
useEffect(() => {
|
||||
if (isImportingBook.current) return;
|
||||
isImportingBook.current = true;
|
||||
@@ -316,12 +319,16 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
return (
|
||||
<div className='bookshelf'>
|
||||
<div
|
||||
ref={autofocusRef}
|
||||
tabIndex={-1}
|
||||
className={clsx(
|
||||
'bookshelf-items transform-wrapper',
|
||||
'bookshelf-items transform-wrapper focus:outline-none',
|
||||
viewMode === 'grid' && 'grid flex-1 grid-cols-3 gap-x-4 px-4 sm:gap-x-0 sm:px-2',
|
||||
viewMode === 'grid' && 'sm:grid-cols-4 md:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-12',
|
||||
viewMode === 'list' && 'flex flex-col',
|
||||
)}
|
||||
role='list'
|
||||
aria-label={_('Bookshelf')}
|
||||
>
|
||||
{filteredBookshelfItems.map((item) => (
|
||||
<BookshelfItem
|
||||
@@ -347,6 +354,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
))}
|
||||
{viewMode === 'grid' && !navBooksGroup && allBookshelfItems.length > 0 && (
|
||||
<button
|
||||
aria-label={_('Import Books')}
|
||||
className={clsx(
|
||||
'border-1 bg-base-100 hover:bg-base-300/50 flex items-center justify-center',
|
||||
'mx-0 my-4 aspect-[28/41] sm:mx-4',
|
||||
|
||||
@@ -322,6 +322,8 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
|
||||
appService?.isMobileApp && 'no-context-menu',
|
||||
pressing && mode === 'grid' ? 'scale-95' : 'scale-100',
|
||||
)}
|
||||
role='group'
|
||||
aria-label={'format' in item ? item.title : item.name}
|
||||
style={{
|
||||
transition: 'transform 0.2s',
|
||||
}}
|
||||
|
||||
@@ -2,6 +2,7 @@ import clsx from 'clsx';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import MenuItem from '@/components/MenuItem';
|
||||
import Menu from '@/components/Menu';
|
||||
|
||||
interface ImportMenuProps {
|
||||
setIsDropdownOpen?: (open: boolean) => void;
|
||||
@@ -18,15 +19,15 @@ const ImportMenu: React.FC<ImportMenuProps> = ({ setIsDropdownOpen, onImportBook
|
||||
};
|
||||
|
||||
return (
|
||||
<ul
|
||||
tabIndex={-1}
|
||||
<Menu
|
||||
label={_('Import Books')}
|
||||
className={clsx(
|
||||
'dropdown-content bg-base-100 menu rounded-box z-[1] mt-3 w-52 p-2 shadow',
|
||||
'dropdown-content bg-base-100 rounded-box z-[1] mt-3 w-52 p-2 shadow',
|
||||
appService?.isMobile ? 'no-triangle' : 'dropdown-center',
|
||||
)}
|
||||
>
|
||||
<MenuItem label={_('From Local File')} onClick={handleImportBooks} />
|
||||
</ul>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -172,34 +172,27 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
)}
|
||||
<span className='bg-base-content/50 mx-2 h-4 w-[0.5px]'></span>
|
||||
<Dropdown
|
||||
label={_('Import Books')}
|
||||
className={clsx(
|
||||
'exclude-title-bar-mousedown dropdown-bottom flex h-6 cursor-pointer justify-center',
|
||||
appService?.isMobile ? 'dropdown-end' : 'dropdown-center',
|
||||
)}
|
||||
buttonClassName='p-0 h-6 min-h-6 w-6 flex items-center justify-center'
|
||||
toggleButton={
|
||||
<div className='lg:tooltip lg:tooltip-bottom' data-tip={_('Import Books')}>
|
||||
<PiPlus className='m-0.5 h-5 w-5' />
|
||||
</div>
|
||||
}
|
||||
toggleButton={<PiPlus role='none' className='m-0.5 h-5 w-5' />}
|
||||
>
|
||||
<ImportMenu onImportBooks={onImportBooks} />
|
||||
</Dropdown>
|
||||
{appService?.isMobile ? null : (
|
||||
<button
|
||||
onClick={onToggleSelectMode}
|
||||
aria-label={_('Select Multiple Books')}
|
||||
aria-label={_('Select Books')}
|
||||
title={_('Select Books')}
|
||||
className='h-6'
|
||||
>
|
||||
<div
|
||||
className='lg:tooltip lg:tooltip-bottom cursor-pointer'
|
||||
data-tip={_('Select Books')}
|
||||
>
|
||||
<PiSelectionAllDuotone
|
||||
role='button'
|
||||
className={`h-6 w-6 ${isSelectMode ? 'fill-gray-400' : 'fill-gray-500'}`}
|
||||
/>
|
||||
</div>
|
||||
<PiSelectionAllDuotone
|
||||
role='button'
|
||||
className={`h-6 w-6 ${isSelectMode ? 'fill-gray-400' : 'fill-gray-500'}`}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -224,16 +217,18 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
) : (
|
||||
<div className='flex h-full items-center gap-x-2 sm:gap-x-4'>
|
||||
<Dropdown
|
||||
label={_('View Menu')}
|
||||
className='exclude-title-bar-mousedown dropdown-bottom dropdown-end'
|
||||
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
|
||||
toggleButton={<PiDotsThreeCircle size={iconSize18} />}
|
||||
toggleButton={<PiDotsThreeCircle role='none' size={iconSize18} />}
|
||||
>
|
||||
<ViewMenu />
|
||||
</Dropdown>
|
||||
<Dropdown
|
||||
label={_('Settings Menu')}
|
||||
className='exclude-title-bar-mousedown dropdown-bottom dropdown-end'
|
||||
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
|
||||
toggleButton={<MdOutlineMenu size={iconSize18} />}
|
||||
toggleButton={<MdOutlineMenu role='none' size={iconSize18} />}
|
||||
>
|
||||
<SettingsMenu />
|
||||
</Dropdown>
|
||||
|
||||
@@ -26,7 +26,11 @@ const ReadingProgress: React.FC<ReadingProgressProps> = memo(
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='text-neutral-content/70 flex justify-between text-xs'>
|
||||
<div
|
||||
className='text-neutral-content/70 flex justify-between text-xs'
|
||||
role='status'
|
||||
aria-label={`${progressPercentage}%`}
|
||||
>
|
||||
<span>{progressPercentage}%</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,6 @@ import React, { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { PiUserCircle } from 'react-icons/pi';
|
||||
import { PiUserCircleCheck } from 'react-icons/pi';
|
||||
import { MdCheck } from 'react-icons/md';
|
||||
import { TbSunMoon } from 'react-icons/tb';
|
||||
import { BiMoon, BiSun } from 'react-icons/bi';
|
||||
|
||||
@@ -23,6 +22,7 @@ import { optInTelemetry, optOutTelemetry } from '@/utils/telemetry';
|
||||
import UserAvatar from '@/components/UserAvatar';
|
||||
import MenuItem from '@/components/MenuItem';
|
||||
import Quota from '@/components/Quota';
|
||||
import Menu from '@/components/Menu';
|
||||
|
||||
interface SettingsMenuProps {
|
||||
setIsDropdownOpen?: (isOpen: boolean) => void;
|
||||
@@ -165,9 +165,16 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
const avatarUrl = user?.user_metadata?.['picture'] || user?.user_metadata?.['avatar_url'];
|
||||
const userFullName = user?.user_metadata?.['full_name'];
|
||||
const userDisplayName = userFullName ? userFullName.split(' ')[0] : null;
|
||||
const themeModeLabel =
|
||||
themeMode === 'dark'
|
||||
? _('Dark Mode')
|
||||
: themeMode === 'light'
|
||||
? _('Light Mode')
|
||||
: _('Auto Mode');
|
||||
|
||||
return (
|
||||
<div
|
||||
<Menu
|
||||
label={_('Settings Menu')}
|
||||
className={clsx(
|
||||
'settings-menu dropdown-content no-triangle border-base-100',
|
||||
'z-20 mt-2 max-w-[90vw] shadow-2xl',
|
||||
@@ -181,6 +188,7 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
: _('Logged in')
|
||||
}
|
||||
labelClass='!max-w-40'
|
||||
aria-label={_('View account details and quota')}
|
||||
Icon={
|
||||
avatarUrl ? (
|
||||
<UserAvatar url={avatarUrl} size={iconSize} DefaultIcon={PiUserCircleCheck} />
|
||||
@@ -201,27 +209,27 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
)}
|
||||
<MenuItem
|
||||
label={_('Auto Upload Books to Cloud')}
|
||||
Icon={isAutoUpload ? MdCheck : undefined}
|
||||
toggled={isAutoUpload}
|
||||
onClick={toggleAutoUploadBooks}
|
||||
/>
|
||||
{isTauriAppPlatform() && !appService?.isMobile && (
|
||||
<MenuItem
|
||||
label={_('Auto Import on File Open')}
|
||||
Icon={isAutoImportBooksOnOpen ? MdCheck : undefined}
|
||||
toggled={isAutoImportBooksOnOpen}
|
||||
onClick={toggleAutoImportBooksOnOpen}
|
||||
/>
|
||||
)}
|
||||
{isTauriAppPlatform() && (
|
||||
<MenuItem
|
||||
label={_('Open Last Book on Start')}
|
||||
Icon={isOpenLastBooks ? MdCheck : undefined}
|
||||
toggled={isOpenLastBooks}
|
||||
onClick={toggleOpenLastBooks}
|
||||
/>
|
||||
)}
|
||||
{appService?.hasUpdater && (
|
||||
<MenuItem
|
||||
label={_('Check Updates on Start')}
|
||||
Icon={isAutoCheckUpdates ? MdCheck : undefined}
|
||||
toggled={isAutoCheckUpdates}
|
||||
onClick={toggleAutoCheckUpdates}
|
||||
/>
|
||||
)}
|
||||
@@ -229,39 +237,29 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
{appService?.hasWindow && (
|
||||
<MenuItem
|
||||
label={_('Open Book in New Window')}
|
||||
Icon={settings.openBookInNewWindow ? MdCheck : undefined}
|
||||
toggled={settings.openBookInNewWindow}
|
||||
onClick={toggleOpenInNewWindow}
|
||||
/>
|
||||
)}
|
||||
{appService?.hasWindow && <MenuItem label={_('Fullscreen')} onClick={handleFullScreen} />}
|
||||
{appService?.hasWindow && (
|
||||
<MenuItem
|
||||
label={_('Always on Top')}
|
||||
Icon={isAlwaysOnTop ? MdCheck : undefined}
|
||||
onClick={toggleAlwaysOnTop}
|
||||
/>
|
||||
<MenuItem label={_('Always on Top')} toggled={isAlwaysOnTop} onClick={toggleAlwaysOnTop} />
|
||||
)}
|
||||
{appService?.isMobileApp && (
|
||||
<MenuItem
|
||||
label={_('Always Show Status Bar')}
|
||||
Icon={isAlwaysShowStatusBar ? MdCheck : undefined}
|
||||
toggled={isAlwaysShowStatusBar}
|
||||
onClick={toggleAlwaysShowStatusBar}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
label={_('Keep Screen Awake')}
|
||||
Icon={isScreenWakeLock ? MdCheck : undefined}
|
||||
toggled={isScreenWakeLock}
|
||||
onClick={toggleScreenWakeLock}
|
||||
/>
|
||||
<MenuItem label={_('Reload Page')} onClick={handleReloadPage} />
|
||||
<MenuItem
|
||||
label={
|
||||
themeMode === 'dark'
|
||||
? _('Dark Mode')
|
||||
: themeMode === 'light'
|
||||
? _('Light Mode')
|
||||
: _('Auto Mode')
|
||||
}
|
||||
label={themeModeLabel}
|
||||
Icon={themeMode === 'dark' ? BiMoon : themeMode === 'light' ? BiSun : TbSunMoon}
|
||||
onClick={cycleThemeMode}
|
||||
/>
|
||||
@@ -274,10 +272,10 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
<MenuItem
|
||||
label={_('Help improve Readest')}
|
||||
description={isTelemetryEnabled ? _('Sharing anonymized statistics') : ''}
|
||||
Icon={isTelemetryEnabled ? MdCheck : undefined}
|
||||
toggled={isTelemetryEnabled}
|
||||
onClick={toggleTelemetry}
|
||||
/>
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { MdCheck } from 'react-icons/md';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { LibraryCoverFitType, LibrarySortByType, LibraryViewModeType } from '@/types/settings';
|
||||
import { navigateToLibrary } from '@/utils/nav';
|
||||
import MenuItem from '@/components/MenuItem';
|
||||
import Menu from '@/components/Menu';
|
||||
|
||||
interface ViewMenuProps {
|
||||
setIsDropdownOpen?: (isOpen: boolean) => void;
|
||||
@@ -92,13 +92,16 @@ const ViewMenu: React.FC<ViewMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='settings-menu dropdown-content no-triangle border-base-100 z-20 mt-2 shadow-2xl'>
|
||||
<Menu
|
||||
label={_('View Menu')}
|
||||
className='view-menu dropdown-content no-triangle border-base-100 z-20 mt-2 shadow-2xl'
|
||||
>
|
||||
{viewOptions.map((option) => (
|
||||
<MenuItem
|
||||
key={option.value}
|
||||
label={option.label}
|
||||
buttonClass='h-8'
|
||||
Icon={viewMode === option.value ? MdCheck : undefined}
|
||||
toggled={viewMode === option.value}
|
||||
onClick={() => handleSetViewMode(option.value as LibraryViewModeType)}
|
||||
/>
|
||||
))}
|
||||
@@ -114,7 +117,7 @@ const ViewMenu: React.FC<ViewMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
key={option.value}
|
||||
label={option.label}
|
||||
buttonClass='h-8'
|
||||
Icon={coverFit === option.value ? MdCheck : undefined}
|
||||
toggled={coverFit === option.value}
|
||||
onClick={() => handleToggleCropCovers(option.value as LibraryCoverFitType)}
|
||||
/>
|
||||
))}
|
||||
@@ -130,7 +133,7 @@ const ViewMenu: React.FC<ViewMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
key={option.value}
|
||||
label={option.label}
|
||||
buttonClass='h-8'
|
||||
Icon={sortBy === option.value ? MdCheck : undefined}
|
||||
toggled={sortBy === option.value}
|
||||
onClick={() => handleSetSortBy(option.value as LibrarySortByType)}
|
||||
/>
|
||||
))}
|
||||
@@ -140,11 +143,11 @@ const ViewMenu: React.FC<ViewMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
key={option.value.toString()}
|
||||
label={option.label}
|
||||
buttonClass='h-8'
|
||||
Icon={isAscending === option.value ? MdCheck : undefined}
|
||||
toggled={isAscending === option.value}
|
||||
onClick={() => handleSetSortAscending(option.value)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -627,13 +627,20 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
return (
|
||||
<div
|
||||
ref={pageRef}
|
||||
role='main'
|
||||
aria-label='Your Library'
|
||||
className={clsx(
|
||||
'library-page bg-base-200 text-base-content flex select-none flex-col overflow-hidden',
|
||||
appService?.isIOSApp ? 'h-[100vh]' : 'h-dvh',
|
||||
appService?.hasRoundedWindow && isRoundedWindow && 'window-border rounded-window',
|
||||
)}
|
||||
>
|
||||
<div className='top-0 z-40 w-full'>
|
||||
<div
|
||||
className='top-0 z-40 w-full'
|
||||
role='banner'
|
||||
tabIndex={-1}
|
||||
aria-label={_('Library Header')}
|
||||
>
|
||||
<LibraryHeader
|
||||
isSelectMode={isSelectMode}
|
||||
isSelectAll={isSelectAll}
|
||||
@@ -652,6 +659,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
(libraryBooks.some((book) => !book.deletedAt) ? (
|
||||
<OverlayScrollbarsComponent
|
||||
defer
|
||||
aria-label=''
|
||||
ref={osRef}
|
||||
className='flex-grow'
|
||||
options={{ scrollbars: { autoHide: 'scroll' } }}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useSidebarStore } from '@/store/sidebarStore';
|
||||
import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { getGridTemplate, getInsetEdges } from '@/utils/grid';
|
||||
import { getViewInsets } from '@/utils/insets';
|
||||
import FoliateViewer from './FoliateViewer';
|
||||
@@ -27,6 +28,7 @@ interface BooksGridProps {
|
||||
}
|
||||
|
||||
const BooksGrid: React.FC<BooksGridProps> = ({ bookKeys, onCloseBook }) => {
|
||||
const _ = useTranslation();
|
||||
const { appService } = useEnv();
|
||||
const { getConfig, getBookData } = useBookDataStore();
|
||||
const { getProgress, getViewState, getViewSettings } = useReaderStore();
|
||||
@@ -75,6 +77,8 @@ const BooksGrid: React.FC<BooksGridProps> = ({ bookKeys, onCloseBook }) => {
|
||||
gridTemplateColumns: gridTemplate.columns,
|
||||
gridTemplateRows: gridTemplate.rows,
|
||||
}}
|
||||
role='main'
|
||||
aria-label={_('Books Grid')}
|
||||
>
|
||||
{bookKeys.map((bookKey, index) => {
|
||||
const bookData = getBookData(bookKey);
|
||||
|
||||
@@ -15,6 +15,8 @@ import { usePagination } from '../hooks/usePagination';
|
||||
import { useFoliateEvents } from '../hooks/useFoliateEvents';
|
||||
import { useProgressSync } from '../hooks/useProgressSync';
|
||||
import { useProgressAutoSave } from '../hooks/useProgressAutoSave';
|
||||
import { useAutoFocus } from '@/hooks/useAutoFocus';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useKOSync } from '../hooks/useKOSync';
|
||||
import {
|
||||
applyFixedlayoutStyles,
|
||||
@@ -62,6 +64,7 @@ const FoliateViewer: React.FC<{
|
||||
gridInsets: Insets;
|
||||
contentInsets: Insets;
|
||||
}> = ({ bookKey, bookDoc, config, gridInsets, contentInsets: insets }) => {
|
||||
const _ = useTranslation();
|
||||
const { appService, envConfig } = useEnv();
|
||||
const { themeCode, isDarkMode } = useThemeStore();
|
||||
const { settings } = useSettingsStore();
|
||||
@@ -81,6 +84,8 @@ const FoliateViewer: React.FC<{
|
||||
const [loading, setLoading] = useState(false);
|
||||
const docLoaded = useRef(false);
|
||||
|
||||
useAutoFocus<HTMLDivElement>({ ref: containerRef });
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setToastMessage(''), 2000);
|
||||
return () => clearTimeout(timer);
|
||||
@@ -410,7 +415,10 @@ const FoliateViewer: React.FC<{
|
||||
<>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className='foliate-viewer h-[100%] w-[100%]'
|
||||
tabIndex={-1}
|
||||
role='document'
|
||||
aria-label={_('Book Content')}
|
||||
className='foliate-viewer h-[100%] w-[100%] focus:outline-none'
|
||||
{...mouseHandlers}
|
||||
{...touchHandlers}
|
||||
/>
|
||||
|
||||
@@ -173,7 +173,8 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
role='button'
|
||||
role='none'
|
||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
||||
tabIndex={0}
|
||||
className={clsx(
|
||||
'absolute bottom-0 left-0 z-10 hidden w-full sm:flex sm:h-[52px]',
|
||||
@@ -185,6 +186,8 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
onTouchStart={() => !appService?.isMobile && setHoveredBookKey(bookKey)}
|
||||
/>
|
||||
<div
|
||||
role='group'
|
||||
aria-label={_('Footer Bar')}
|
||||
className={clsx(
|
||||
'footer-bar shadow-xs bottom-0 z-10 flex w-full flex-col',
|
||||
'sm:h-[52px] sm:justify-center',
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useEnv } from '@/context/EnvContext';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useSidebarStore } from '@/store/sidebarStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useTrafficLightStore } from '@/store/trafficLightStore';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import WindowButtons from '@/components/WindowButtons';
|
||||
@@ -37,6 +38,7 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
|
||||
onCloseBook,
|
||||
onSetSettingsDialogOpen,
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const { appService } = useEnv();
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
const {
|
||||
@@ -105,7 +107,8 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
|
||||
}}
|
||||
>
|
||||
<div
|
||||
role='button'
|
||||
role='none'
|
||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
||||
tabIndex={0}
|
||||
className={clsx('absolute top-0 z-10 h-11 w-full')}
|
||||
onFocus={() => !appService?.isMobile && setHoveredBookKey(bookKey)}
|
||||
@@ -124,6 +127,8 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
|
||||
/>
|
||||
<div
|
||||
ref={headerRef}
|
||||
role='group'
|
||||
aria-label={_('Header Bar')}
|
||||
className={clsx(
|
||||
`header-bar bg-base-100 absolute top-0 z-10 flex h-11 w-full items-center pr-4`,
|
||||
`shadow-xs transition-[opacity,margin-top] duration-300`,
|
||||
@@ -154,25 +159,29 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
|
||||
</div>
|
||||
|
||||
<div
|
||||
role='contentinfo'
|
||||
aria-label={_('Title') + ' - ' + bookTitle}
|
||||
className={clsx(
|
||||
'header-title z-15 bg-base-100 pointer-events-none hidden flex-1 items-center justify-center sm:flex',
|
||||
!windowButtonVisible && 'absolute inset-0',
|
||||
)}
|
||||
>
|
||||
<h2
|
||||
<div
|
||||
aria-hidden='true'
|
||||
className={clsx(
|
||||
'line-clamp-1 text-center text-xs font-semibold',
|
||||
!windowButtonVisible && 'max-w-[50%]',
|
||||
)}
|
||||
>
|
||||
{bookTitle}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='bg-base-100 z-20 ml-auto flex h-full items-center space-x-4 ps-2'>
|
||||
<SettingsToggler />
|
||||
<NotebookToggler bookKey={bookKey} />
|
||||
<Dropdown
|
||||
label={_('View Options')}
|
||||
className='exclude-title-bar-mousedown dropdown-bottom dropdown-end'
|
||||
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
|
||||
toggleButton={<PiDotsThreeVerticalBold size={iconSize16} />}
|
||||
|
||||
@@ -215,126 +215,128 @@ export const KOSyncSettingsWindow: React.FC = () => {
|
||||
title={_('KOReader Sync Settings')}
|
||||
boxClassName='sm:!min-w-[520px] sm:h-auto'
|
||||
>
|
||||
<div className='mb-4 mt-0 flex flex-col gap-4 p-2 sm:p-4'>
|
||||
{isConfigured ? (
|
||||
<>
|
||||
<div className='text-center'>
|
||||
<p className='text-base-content/80 text-sm'>
|
||||
{_('Sync as {{userDisplayName}}', {
|
||||
userDisplayName: settings.kosync.username,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div className='flex h-14 items-center justify-between'>
|
||||
<span className='text-base-content/80'>{_('Sync Server Connected')}</span>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='toggle'
|
||||
checked={settings.kosync.enabled}
|
||||
onChange={() => handleDisconnect()}
|
||||
/>
|
||||
</div>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Sync Strategy')}</span>
|
||||
</label>
|
||||
<StyledSelect
|
||||
value={settings.kosync.strategy}
|
||||
onChange={handleStrategyChange}
|
||||
options={[
|
||||
{ value: 'prompt', label: _('Ask on conflict') },
|
||||
{ value: 'silent', label: _('Always use latest') },
|
||||
{ value: 'send', label: _('Send changes only') },
|
||||
{ value: 'receive', label: _('Receive changes only') },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Checksum Method')}</span>
|
||||
</label>
|
||||
<StyledSelect
|
||||
value={settings.kosync.checksumMethod}
|
||||
onChange={handleChecksumMethodChange}
|
||||
options={[
|
||||
{ value: 'binary', label: _('File Content (recommended)') },
|
||||
{ value: 'filename', label: _('File Name'), disabled: true },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Device Name')}</span>
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
placeholder={osName ? `Readest (${osName})` : 'Readest'}
|
||||
className='input input-bordered h-12 w-full focus:outline-none focus:ring-0'
|
||||
value={deviceName}
|
||||
onChange={handleDeviceNameChange}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className='text-base-content/70 text-center text-sm'>
|
||||
{_('Connect to your KOReader Sync server.')}
|
||||
</p>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Server URL')}</span>
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
placeholder='https://koreader.sync.server'
|
||||
className='input input-bordered h-12 w-full focus:outline-none focus:ring-0'
|
||||
spellCheck='false'
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<form className='flex flex-col gap-4'>
|
||||
{isOpen && (
|
||||
<div className='mb-4 mt-0 flex flex-col gap-4 p-2 sm:p-4'>
|
||||
{isConfigured ? (
|
||||
<>
|
||||
<div className='text-center'>
|
||||
<p className='text-base-content/80 text-sm'>
|
||||
{_('Sync as {{userDisplayName}}', {
|
||||
userDisplayName: settings.kosync.username,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div className='flex h-14 items-center justify-between'>
|
||||
<span className='text-base-content/80'>{_('Sync Server Connected')}</span>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='toggle'
|
||||
checked={settings.kosync.enabled}
|
||||
onChange={() => handleDisconnect()}
|
||||
/>
|
||||
</div>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Username')}</span>
|
||||
<span className='label-text font-medium'>{_('Sync Strategy')}</span>
|
||||
</label>
|
||||
<StyledSelect
|
||||
value={settings.kosync.strategy}
|
||||
onChange={handleStrategyChange}
|
||||
options={[
|
||||
{ value: 'prompt', label: _('Ask on conflict') },
|
||||
{ value: 'silent', label: _('Always use latest') },
|
||||
{ value: 'send', label: _('Send changes only') },
|
||||
{ value: 'receive', label: _('Receive changes only') },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Checksum Method')}</span>
|
||||
</label>
|
||||
<StyledSelect
|
||||
value={settings.kosync.checksumMethod}
|
||||
onChange={handleChecksumMethodChange}
|
||||
options={[
|
||||
{ value: 'binary', label: _('File Content (recommended)') },
|
||||
{ value: 'filename', label: _('File Name'), disabled: true },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Device Name')}</span>
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
placeholder={_('Your Username')}
|
||||
placeholder={osName ? `Readest (${osName})` : 'Readest'}
|
||||
className='input input-bordered h-12 w-full focus:outline-none focus:ring-0'
|
||||
spellCheck='false'
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
autoComplete='username'
|
||||
value={deviceName}
|
||||
onChange={handleDeviceNameChange}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className='text-base-content/70 text-center text-sm'>
|
||||
{_('Connect to your KOReader Sync server.')}
|
||||
</p>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Password')}</span>
|
||||
<span className='label-text font-medium'>{_('Server URL')}</span>
|
||||
</label>
|
||||
<input
|
||||
type='password'
|
||||
placeholder={_('Your Password')}
|
||||
type='text'
|
||||
placeholder='https://koreader.sync.server'
|
||||
className='input input-bordered h-12 w-full focus:outline-none focus:ring-0'
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
autoComplete='current-password'
|
||||
spellCheck='false'
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<button
|
||||
className='btn btn-primary mt-2 h-12 min-h-12 w-full'
|
||||
onClick={handleConnect}
|
||||
disabled={isConnecting || !url || !username || !password}
|
||||
>
|
||||
{isConnecting ? <span className='loading loading-spinner'></span> : _('Connect')}
|
||||
</button>
|
||||
{connectionStatus && (
|
||||
<div className='text-error h-4 text-center text-sm'>{connectionStatus}</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<form className='flex flex-col gap-4'>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Username')}</span>
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
placeholder={_('Your Username')}
|
||||
className='input input-bordered h-12 w-full focus:outline-none focus:ring-0'
|
||||
spellCheck='false'
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
autoComplete='username'
|
||||
/>
|
||||
</div>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Password')}</span>
|
||||
</label>
|
||||
<input
|
||||
type='password'
|
||||
placeholder={_('Your Password')}
|
||||
className='input input-bordered h-12 w-full focus:outline-none focus:ring-0'
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
autoComplete='current-password'
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<button
|
||||
className='btn btn-primary mt-2 h-12 min-h-12 w-full'
|
||||
onClick={handleConnect}
|
||||
disabled={isConnecting || !url || !username || !password}
|
||||
>
|
||||
{isConnecting ? <span className='loading loading-spinner'></span> : _('Connect')}
|
||||
</button>
|
||||
{connectionStatus && (
|
||||
<div className='text-error h-4 text-center text-sm'>{connectionStatus}</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -65,6 +65,8 @@ const ProgressInfoView: React.FC<PageInfoProps> = ({
|
||||
isVertical ? 'writing-vertical-rl' : 'w-full',
|
||||
isScrolled && !isVertical && 'bg-base-100',
|
||||
)}
|
||||
role='group'
|
||||
aria-label={_('Progress Information')}
|
||||
style={
|
||||
isVertical
|
||||
? {
|
||||
|
||||
@@ -51,6 +51,7 @@ const SectionInfo: React.FC<SectionInfoProps> = ({
|
||||
isVertical ? 'writing-vertical-rl max-h-[85%]' : 'top-0 h-[44px]',
|
||||
isScrolled && !isVertical && 'bg-base-100',
|
||||
)}
|
||||
role='contentinfo'
|
||||
style={
|
||||
isVertical
|
||||
? {
|
||||
@@ -68,7 +69,7 @@ const SectionInfo: React.FC<SectionInfoProps> = ({
|
||||
}
|
||||
}
|
||||
>
|
||||
<h2
|
||||
<span
|
||||
className={clsx(
|
||||
'text-neutral-content text-center font-sans text-xs font-light',
|
||||
isVertical ? '' : 'line-clamp-1',
|
||||
@@ -76,7 +77,7 @@ const SectionInfo: React.FC<SectionInfoProps> = ({
|
||||
)}
|
||||
>
|
||||
{section || ''}
|
||||
</h2>
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -211,6 +211,8 @@ const Notebook: React.FC = ({}) => {
|
||||
appService?.hasRoundedWindow && 'rounded-window-top-right rounded-window-bottom-right',
|
||||
isNotebookPinned ? 'z-20' : 'z-[45] shadow-2xl',
|
||||
)}
|
||||
role='group'
|
||||
aria-label={_('Notebook')}
|
||||
dir={viewSettings?.rtl && languageDir === 'rtl' ? 'rtl' : 'ltr'}
|
||||
style={{
|
||||
width: `${notebookWidth}`,
|
||||
|
||||
@@ -212,6 +212,7 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
|
||||
</div>
|
||||
<div className='flex h-full items-center justify-end gap-x-2'>
|
||||
<Dropdown
|
||||
label={_('Settings Menu')}
|
||||
className='dropdown-bottom dropdown-end'
|
||||
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0 flex items-center justify-center'
|
||||
toggleButton={<PiDotsThreeVerticalBold />}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { FiSearch } from 'react-icons/fi';
|
||||
import { MdOutlineMenu, MdOutlinePushPin, MdPushPin } from 'react-icons/md';
|
||||
import { MdArrowBackIosNew } from 'react-icons/md';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import { useTrafficLightStore } from '@/store/trafficLightStore';
|
||||
import Dropdown from '@/components/Dropdown';
|
||||
@@ -18,6 +19,7 @@ const SidebarHeader: React.FC<{
|
||||
onTogglePin: () => void;
|
||||
onToggleSearchBar: () => void;
|
||||
}> = ({ isPinned, isSearchBarVisible, onGoToLibrary, onClose, onTogglePin, onToggleSearchBar }) => {
|
||||
const _ = useTranslation();
|
||||
const { appService } = useEnv();
|
||||
const {
|
||||
isTrafficLightVisible,
|
||||
@@ -75,6 +77,7 @@ const SidebarHeader: React.FC<{
|
||||
<FiSearch size={iconSize18} className='text-base-content' />
|
||||
</button>
|
||||
<Dropdown
|
||||
label={_('Book Menu')}
|
||||
className={clsx(
|
||||
window.innerWidth < 640 && 'dropdown-end',
|
||||
'dropdown-bottom flex justify-center',
|
||||
|
||||
@@ -206,6 +206,7 @@ const SearchBar: React.FC<SearchBarProps> = ({
|
||||
|
||||
<div className='bg-base-300 flex h-8 w-8 items-center rounded-r-lg'>
|
||||
<Dropdown
|
||||
label={_('Search Options')}
|
||||
className={clsx(
|
||||
window.innerWidth < 640 && 'dropdown-end',
|
||||
'dropdown-bottom flex justify-center',
|
||||
|
||||
@@ -216,6 +216,8 @@ const SideBar: React.FC<{
|
||||
appService?.hasRoundedWindow && 'rounded-window-top-left rounded-window-bottom-left',
|
||||
isSideBarPinned ? 'z-20' : 'z-[45] shadow-2xl',
|
||||
)}
|
||||
role='group'
|
||||
aria-label={_('Sidebar')}
|
||||
dir={viewSettings?.rtl && languageDir === 'rtl' ? 'rtl' : 'ltr'}
|
||||
style={{
|
||||
width: `${sideBarWidth}`,
|
||||
|
||||
@@ -2,6 +2,7 @@ import clsx from 'clsx';
|
||||
import { useRef } from 'react';
|
||||
import { IoArrowBack } from 'react-icons/io5';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useTrafficLightStore } from '@/store/trafficLightStore';
|
||||
import WindowButtons from '@/components/WindowButtons';
|
||||
|
||||
@@ -10,6 +11,7 @@ interface ProfileHeaderProps {
|
||||
}
|
||||
|
||||
const ProfileHeader: React.FC<ProfileHeaderProps> = ({ onGoBack }) => {
|
||||
const _ = useTranslation();
|
||||
const { appService } = useEnv();
|
||||
const { isTrafficLightVisible } = useTrafficLightStore();
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -23,6 +25,7 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = ({ onGoBack }) => {
|
||||
)}
|
||||
>
|
||||
<button
|
||||
aria-label={_('Go Back')}
|
||||
onClick={onGoBack}
|
||||
className={clsx('btn btn-ghost h-12 min-h-12 w-12 p-0 sm:h-8 sm:min-h-8 sm:w-8')}
|
||||
>
|
||||
|
||||
@@ -11,39 +11,23 @@ interface ButtonProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Button: React.FC<ButtonProps> = ({
|
||||
icon,
|
||||
onClick,
|
||||
disabled = false,
|
||||
tooltip,
|
||||
tooltipDirection = 'top',
|
||||
className,
|
||||
}) => {
|
||||
const Button: React.FC<ButtonProps> = ({ icon, onClick, disabled = false, tooltip, className }) => {
|
||||
const { appService } = useEnv();
|
||||
return (
|
||||
<div
|
||||
<button
|
||||
className={clsx(
|
||||
'lg:tooltip z-50 h-8 min-h-8 w-8',
|
||||
tooltip && `lg:tooltip-${tooltipDirection}`,
|
||||
{
|
||||
'tooltip-hidden': !tooltip,
|
||||
},
|
||||
'btn btn-ghost h-8 min-h-8 w-8 p-0',
|
||||
appService?.isMobileApp && 'hover:bg-transparent',
|
||||
disabled && 'btn-disabled !bg-transparent opacity-50',
|
||||
className,
|
||||
)}
|
||||
data-tip={tooltip}
|
||||
title={tooltip}
|
||||
aria-label={tooltip}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<button
|
||||
className={clsx(
|
||||
'btn btn-ghost h-8 min-h-8 w-8 p-0',
|
||||
appService?.isMobileApp && 'hover:bg-transparent',
|
||||
disabled && 'btn-disabled !bg-transparent opacity-50',
|
||||
className,
|
||||
)}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{icon}
|
||||
</button>
|
||||
</div>
|
||||
{icon}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { MdArrowBackIosNew, MdArrowForwardIos } from 'react-icons/md';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useDrag } from '@/hooks/useDrag';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useDeviceControlStore } from '@/store/deviceStore';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import { impactFeedback } from '@tauri-apps/plugin-haptics';
|
||||
@@ -41,12 +42,14 @@ const Dialog: React.FC<DialogProps> = ({
|
||||
contentClassName,
|
||||
onClose,
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const { appService } = useEnv();
|
||||
const { systemUIVisible, statusBarHeight, safeAreaInsets } = useThemeStore();
|
||||
const { acquireBackKeyInterception, releaseBackKeyInterception } = useDeviceControlStore();
|
||||
const [isFullHeightInMobile, setIsFullHeightInMobile] = useState(!snapHeight);
|
||||
const [isRtl] = useState(() => getDirFromUILanguage() === 'rtl');
|
||||
const dialogRef = useRef<HTMLDialogElement>(null);
|
||||
const previousActiveElementRef = useRef<HTMLElement | null>(null);
|
||||
const iconSize22 = useResponsiveSize(22);
|
||||
const isMobile = window.innerWidth < 640 || window.innerHeight < 640;
|
||||
|
||||
@@ -63,14 +66,30 @@ const Dialog: React.FC<DialogProps> = ({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
if (!isOpen) {
|
||||
if (previousActiveElementRef.current) {
|
||||
previousActiveElementRef.current.focus();
|
||||
previousActiveElementRef.current = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
previousActiveElementRef.current = document.activeElement as HTMLElement;
|
||||
|
||||
setIsFullHeightInMobile(!snapHeight && isMobile);
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
if (appService?.isAndroidApp) {
|
||||
acquireBackKeyInterception();
|
||||
eventDispatcher.onSync('native-key-down', handleKeyDown);
|
||||
}
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
if (dialogRef.current) {
|
||||
dialogRef.current.focus();
|
||||
}
|
||||
}, 100);
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
if (appService?.isAndroidApp) {
|
||||
releaseBackKeyInterception();
|
||||
@@ -155,7 +174,9 @@ const Dialog: React.FC<DialogProps> = ({
|
||||
<dialog
|
||||
ref={dialogRef}
|
||||
id={id ?? 'dialog'}
|
||||
tabIndex={-1}
|
||||
open={isOpen}
|
||||
aria-hidden={!isOpen}
|
||||
className={clsx(
|
||||
'modal sm:min-w-90 z-50 h-full w-full !items-start !bg-transparent sm:w-full sm:!items-center',
|
||||
className,
|
||||
@@ -204,7 +225,7 @@ const Dialog: React.FC<DialogProps> = ({
|
||||
) : (
|
||||
<div className='flex h-11 w-full items-center justify-between'>
|
||||
<button
|
||||
tabIndex={-1}
|
||||
aria-label={_('Close')}
|
||||
onClick={onClose}
|
||||
className={
|
||||
'btn btn-ghost btn-circle flex h-8 min-h-8 w-8 hover:bg-transparent focus:outline-none sm:hidden'
|
||||
@@ -220,7 +241,7 @@ const Dialog: React.FC<DialogProps> = ({
|
||||
<span className='line-clamp-1 text-center font-bold'>{title ?? ''}</span>
|
||||
</div>
|
||||
<button
|
||||
tabIndex={-1}
|
||||
aria-label={_('Close')}
|
||||
onClick={onClose}
|
||||
className={
|
||||
'bg-base-300/65 btn btn-ghost btn-circle ml-auto hidden h-6 min-h-6 w-6 focus:outline-none sm:flex'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useState, isValidElement, ReactElement, ReactNode } from 'react';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import React, { useState, isValidElement, ReactElement, ReactNode, useRef } from 'react';
|
||||
import { Overlay } from './Overlay';
|
||||
import MenuItem from './MenuItem';
|
||||
|
||||
interface DropdownProps {
|
||||
label: string;
|
||||
className?: string;
|
||||
menuClassName?: string;
|
||||
buttonClassName?: string;
|
||||
@@ -52,6 +52,7 @@ const enhanceMenuItems = (
|
||||
};
|
||||
|
||||
const Dropdown: React.FC<DropdownProps> = ({
|
||||
label,
|
||||
className,
|
||||
menuClassName,
|
||||
buttonClassName,
|
||||
@@ -59,14 +60,45 @@ const Dropdown: React.FC<DropdownProps> = ({
|
||||
children,
|
||||
onToggle,
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const [isPointerDown, setIsPointerDown] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const toggleDropdown = () => {
|
||||
console.error('Toggling dropdown, current state:', isOpen);
|
||||
const newIsOpen = !isOpen;
|
||||
setIsDropdownOpen(newIsOpen);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
if (!isOpen) setIsDropdownOpen(true);
|
||||
e.stopPropagation();
|
||||
} else if (e.key === 'Escape' && isOpen) {
|
||||
setIsDropdownOpen(false);
|
||||
e.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
const handleFocus = () => {
|
||||
if (!isOpen && !isPointerDown) {
|
||||
setIsDropdownOpen(true);
|
||||
}
|
||||
setIsFocused(true);
|
||||
};
|
||||
|
||||
const handleBlur = (e: React.FocusEvent) => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const relatedTarget = e.relatedTarget;
|
||||
if (relatedTarget && !containerRef.current.contains(relatedTarget)) {
|
||||
console.log('Dropdown lost focus, closing menu');
|
||||
setIsFocused(false);
|
||||
setIsDropdownOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
const setIsDropdownOpen = (isOpen: boolean) => {
|
||||
setIsOpen(isOpen);
|
||||
onToggle?.(isOpen);
|
||||
@@ -86,28 +118,28 @@ const Dropdown: React.FC<DropdownProps> = ({
|
||||
<div className='dropdown-container flex'>
|
||||
{isOpen && <Overlay onDismiss={() => setIsDropdownOpen(false)} />}
|
||||
<div
|
||||
ref={containerRef}
|
||||
role='menu'
|
||||
tabIndex={0}
|
||||
role='button'
|
||||
aria-label={_('Menu')}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
if (!isOpen) toggleDropdown();
|
||||
} else if (e.key === 'Escape' && isOpen) {
|
||||
toggleDropdown();
|
||||
} else {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}}
|
||||
className={clsx('dropdown', className)}
|
||||
aria-label={label}
|
||||
title={label}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
onPointerDown={() => setIsPointerDown(true)}
|
||||
onPointerUp={() => setIsPointerDown(false)}
|
||||
className={clsx('dropdown flex flex-col', className)}
|
||||
>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||
<div
|
||||
role='none'
|
||||
className={clsx('dropdown-toggle', buttonClassName, isFocused && 'bg-base-300/50')}
|
||||
onClick={toggleDropdown}
|
||||
className={clsx('dropdown-toggle', buttonClassName, isOpen && 'bg-base-300/50')}
|
||||
>
|
||||
{toggleButton}
|
||||
</div>
|
||||
{isOpen && childrenWithToggle}
|
||||
<div role='none' className={clsx('flex items-center justify-center', !isOpen && 'hidden')}>
|
||||
{isOpen && childrenWithToggle}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
interface MenuProps {
|
||||
children: React.ReactNode;
|
||||
label: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Menu: React.FC<MenuProps> = ({ children, className }) => {
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
if (menuRef.current) {
|
||||
const firstItem = menuRef.current.querySelector('[role="menuitem"]');
|
||||
if (firstItem) {
|
||||
(firstItem as HTMLElement).focus();
|
||||
}
|
||||
}
|
||||
}, 200);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={menuRef} role='none' className={clsx(className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Menu;
|
||||
@@ -1,10 +1,13 @@
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { IconType } from 'react-icons';
|
||||
import { MdCheck } from 'react-icons/md';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
|
||||
interface MenuItemProps {
|
||||
label: string;
|
||||
toggled?: boolean;
|
||||
description?: string;
|
||||
tooltip?: string;
|
||||
buttonClass?: string;
|
||||
@@ -12,7 +15,7 @@ interface MenuItemProps {
|
||||
shortcut?: string;
|
||||
disabled?: boolean;
|
||||
noIcon?: boolean;
|
||||
transient?: boolean; // For transient items the dropdown will close on click
|
||||
transient?: boolean;
|
||||
Icon?: React.ReactNode | IconType;
|
||||
children?: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
@@ -21,6 +24,7 @@ interface MenuItemProps {
|
||||
|
||||
const MenuItem: React.FC<MenuItemProps> = ({
|
||||
label,
|
||||
toggled,
|
||||
description,
|
||||
tooltip,
|
||||
buttonClass,
|
||||
@@ -34,36 +38,30 @@ const MenuItem: React.FC<MenuItemProps> = ({
|
||||
onClick,
|
||||
setIsDropdownOpen,
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const iconSize = useResponsiveSize(16);
|
||||
const menuButton = (
|
||||
<button
|
||||
tabIndex={0}
|
||||
className={clsx(
|
||||
'hover:bg-base-300 text-base-content flex w-full flex-col items-center justify-center rounded-md p-1 py-[10px]',
|
||||
disabled && 'btn-disabled text-gray-400',
|
||||
buttonClass,
|
||||
)}
|
||||
aria-label={label}
|
||||
data-tip={tooltip ? tooltip : ''}
|
||||
onClick={() => {
|
||||
onClick?.();
|
||||
if (transient) {
|
||||
setIsDropdownOpen?.(false);
|
||||
}
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
const IconType = Icon || (toggled !== undefined ? (toggled ? MdCheck : undefined) : undefined);
|
||||
|
||||
const handleClick = () => {
|
||||
onClick?.();
|
||||
if (transient) {
|
||||
setIsDropdownOpen?.(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonContent = (
|
||||
<>
|
||||
<div className='flex w-full items-center justify-between'>
|
||||
<div className='flex min-w-0 items-center'>
|
||||
{!noIcon && (
|
||||
<span style={{ minWidth: `${iconSize}px` }}>
|
||||
{typeof Icon === 'function' ? (
|
||||
<Icon
|
||||
{typeof IconType === 'function' ? (
|
||||
<IconType
|
||||
className={disabled ? 'text-gray-400' : 'text-base-content'}
|
||||
size={iconSize}
|
||||
/>
|
||||
) : (
|
||||
Icon
|
||||
IconType
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
@@ -96,22 +94,50 @@ const MenuItem: React.FC<MenuItemProps> = ({
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
|
||||
if (children) {
|
||||
return (
|
||||
<ul className='menu rounded-box m-0 p-0'>
|
||||
<li>
|
||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-to-interactive-role
|
||||
<ul className='menu rounded-box m-0 p-0' role='menuitem' tabIndex={-1}>
|
||||
<li aria-label={label}>
|
||||
<details>
|
||||
<summary className='hover:bg-base-300 p-0 pr-3'>{menuButton}</summary>
|
||||
<summary
|
||||
className={clsx(
|
||||
'hover:bg-base-300 text-base-content cursor-pointer rounded-md p-1 py-[10px] pr-3',
|
||||
disabled && 'btn-disabled cursor-not-allowed text-gray-400',
|
||||
buttonClass,
|
||||
)}
|
||||
title={tooltip ? tooltip : ''}
|
||||
>
|
||||
{buttonContent}
|
||||
</summary>
|
||||
{children}
|
||||
</details>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
return menuButton;
|
||||
|
||||
return (
|
||||
<button
|
||||
role={disabled ? 'none' : 'menuitem'}
|
||||
aria-label={toggled !== undefined ? `${label} - ${toggled ? _('ON') : _('OFF')}` : undefined}
|
||||
aria-live={toggled === undefined ? 'polite' : 'off'}
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
className={clsx(
|
||||
'hover:bg-base-300 text-base-content flex w-full flex-col items-center justify-center rounded-md p-1 py-[10px]',
|
||||
disabled && 'btn-disabled text-gray-400',
|
||||
buttonClass,
|
||||
)}
|
||||
title={tooltip ? tooltip : ''}
|
||||
onClick={handleClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{buttonContent}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default MenuItem;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
|
||||
interface OverlayProps {
|
||||
onDismiss: () => void;
|
||||
@@ -8,9 +7,7 @@ interface OverlayProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Overlay: React.FC<OverlayProps> = ({ onDismiss, dismissLabel, className }) => {
|
||||
const _ = useTranslation();
|
||||
|
||||
export const Overlay: React.FC<OverlayProps> = ({ onDismiss, className }) => {
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Escape' || e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
@@ -21,9 +18,8 @@ export const Overlay: React.FC<OverlayProps> = ({ onDismiss, dismissLabel, class
|
||||
return (
|
||||
<div
|
||||
className={clsx('overlay fixed inset-0 cursor-default', className)}
|
||||
role='button'
|
||||
role='none'
|
||||
tabIndex={-1}
|
||||
aria-label={dismissLabel || _('Dismiss')}
|
||||
onClick={onDismiss}
|
||||
onContextMenu={onDismiss}
|
||||
onKeyDown={handleKeyDown}
|
||||
|
||||
@@ -70,6 +70,7 @@ const BookDetailView: React.FC<BookDetailViewProps> = ({
|
||||
)}
|
||||
{onDelete && (
|
||||
<Dropdown
|
||||
label={_('Delete Book Options')}
|
||||
className='dropdown-bottom flex justify-center'
|
||||
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
|
||||
toggleButton={<MdOutlineDelete className='fill-red-500' />}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
interface UseAutoFocusOptions {
|
||||
ref?: React.RefObject<HTMLElement | null>;
|
||||
delay?: number;
|
||||
condition?: boolean;
|
||||
preventScroll?: boolean;
|
||||
}
|
||||
|
||||
export const useAutoFocus = <T extends HTMLElement = HTMLDivElement>(
|
||||
options: UseAutoFocusOptions = {},
|
||||
) => {
|
||||
const { delay = 0, condition = true, preventScroll = false, ref } = options;
|
||||
const localRef = useRef<T>(null);
|
||||
const focusRef = ref || localRef;
|
||||
|
||||
useEffect(() => {
|
||||
if (condition && focusRef.current) {
|
||||
const focusElement = () => {
|
||||
focusRef.current?.focus({ preventScroll });
|
||||
};
|
||||
|
||||
if (delay > 0) {
|
||||
const timer = setTimeout(focusElement, delay);
|
||||
return () => clearTimeout(timer);
|
||||
} else {
|
||||
focusElement();
|
||||
}
|
||||
}
|
||||
return;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [condition, delay, preventScroll]);
|
||||
|
||||
return focusRef as React.RefObject<T>;
|
||||
};
|
||||
Reference in New Issue
Block a user