forked from akai/readest
a11y: accessibility for mobile platforms (#2044)
This commit is contained in:
@@ -116,6 +116,7 @@
|
||||
"Book Details": "تفاصيل الكتاب",
|
||||
"From Local File": "من ملف محلي",
|
||||
"TOC": "جدول المحتويات",
|
||||
"Table of Contents": "جدول المحتويات",
|
||||
"Book uploaded: {{title}}": "تم تحميل الكتاب: {{title}}",
|
||||
"Failed to upload book: {{title}}": "فشل تحميل الكتاب: {{title}}",
|
||||
"Book downloaded: {{title}}": "تم تنزيل الكتاب: {{title}}",
|
||||
@@ -558,5 +559,7 @@
|
||||
"Close": "إغلاق",
|
||||
"Delete Book Options": "خيارات حذف الكتاب",
|
||||
"ON": "تشغيل",
|
||||
"OFF": "إيقاف"
|
||||
"OFF": "إيقاف",
|
||||
"Reading Progress": "تقدم القراءة",
|
||||
"Page Margin": "هامش الصفحة"
|
||||
}
|
||||
|
||||
@@ -323,6 +323,7 @@
|
||||
"Match Whole Words": "পূর্ণ শব্দ মিলান",
|
||||
"Match Diacritics": "ডায়াক্রিটিক্স মিলান",
|
||||
"TOC": "সূচিপত্র",
|
||||
"Table of Contents": "সূচিপত্র",
|
||||
"Sidebar": "সাইডবার",
|
||||
"Disable Translation": "অনুবাদ নিষ্ক্রিয়",
|
||||
"Translation Not Available": "অনুবাদ উপলব্ধ নয়",
|
||||
@@ -542,5 +543,7 @@
|
||||
"Close": "বন্ধ করুন",
|
||||
"Delete Book Options": "বই মুছে ফেলার অপশন",
|
||||
"ON": "চালু",
|
||||
"OFF": "বন্ধ"
|
||||
"OFF": "বন্ধ",
|
||||
"Reading Progress": "পড়ার অগ্রগতি",
|
||||
"Page Margin": "পৃষ্ঠার মার্জিন"
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"Book Details": "དཔེ་དེབ་ཀྱི་གནས་ཚུལ་རྒྱས་པ།",
|
||||
"From Local File": "ས་གནས་ཀྱི་ཡིག་ཆ་ནས་ནང་འདྲེན།",
|
||||
"TOC": "དཀར་ཆག",
|
||||
"Table of Contents": "དཀར་ཆག",
|
||||
"Book uploaded: {{title}}": "དཔེ་དེབ་སྤྲད་ཟིན། {{title}}",
|
||||
"Failed to upload book: {{title}}": "དཔེ་དེབ་སྤྲད་མ་ཐུབ། {{title}}",
|
||||
"Book downloaded: {{title}}": "དཔེ་དེབ་ཕབ་ལེན་བྱས་ཟིན། {{title}}",
|
||||
@@ -538,5 +539,7 @@
|
||||
"Close": "ཁ་རྒྱག",
|
||||
"Delete Book Options": "དེབ་བཏོན་གཏང་གི་གདམ་ཁ",
|
||||
"ON": "འབྱུང་ཁུངས",
|
||||
"OFF": "བརྗེ་བ"
|
||||
"OFF": "བརྗེ་བ",
|
||||
"Reading Progress": "ཀློག་པའི་ཡར་འཕེལ།",
|
||||
"Page Margin": "ཤོག་གདོང་།"
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
"Book Details": "Buchdetails",
|
||||
"From Local File": "Aus lokaler Datei",
|
||||
"TOC": "Inhaltsverzeichnis",
|
||||
"Table of Contents": "Inhaltsverzeichnis",
|
||||
"Book uploaded: {{title}}": "Buch hochgeladen: {{title}}",
|
||||
"Failed to upload book: {{title}}": "Fehler beim Hochladen des Buches: {{title}}",
|
||||
"Book downloaded: {{title}}": "Buch heruntergeladen: {{title}}",
|
||||
@@ -542,5 +543,7 @@
|
||||
"Close": "Schließen",
|
||||
"Delete Book Options": "Buch-Löschoptionen",
|
||||
"ON": "EIN",
|
||||
"OFF": "AUS"
|
||||
"OFF": "AUS",
|
||||
"Reading Progress": "Lesefortschritt",
|
||||
"Page Margin": "Seitenrand"
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"Book Details": "Λεπτομέρειες βιβλίου",
|
||||
"From Local File": "Από τοπικό αρχείο",
|
||||
"TOC": "Πίνακας περιεχομένων",
|
||||
"Table of Contents": "Πίνακας περιεχομένων",
|
||||
"Book uploaded: {{title}}": "Το βιβλίο με τίτλο {{title}} ανέβηκε",
|
||||
"Failed to upload book: {{title}}": "Αποτυχία ανέβασμα βιβλίου: {{title}}",
|
||||
"Book downloaded: {{title}}": "Το βιβλίο με τίτλο {{title}} κατέβηκε",
|
||||
@@ -542,5 +543,7 @@
|
||||
"Close": "Κλείσιμο",
|
||||
"Delete Book Options": "Επιλογές διαγραφής βιβλίου",
|
||||
"ON": "ΕΝΕΡΓΟΠΟΙΗΣΗ",
|
||||
"OFF": "ΑΠΕΝΕΡΓΟΠΟΙΗΣΗ"
|
||||
"OFF": "ΑΠΕΝΕΡΓΟΠΟΙΗΣΗ",
|
||||
"Reading Progress": "Πρόοδος ανάγνωσης",
|
||||
"Page Margin": "Περιθώριο σελίδας"
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@
|
||||
"Book Details": "Detalles del libro",
|
||||
"From Local File": "Desde archivo local",
|
||||
"TOC": "Índice",
|
||||
"Table of Contents": "Índice",
|
||||
"Book uploaded: {{title}}": "Libro subido: {{title}}",
|
||||
"Failed to upload book: {{title}}": "Error al subir libro: {{title}}",
|
||||
"Book downloaded: {{title}}": "Libro descargado: {{title}}",
|
||||
@@ -546,5 +547,7 @@
|
||||
"Close": "Cerrar",
|
||||
"Delete Book Options": "Opciones de Eliminar Libro",
|
||||
"ON": "ENCENDIDO",
|
||||
"OFF": "APAGADO"
|
||||
"OFF": "APAGADO",
|
||||
"Reading Progress": "Progreso de Lectura",
|
||||
"Page Margin": "Márgenes de Página"
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"Book Details": "Détails du livre",
|
||||
"From Local File": "Depuis un fichier local",
|
||||
"TOC": "Table des matières",
|
||||
"Table of Contents": "Table des matières",
|
||||
"Book uploaded: {{title}}": "Livre téléchargé : {{title}}",
|
||||
"Failed to upload book: {{title}}": "Échec du téléchargement du livre : {{title}}",
|
||||
"Book downloaded: {{title}}": "Livre téléchargé : {{title}}",
|
||||
@@ -546,5 +547,7 @@
|
||||
"Close": "Fermer",
|
||||
"Delete Book Options": "Options de suppression du livre",
|
||||
"ON": "ACTIVER",
|
||||
"OFF": "DÉSACTIVER"
|
||||
"OFF": "DÉSACTIVER",
|
||||
"Reading Progress": "Progression de lecture",
|
||||
"Page Margin": "Marge de page"
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"Book Details": "पुस्तक विवरण",
|
||||
"From Local File": "स्थानीय फ़ाइल से",
|
||||
"TOC": "सामग्री",
|
||||
"Table of Contents": "सामग्री",
|
||||
"Book uploaded: {{title}}": "पुस्तक अपलोड की गई: {{title}}",
|
||||
"Failed to upload book: {{title}}": "पुस्तक अपलोड करने में विफल: {{title}}",
|
||||
"Book downloaded: {{title}}": "पुस्तक डाउनलोड की गई: {{title}}",
|
||||
@@ -542,5 +543,7 @@
|
||||
"Close": "बंद करें",
|
||||
"Delete Book Options": "पुस्तक हटाने के विकल्प",
|
||||
"ON": "चालू",
|
||||
"OFF": "बंद"
|
||||
"OFF": "बंद",
|
||||
"Reading Progress": "पढ़ने की प्रगति",
|
||||
"Page Margin": "पृष्ठ का मार्जिन"
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"Book Details": "Detail Buku",
|
||||
"From Local File": "Dari file lokal",
|
||||
"TOC": "Daftar Isi",
|
||||
"Table of Contents": "Daftar Isi",
|
||||
"Book uploaded: {{title}}": "Buku diunggah: {{title}}",
|
||||
"Failed to upload book: {{title}}": "Gagal mengunggah buku: {{title}}",
|
||||
"Book downloaded: {{title}}": "Buku diunduh: {{title}}",
|
||||
@@ -538,5 +539,7 @@
|
||||
"Close": "Tutup",
|
||||
"Delete Book Options": "Opsi Hapus Buku",
|
||||
"ON": "AKTIF",
|
||||
"OFF": "TIDAK AKTIF"
|
||||
"OFF": "TIDAK AKTIF",
|
||||
"Reading Progress": "Progres Membaca",
|
||||
"Page Margin": "Margin Halaman"
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"Book Details": "Dettagli libro",
|
||||
"From Local File": "Da file locale",
|
||||
"TOC": "Sommario",
|
||||
"Table of Contents": "Sommario",
|
||||
"Book uploaded: {{title}}": "Libro caricato: {{title}}",
|
||||
"Failed to upload book: {{title}}": "Caricamento libro non riuscito: {{title}}",
|
||||
"Book downloaded: {{title}}": "Libro scaricato: {{title}}",
|
||||
@@ -546,5 +547,7 @@
|
||||
"Close": "Chiudi",
|
||||
"Delete Book Options": "Opzioni di eliminazione libro",
|
||||
"ON": "ATTIVO",
|
||||
"OFF": "DISATTIVO"
|
||||
"OFF": "DISATTIVO",
|
||||
"Reading Progress": "Progresso di lettura",
|
||||
"Page Margin": "Margine di pagina"
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"Book Details": "書籍の詳細",
|
||||
"From Local File": "ローカルファイルから",
|
||||
"TOC": "目次",
|
||||
"Table of Contents": "目次",
|
||||
"Book uploaded: {{title}}": "書籍がアップロードされました:{{title}}",
|
||||
"Failed to upload book: {{title}}": "書籍のアップロードに失敗しました:{{title}}",
|
||||
"Book downloaded: {{title}}": "書籍がダウンロードされました:{{title}}",
|
||||
@@ -538,5 +539,7 @@
|
||||
"Close": "閉じる",
|
||||
"Delete Book Options": "本の削除オプション",
|
||||
"ON": "オン",
|
||||
"OFF": "オフ"
|
||||
"OFF": "オフ",
|
||||
"Reading Progress": "読書進捗",
|
||||
"Page Margin": "ページマージン"
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"Book Details": "책 세부 정보",
|
||||
"From Local File": "로컬 파일에서",
|
||||
"TOC": "목차",
|
||||
"Table of Contents": "목차",
|
||||
"Book uploaded: {{title}}": "책 업로드됨: {{title}}",
|
||||
"Failed to upload book: {{title}}": "책 업로드 실패: {{title}}",
|
||||
"Book downloaded: {{title}}": "책 다운로드됨: {{title}}",
|
||||
@@ -538,5 +539,7 @@
|
||||
"Close": "닫기",
|
||||
"Delete Book Options": "도서 삭제 옵션",
|
||||
"ON": "켜기",
|
||||
"OFF": "끄기"
|
||||
"OFF": "끄기",
|
||||
"Reading Progress": "읽기 진행 상황",
|
||||
"Page Margin": "페이지 여백"
|
||||
}
|
||||
|
||||
@@ -210,6 +210,7 @@
|
||||
"Match Whole Words": "Hele woorden",
|
||||
"Match Diacritics": "Diakritische tekens",
|
||||
"TOC": "Inhoudsopgave",
|
||||
"Table of Contents": "Inhoudsopgave",
|
||||
"Sidebar": "Zijbalk",
|
||||
"TTS not supported for PDF": "TTS niet ondersteund voor PDF",
|
||||
"No Timeout": "Geen time-out",
|
||||
@@ -542,5 +543,7 @@
|
||||
"Close": "Sluiten",
|
||||
"Delete Book Options": "Boekverwijderopties",
|
||||
"ON": "AAN",
|
||||
"OFF": "UIT"
|
||||
"OFF": "UIT",
|
||||
"Reading Progress": "Leesvoortgang",
|
||||
"Page Margin": "Pagina Marges"
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"Book Details": "Szczegóły książki",
|
||||
"From Local File": "Z pliku lokalnego",
|
||||
"TOC": "Spis treści",
|
||||
"Table of Contents": "Spis treści",
|
||||
"Book uploaded: {{title}}": "Książka przesłana: {{title}}",
|
||||
"Failed to upload book: {{title}}": "Nie udało się przesłać książki: {{title}}",
|
||||
"Book downloaded: {{title}}": "Książka pobrana: {{title}}",
|
||||
@@ -550,5 +551,7 @@
|
||||
"Close": "Zamknij",
|
||||
"Delete Book Options": "Opcje usuwania książki",
|
||||
"ON": "WŁĄCZONE",
|
||||
"OFF": "WYŁĄCZONE"
|
||||
"OFF": "WYŁĄCZONE",
|
||||
"Reading Progress": "Postęp czytania",
|
||||
"Page Margin": "Margines strony"
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"Book Details": "Detalhes do Livro",
|
||||
"From Local File": "Do arquivo local",
|
||||
"TOC": "Índice",
|
||||
"Table of Contents": "Índice",
|
||||
"Book uploaded: {{title}}": "Livro enviado: {{title}}",
|
||||
"Failed to upload book: {{title}}": "Falha ao enviar livro: {{title}}",
|
||||
"Book downloaded: {{title}}": "Livro baixado: {{title}}",
|
||||
@@ -546,5 +547,7 @@
|
||||
"Close": "Fechar",
|
||||
"Delete Book Options": "Opções de Exclusão de Livro",
|
||||
"ON": "LIGADO",
|
||||
"OFF": "DESLIGADO"
|
||||
"OFF": "DESLIGADO",
|
||||
"Reading Progress": "Progresso de Leitura",
|
||||
"Page Margin": "Margem da Página"
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"Book Details": "Детали книги",
|
||||
"From Local File": "Из локального файла",
|
||||
"TOC": "Содержание",
|
||||
"Table of Contents": "Содержание",
|
||||
"Book uploaded: {{title}}": "Книга загружена: {{title}}",
|
||||
"Failed to upload book: {{title}}": "Не удалось загрузить книгу: {{title}}",
|
||||
"Book downloaded: {{title}}": "Книга скачана: {{title}}",
|
||||
@@ -550,5 +551,7 @@
|
||||
"Close": "Закрыть",
|
||||
"Delete Book Options": "Параметры удаления книги",
|
||||
"ON": "ВКЛ",
|
||||
"OFF": "ВЫКЛ"
|
||||
"OFF": "ВЫКЛ",
|
||||
"Reading Progress": "Прогресс чтения",
|
||||
"Page Margin": "Поля страницы"
|
||||
}
|
||||
|
||||
@@ -323,6 +323,7 @@
|
||||
"Match Whole Words": "සම්පූර්ණ වචන ගැළපීම",
|
||||
"Match Diacritics": "ධ්වනි ලකුණු ගැළපීම",
|
||||
"TOC": "සූචිය",
|
||||
"Table of Contents": "සූචිය",
|
||||
"Sidebar": "පැති තීරුව",
|
||||
"Disable Translation": "පරිවර්තනය අබල කරන්න",
|
||||
"Translation Not Available": "පරිවර්තනය ලබා ගත නොහැක",
|
||||
@@ -542,5 +543,7 @@
|
||||
"Close": "වසන්න",
|
||||
"Delete Book Options": "පොත මකන විකල්ප",
|
||||
"ON": "ඇක්ටිව්",
|
||||
"OFF": "ඩිසැක්ටිව්"
|
||||
"OFF": "ඩිසැක්ටිව්",
|
||||
"Reading Progress": "කියවීමේ ප්රගතිය",
|
||||
"Page Margin": "පිටු මාර්ජින්"
|
||||
}
|
||||
|
||||
@@ -323,6 +323,7 @@
|
||||
"Match Whole Words": "முழு வார்த்தைகள் பொருத்தம்",
|
||||
"Match Diacritics": "உச்சரிப்பு குறிகள் பொருத்தம்",
|
||||
"TOC": "உள்ளடக்கம்",
|
||||
"Table of Contents": "உள்ளடக்கம்",
|
||||
"Sidebar": "பக்கப்பட்டி",
|
||||
"Disable Translation": "மொழிபெயர்ப்பை முடக்கவும்",
|
||||
"Translation Not Available": "மொழிபெயர்ப்பு கிடைக்கவில்லை",
|
||||
@@ -542,5 +543,7 @@
|
||||
"Close": "மூடு",
|
||||
"Delete Book Options": "புத்தகத்தை நீக்கும் விருப்பங்கள்",
|
||||
"ON": "செயல்படுத்தவும்",
|
||||
"OFF": "நிறுத்தவும்"
|
||||
"OFF": "நிறுத்தவும்",
|
||||
"Reading Progress": "வாசிப்பு முன்னேற்றம்",
|
||||
"Page Margin": "பக்கம் மாறின"
|
||||
}
|
||||
|
||||
@@ -273,6 +273,7 @@
|
||||
"Match Whole Words": "ตรงทั้งคำ",
|
||||
"Match Diacritics": "ตรงตามเครื่องหมายวรรณยุกต์",
|
||||
"TOC": "สารบัญ",
|
||||
"Table of Contents": "สารบัญ",
|
||||
"Sidebar": "แถบข้าง",
|
||||
"Disable Translation": "ปิดการแปล",
|
||||
"Translation Not Available": "ไม่มีการแปล",
|
||||
@@ -538,5 +539,7 @@
|
||||
"Close": "ปิด",
|
||||
"Delete Book Options": "ตัวเลือกการลบหนังสือ",
|
||||
"ON": "เปิด",
|
||||
"OFF": "ปิด"
|
||||
"OFF": "ปิด",
|
||||
"Reading Progress": "ความคืบหน้าในการอ่าน",
|
||||
"Page Margin": "ขอบหน้า"
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"Book Details": "Kitap Detayları",
|
||||
"From Local File": "Yerel dosyadan",
|
||||
"TOC": "İçindekiler",
|
||||
"Table of Contents": "İçindekiler",
|
||||
"Book uploaded: {{title}}": "Kitap yüklendi: {{title}}",
|
||||
"Failed to upload book: {{title}}": "Kitap yüklenemedi: {{title}}",
|
||||
"Book downloaded: {{title}}": "Kitap indirildi: {{title}}",
|
||||
@@ -542,5 +543,7 @@
|
||||
"Close": "Kapat",
|
||||
"Delete Book Options": "Kitap Silme Seçenekleri",
|
||||
"ON": "AÇIK",
|
||||
"OFF": "KAPALI"
|
||||
"OFF": "KAPALI",
|
||||
"Reading Progress": "Okuma İlerlemesi",
|
||||
"Page Margin": "Sayfa Kenar Boşluğu"
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"Book Details": "Деталі книги",
|
||||
"From Local File": "З локального файлу",
|
||||
"TOC": "Зміст",
|
||||
"Table of Contents": "Зміст",
|
||||
"Book uploaded: {{title}}": "Книгу завантажено: {{title}}",
|
||||
"Failed to upload book: {{title}}": "Не вдалося завантажити книгу: {{title}}",
|
||||
"Book downloaded: {{title}}": "Книгу завантажено: {{title}}",
|
||||
@@ -550,5 +551,7 @@
|
||||
"Close": "Закрити",
|
||||
"Delete Book Options": "Параметри видалення книги",
|
||||
"ON": "УВІМКНЕННЯ",
|
||||
"OFF": "ВИМКНЕННЯ"
|
||||
"OFF": "ВИМКНЕННЯ",
|
||||
"Reading Progress": "Проґрес читання",
|
||||
"Page Margin": "Поля сторінки"
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"Book Details": "Chi tiết sách",
|
||||
"From Local File": "Từ tệp địa phương",
|
||||
"TOC": "Mục lục",
|
||||
"Table of Contents": "Mục lục",
|
||||
"Book uploaded: {{title}}": "Sách đã tải lên: {{title}}",
|
||||
"Failed to upload book: {{title}}": "Không thể tải lên sách: {{title}}",
|
||||
"Book downloaded: {{title}}": "Sách đã tải về: {{title}}",
|
||||
@@ -538,5 +539,7 @@
|
||||
"Close": "Đóng",
|
||||
"Delete Book Options": "Tùy chọn xóa sách",
|
||||
"ON": "BẬT",
|
||||
"OFF": "TẮT"
|
||||
"OFF": "TẮT",
|
||||
"Reading Progress": "Tiến độ đọc",
|
||||
"Page Margin": "Lề trang"
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"Book Details": "书籍详情",
|
||||
"From Local File": "从本地文件导入",
|
||||
"TOC": "目录",
|
||||
"Table of Contents": "目录",
|
||||
"Book uploaded: {{title}}": "书籍已上传:{{title}}",
|
||||
"Failed to upload book: {{title}}": "上传书籍失败:{{title}}",
|
||||
"Book downloaded: {{title}}": "书籍已下载:{{title}}",
|
||||
@@ -538,5 +539,7 @@
|
||||
"Close": "关闭",
|
||||
"Delete Book Options": "删除书籍选项",
|
||||
"ON": "开启",
|
||||
"OFF": "关闭"
|
||||
"OFF": "关闭",
|
||||
"Reading Progress": "阅读进度",
|
||||
"Page Margin": "页面边距"
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"Book Details": "書籍詳情",
|
||||
"From Local File": "從本地文件導入",
|
||||
"TOC": "目錄",
|
||||
"Table of Contents": "目錄",
|
||||
"Book uploaded: {{title}}": "書籍已上傳:{{title}}",
|
||||
"Failed to upload book: {{title}}": "書籍上傳失敗:{{title}}",
|
||||
"Book downloaded: {{title}}": "書籍已下載:{{title}}",
|
||||
@@ -538,5 +539,7 @@
|
||||
"Close": "關閉",
|
||||
"Delete Book Options": "刪除書籍選項",
|
||||
"ON": "開啟",
|
||||
"OFF": "關閉"
|
||||
"OFF": "關閉",
|
||||
"Reading Progress": "閱讀進度",
|
||||
"Page Margin": "頁邊距"
|
||||
}
|
||||
|
||||
@@ -327,7 +327,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
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'
|
||||
role='main'
|
||||
aria-label={_('Bookshelf')}
|
||||
>
|
||||
{filteredBookshelfItems.map((item) => (
|
||||
|
||||
@@ -312,6 +312,17 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
|
||||
[isSelectMode, handleSelectItem, handleOpenItem, handleContextMenu],
|
||||
);
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
handleOpenItem();
|
||||
}
|
||||
if (e.key === 'ContextMenu' || (e.shiftKey && e.key === 'F10')) {
|
||||
e.preventDefault();
|
||||
handleContextMenu();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={clsx(mode === 'list' && 'sm:hover:bg-base-300/50 px-4 sm:px-6')}>
|
||||
<div
|
||||
@@ -319,14 +330,17 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
|
||||
'group',
|
||||
mode === 'grid' && 'sm:hover:bg-base-300/50 flex h-full flex-col px-0 py-4 sm:px-4',
|
||||
mode === 'list' && 'border-base-300 flex flex-col border-b py-2',
|
||||
'focus-inset-2',
|
||||
appService?.isMobileApp && 'no-context-menu',
|
||||
pressing && mode === 'grid' ? 'scale-95' : 'scale-100',
|
||||
)}
|
||||
role='group'
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
aria-label={'format' in item ? item.title : item.name}
|
||||
style={{
|
||||
transition: 'transform 0.2s',
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
{...handlers}
|
||||
>
|
||||
<div className='flex-grow'>
|
||||
|
||||
@@ -627,7 +627,6 @@ 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',
|
||||
|
||||
@@ -222,6 +222,7 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
>
|
||||
<div className='flex w-full items-center justify-between gap-x-6'>
|
||||
<Slider
|
||||
label={_('Reading Progress')}
|
||||
heightPx={sliderHeight}
|
||||
bubbleLabel={`${Math.round(progressFraction * 100)}%`}
|
||||
initialValue={progressValid ? progressFraction * 100 : 0}
|
||||
@@ -275,6 +276,7 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
}}
|
||||
>
|
||||
<Slider
|
||||
label={_('Font Size')}
|
||||
initialValue={viewSettings?.defaultFontSize ?? 16}
|
||||
bubbleLabel={`${viewSettings?.defaultFontSize ?? 16}`}
|
||||
minLabel='A'
|
||||
@@ -287,6 +289,7 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
/>
|
||||
<div className='flex w-full items-center justify-between gap-x-6'>
|
||||
<Slider
|
||||
label={_('Page Margin')}
|
||||
initialValue={getMarginProgressValue(
|
||||
viewSettings?.marginTopPx ?? 44,
|
||||
viewSettings?.gapPercent ?? 5,
|
||||
@@ -298,6 +301,7 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
onChange={handleMarginChange}
|
||||
/>
|
||||
<Slider
|
||||
label={_('Line Spacing')}
|
||||
initialValue={(viewSettings?.lineHeight ?? 1.6) * 10}
|
||||
bubbleElement={<RxLineHeight size={marginIconSize} />}
|
||||
minLabel={_('Small')}
|
||||
@@ -317,15 +321,22 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
tooltip={_('Table of Contents')}
|
||||
icon={<TOCIcon size={tocIconSize} className='' />}
|
||||
onClick={() => handleSetActionTab('toc')}
|
||||
/>
|
||||
<Button icon={<NoteIcon className='' />} onClick={() => handleSetActionTab('note')} />
|
||||
<Button
|
||||
tooltip={_('Notes')}
|
||||
icon={<NoteIcon className='' />}
|
||||
onClick={() => handleSetActionTab('note')}
|
||||
/>
|
||||
<Button
|
||||
tooltip={_('Reading Progress')}
|
||||
icon={<SliderIcon className={clsx(actionTab === 'progress' && 'text-blue-500')} />}
|
||||
onClick={() => handleSetActionTab('progress')}
|
||||
/>
|
||||
<Button
|
||||
tooltip={_('Font & Layout')}
|
||||
icon={
|
||||
<FontIcon
|
||||
size={fontIconSize}
|
||||
@@ -335,6 +346,7 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
onClick={() => handleSetActionTab('font')}
|
||||
/>
|
||||
<Button
|
||||
tooltip={_('Speak')}
|
||||
icon={<TTSIcon className={ttsEnabled ? 'text-blue-500' : ''} />}
|
||||
onClick={() => handleSetActionTab('tts')}
|
||||
/>
|
||||
|
||||
@@ -184,7 +184,8 @@ const CustomFonts: React.FC<CustomFontsProps> = ({ bookKey, onBack }) => {
|
||||
</div>
|
||||
|
||||
{availableFamilies.map((family) => (
|
||||
<button
|
||||
<div
|
||||
role='none'
|
||||
key={family.name}
|
||||
className={clsx(
|
||||
'card h-12 border shadow-sm',
|
||||
@@ -215,7 +216,7 @@ const CustomFonts: React.FC<CustomFontsProps> = ({ bookKey, onBack }) => {
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ const PlanCard: React.FC<PlanCardProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button className='mb-6 space-y-3' onClick={() => onSelectPlan(index)}>
|
||||
<div role='none' className='mb-6 space-y-3' onClick={() => onSelectPlan(index)}>
|
||||
{plan.features.map((feature, featureIndex) => (
|
||||
<div key={featureIndex} className='flex flex-col'>
|
||||
<div className='flex items-center gap-2'>
|
||||
@@ -62,9 +62,13 @@ const PlanCard: React.FC<PlanCardProps> = ({
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button className='mb-6 rounded-lg bg-white/50 p-4' onClick={() => onSelectPlan(index)}>
|
||||
<div
|
||||
role='none'
|
||||
className='mb-6 rounded-lg bg-white/50 p-4'
|
||||
onClick={() => onSelectPlan(index)}
|
||||
>
|
||||
<h5 className='mb-3 font-semibold'>{_('Plan Limits')}</h5>
|
||||
<div className='space-y-2'>
|
||||
{Object.entries(plan.limits).map(([key, value]) => (
|
||||
@@ -74,7 +78,7 @@ const PlanCard: React.FC<PlanCardProps> = ({
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<PlanActionButton
|
||||
plan={plan}
|
||||
|
||||
@@ -62,48 +62,50 @@ const Dropdown: React.FC<DropdownProps> = ({
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const [isPointerDown, setIsPointerDown] = useState(false);
|
||||
const lastInteractionWasTapOrClick = useRef(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const setIsDropdownOpen = (open: boolean) => {
|
||||
setIsOpen(open);
|
||||
onToggle?.(open);
|
||||
};
|
||||
|
||||
const handleTouchOrClick = () => {
|
||||
lastInteractionWasTapOrClick.current = true;
|
||||
setTimeout(() => (lastInteractionWasTapOrClick.current = false), 100);
|
||||
};
|
||||
|
||||
const handleFocus = () => {
|
||||
setIsFocused(true);
|
||||
// skip touch and pointer triggered focus, this is only for keyboard and aria navigation
|
||||
if (!lastInteractionWasTapOrClick.current) {
|
||||
setIsDropdownOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleDropdown = () => {
|
||||
console.error('Toggling dropdown, current state:', isOpen);
|
||||
const newIsOpen = !isOpen;
|
||||
setIsDropdownOpen(newIsOpen);
|
||||
setIsFocused(!isOpen);
|
||||
setIsDropdownOpen(!isOpen);
|
||||
};
|
||||
|
||||
const handleBlur = (e: React.FocusEvent) => {
|
||||
if (!containerRef.current) return;
|
||||
if (!containerRef.current.contains(e.relatedTarget as Node)) {
|
||||
setIsFocused(false);
|
||||
setIsDropdownOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
if (!isOpen) setIsDropdownOpen(true);
|
||||
e.stopPropagation();
|
||||
} else if (e.key === 'Escape' && isOpen) {
|
||||
} else if (e.key === 'Escape') {
|
||||
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);
|
||||
};
|
||||
|
||||
const childrenWithToggle = isValidElement(children)
|
||||
? React.cloneElement(children, {
|
||||
...(typeof children.type !== 'string' && {
|
||||
@@ -120,24 +122,29 @@ const Dropdown: React.FC<DropdownProps> = ({
|
||||
<div
|
||||
ref={containerRef}
|
||||
role='menu'
|
||||
tabIndex={0}
|
||||
aria-label={label}
|
||||
tabIndex={-1}
|
||||
title={label}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
onPointerDown={() => setIsPointerDown(true)}
|
||||
onPointerUp={() => setIsPointerDown(false)}
|
||||
className={clsx('dropdown flex flex-col', className)}
|
||||
>
|
||||
<div
|
||||
role='none'
|
||||
className={clsx('dropdown-toggle', buttonClassName, isFocused && 'bg-base-300/50')}
|
||||
<button
|
||||
aria-haspopup='menu'
|
||||
aria-expanded={isOpen}
|
||||
aria-label={label}
|
||||
className={clsx(
|
||||
'dropdown-toggle',
|
||||
isFocused && isOpen && 'bg-base-300/50',
|
||||
buttonClassName,
|
||||
)}
|
||||
onTouchStart={handleTouchOrClick}
|
||||
onPointerDown={handleTouchOrClick}
|
||||
onFocus={handleFocus}
|
||||
onClick={toggleDropdown}
|
||||
>
|
||||
{toggleButton}
|
||||
</div>
|
||||
<div role='none' className={clsx('flex items-center justify-center', !isOpen && 'hidden')}>
|
||||
</button>
|
||||
<div role='none' className={clsx('flex items-center justify-center')}>
|
||||
{isOpen && childrenWithToggle}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@ const LegalLinks = () => {
|
||||
: 'https://readest.com/terms-of-service';
|
||||
|
||||
return (
|
||||
<div className='my-2 flex flex-wrap justify-center gap-4 text-xs'>
|
||||
<div className='my-2 flex flex-wrap justify-center gap-4 text-sm sm:text-xs'>
|
||||
<Link href={termsUrl} className='text-blue-500 underline hover:text-blue-600'>
|
||||
{_('Terms of Service')}
|
||||
</Link>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
interface SliderProps {
|
||||
label: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
@@ -18,6 +19,7 @@ interface SliderProps {
|
||||
}
|
||||
|
||||
const Slider: React.FC<SliderProps> = ({
|
||||
label,
|
||||
min = 0,
|
||||
max = 100,
|
||||
step = 1,
|
||||
@@ -65,6 +67,7 @@ const Slider: React.FC<SliderProps> = ({
|
||||
return (
|
||||
<div
|
||||
ref={sliderRef}
|
||||
aria-label={label}
|
||||
className={`slider bg-base-200 mx-auto w-full rounded-xl ${className}`}
|
||||
dir={isRtl ? 'rtl' : undefined}
|
||||
>
|
||||
@@ -111,6 +114,12 @@ const Slider: React.FC<SliderProps> = ({
|
||||
value={value}
|
||||
className='absolute inset-0 h-full w-full cursor-pointer opacity-0'
|
||||
onChange={handleChange}
|
||||
aria-label={label}
|
||||
aria-valuemin={min}
|
||||
aria-valuemax={max}
|
||||
aria-valuenow={value}
|
||||
aria-valuetext={`${value}`}
|
||||
aria-orientation='horizontal'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import Link from './Link';
|
||||
|
||||
const SupportLinks = () => {
|
||||
const _ = useTranslation();
|
||||
const iconSize = useResponsiveSize(22);
|
||||
const iconSize = useResponsiveSize(24);
|
||||
|
||||
return (
|
||||
<div className='my-2 flex flex-col items-center gap-2'>
|
||||
|
||||
@@ -365,3 +365,11 @@ foliate-view {
|
||||
rgba(0,0,0,0.5) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.focus-inset-2 {
|
||||
@apply focus:outline-none;
|
||||
}
|
||||
|
||||
.focus-inset-2:focus {
|
||||
box-shadow: inset 0 0 0 2px transparent, inset 0 0 0 3px #6396e7;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user