diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index 1a857770..e8b39f79 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -459,7 +459,6 @@ "Google Books": "كتب جوجل", "Open Library": "المكتبة المفتوحة", "Fiction, Science, History": "خيال، علوم، تاريخ", - "Select Cover Image": "اختر صورة الغلاف", "Open Book in New Window": "فتح الكتاب في نافذة جديدة", "Voices for {{lang}}": "الأصوات لـ {{lang}}", "Yandex Translate": "ترجمة Yandex", @@ -500,7 +499,6 @@ "File Content (recommended)": "محتوى الملف (موصى به)", "File Name": "اسم الملف", "Device Name": "اسم الجهاز", - "Sync Tolerance": "تحمل المزامنة", "Connect to your KOReader Sync server.": "الاتصال بخادم مزامنة KOReader الخاص بك.", "Server URL": "عنوان URL للخادم", "Username": "اسم المستخدم", @@ -519,7 +517,6 @@ "Approximately {{percentage}}%": "تقريبًا {{percentage}}%", "Failed to connect": "فشل في الاتصال", "Sync Server Connected": "تم الاتصال بخادم المزامنة", - "Precision: {{precision}} digits after the decimal": "الدقة: {{precision}} أرقام بعد الفاصلة", "Sync as {{userDisplayName}}": "المزامنة كـ {{userDisplayName}}", "Custom Fonts": "الخطوط المخصصة", "Cancel Delete": "إلغاء الحذف", @@ -533,5 +530,7 @@ "Select Video": "حدد فيديو", "Select Audio": "حدد صوت", "Select Fonts": "حدد الخطوط", - "Supported font formats: .ttf, .otf, .woff, .woff2": "تنسيقات الخط المدعومة: .ttf، .otf، .woff، .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "تنسيقات الخط المدعومة: .ttf، .otf، .woff، .woff2", + "Push Progress": "تقدم الدفع", + "Pull Progress": "تقدم السحب" } diff --git a/apps/readest-app/public/locales/bo/translation.json b/apps/readest-app/public/locales/bo/translation.json index 843dfe00..3cf63050 100644 --- a/apps/readest-app/public/locales/bo/translation.json +++ b/apps/readest-app/public/locales/bo/translation.json @@ -438,7 +438,6 @@ "Google Books": "Google དཔེ་མཛོད།", "Open Library": "སྒོ་འབྱེད་དཔེ་ཁང་།", "Fiction, Science, History": "སྒྲུང་གཏམ། ཚན་རིག ལོ་རྒྱུས།", - "Select Cover Image": "སྟོང་ངོས་ཀྱི་པར་རིས་འདེམས་པ།", "Open Book in New Window": "སྒེའུ་ཁུང་གསར་པའི་ནང་དཔེ་དེབ་ཁ་ཕྱེ་བ།", "Voices for {{lang}}": "{{lang}} གྱི་སྒྲ་སྐད།", "Yandex Translate": "Yandex སྒྱུར་བཅོས།", @@ -482,8 +481,6 @@ "File Content (recommended)": "ཡིག་སྣེ་འབྱུང་ཁུངས།", "File Name": "ཡིག་སྣེ་འབྱུང་ཁུངས།", "Device Name": "ཡིག་སྣེ་འབྱུང་ཁུངས།", - "Sync Tolerance": "ཡིག་སྣེ་འབྱུང་ཁུངས།", - "Precision: {{precision}} digits after the decimal": "ཡིག་སྣེ་འབྱུང་ཁུངས།", "Connect to your KOReader Sync server.": "ཁྱེད་ཀྱིས KOReader སྤྲི་དོན་སང་བཞིན་འདེམས་པ།", "Server URL": "སང་བཞིན་འདེམས་པའི URL", "Username": "ཁྱེད་ཀྱིས་འབྱུང་ཁུངས་འདེམས་པ།", @@ -513,5 +510,7 @@ "Select Video": "བརྙན་ཟློས་འདེམས་པ།", "Select Audio": "སྒྲ་ཟློས་འདེམས་པ།", "Select Fonts": "ཡིག་གཟུགས་འདེམས་པ།", - "Supported font formats: .ttf, .otf, .woff, .woff2": "ཡིག་གཟུགས་འདེམས་པ་བྱས་ནས་བསྐྱར་བརྗེ་བ། ཡིག་གཟུགས་ཀྱི་སྒྲ་སྐད། .ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "ཡིག་གཟུགས་འདེམས་པ་བྱས་ནས་བསྐྱར་བརྗེ་བ། ཡིག་གཟུགས་ཀྱི་སྒྲ་སྐད། .ttf, .otf, .woff, .woff2", + "Push Progress": "འདེགས་པའི་འཕེལ་རིམ།", + "Pull Progress": "འདྲུད་པའི་འཕེལ་རིམ།" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index 312346e0..5540b0d9 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -443,7 +443,6 @@ "Google Books": "Google Books", "Open Library": "Open Library", "Fiction, Science, History": "Belletristik, Wissenschaft, Geschichte", - "Select Cover Image": "Coverbild auswählen", "Open Book in New Window": "Buch in neuem Fenster öffnen", "Voices for {{lang}}": "Stimmen für {{lang}}", "Yandex Translate": "Yandex Übersetzer", @@ -484,7 +483,6 @@ "File Content (recommended)": "Dateiinhalte (empfohlen)", "File Name": "Dateiname", "Device Name": "Gerätename", - "Sync Tolerance": "Sync-Toleranz", "Connect to your KOReader Sync server.": "Mit Ihrem KOReader Sync-Server verbinden.", "Server URL": "Server-URL", "Username": "Benutzername", @@ -503,7 +501,6 @@ "Approximately {{percentage}}%": "Ungefähr {{percentage}}%", "Failed to connect": "Verbindung fehlgeschlagen", "Sync Server Connected": "Sync-Server verbunden", - "Precision: {{precision}} digits after the decimal": "Präzision: {{precision}} Nachkommastellen", "Sync as {{userDisplayName}}": "Sync als {{userDisplayName}}", "Custom Fonts": "Benutzerdefinierte Schriftarten", "Cancel Delete": "Löschen abbrechen", @@ -517,5 +514,7 @@ "Select Video": "Video auswählen", "Select Audio": "Audio auswählen", "Select Fonts": "Schriftarten auswählen", - "Supported font formats: .ttf, .otf, .woff, .woff2": "Unterstützte Schriftartenformate: .ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "Unterstützte Schriftartenformate: .ttf, .otf, .woff, .woff2", + "Push Progress": "Fortschritt senden", + "Pull Progress": "Fortschritt empfangen" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index 745f9bf3..6e8e85cd 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -443,7 +443,6 @@ "Google Books": "Google Books", "Open Library": "Open Library", "Fiction, Science, History": "Μυθοπλασία, Επιστήμη, Ιστορία", - "Select Cover Image": "Επιλέξτε εικόνα εξωφύλλου", "Open Book in New Window": "Άνοιγμα βιβλίου σε νέο παράθυρο", "Voices for {{lang}}": "Φωνές για {{lang}}", "Yandex Translate": "Μετάφραση Yandex", @@ -484,7 +483,6 @@ "File Content (recommended)": "Περιεχόμενο αρχείου (συνιστάται)", "File Name": "Όνομα αρχείου", "Device Name": "Όνομα συσκευής", - "Sync Tolerance": "Ανοχή συγχρονισμού", "Connect to your KOReader Sync server.": "Συνδεθείτε στον διακομιστή συγχρονισμού KOReader.", "Server URL": "Διεύθυνση URL διακομιστή", "Username": "Όνομα χρήστη", @@ -503,7 +501,6 @@ "Approximately {{percentage}}%": "Περίπου {{percentage}}%", "Failed to connect": "Αποτυχία σύνδεσης", "Sync Server Connected": "Ο διακομιστής συγχρονισμού είναι συνδεδεμένος", - "Precision: {{precision}} digits after the decimal": "Ακρίβεια: {{precision}} ψηφία μετά την υποδιαστολή", "Sync as {{userDisplayName}}": "Συγχρονισμός ως {{userDisplayName}}", "Custom Fonts": "Προσαρμοσμένες Γραμματοσειρές", "Cancel Delete": "Ακύρωση Διαγραφής", @@ -517,5 +514,7 @@ "Select Video": "Επιλογή Βίντεο", "Select Audio": "Επιλογή Ήχου", "Select Fonts": "Επιλογή Γραμματοσειρών", - "Supported font formats: .ttf, .otf, .woff, .woff2": "Υποστηριζόμενες μορφές γραμματοσειρών: .ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "Υποστηριζόμενες μορφές γραμματοσειρών: .ttf, .otf, .woff, .woff2", + "Push Progress": "Αποστολή προόδου", + "Pull Progress": "Λήψη προόδου" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index ed3ead48..111df171 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -88,7 +88,6 @@ "File Content (recommended)": "Contenido del archivo (recomendado)", "File Name": "Nombre del archivo", "Device Name": "Nombre del dispositivo", - "Sync Tolerance": "Tolerancia de sincronización", "Reading Progress Synced": "Progreso de lectura sincronizado", "Sync Conflict": "Conflicto de sincronización", "Sync reading progress from \"{{deviceName}}\"?": "¿Quieres sincronizar el progreso de lectura desde el dispositivo \"{{deviceName}}\"?", @@ -476,7 +475,6 @@ "Google Books": "Google Books", "Open Library": "Open Library", "Fiction, Science, History": "Ficción, Ciencia, Historia", - "Select Cover Image": "Seleccionar imagen de portada", "Open Book in New Window": "Abre el libro en una nueva ventana", "Voices for {{lang}}": "Voces para {{lang}}", "Yandex Translate": "Yandex Translate", @@ -507,7 +505,6 @@ "Remove from Device Only": "Eliminar solo del dispositivo", "Failed to connect": "Error al conectar", "Sync Server Connected": "Servidor de sincronización conectado", - "Precision: {{precision}} digits after the decimal": "Precisión: {{precision}} dígitos después del decimal", "Sync as {{userDisplayName}}": "Sincronizar como {{userDisplayName}}", "Custom Fonts": "Fuentes Personalizadas", "Cancel Delete": "Cancelar Eliminación", @@ -521,5 +518,7 @@ "Select Video": "Seleccionar Video", "Select Audio": "Seleccionar Audio", "Select Fonts": "Seleccionar Fuentes", - "Supported font formats: .ttf, .otf, .woff, .woff2": "Formatos de fuente compatibles: .ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "Formatos de fuente compatibles: .ttf, .otf, .woff, .woff2", + "Push Progress": "Empujar progreso", + "Pull Progress": "Tirar progreso" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index 84814fe9..cf366dbe 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -447,7 +447,6 @@ "Google Books": "Google Books", "Open Library": "Open Library", "Fiction, Science, History": "Fiction, Science, Histoire", - "Select Cover Image": "Sélectionner l'image de couverture", "Open Book in New Window": "Ouvrir dans une nouvelle fenêtre", "Voices for {{lang}}": "Voix pour {{lang}}", "Yandex Translate": "Yandex Traduction", @@ -488,7 +487,6 @@ "File Content (recommended)": "Contenu du fichier (recommandé)", "File Name": "Nom du fichier", "Device Name": "Nom de l'appareil", - "Sync Tolerance": "Tolérance de synchronisation", "Connect to your KOReader Sync server.": "Connectez-vous à votre serveur de synchronisation KOReader.", "Server URL": "URL du serveur", "Username": "Nom d'utilisateur", @@ -507,7 +505,6 @@ "Approximately {{percentage}}%": "Environ {{percentage}}%", "Failed to connect": "Échec de la connexion", "Sync Server Connected": "Serveur de synchronisation connecté", - "Precision: {{precision}} digits after the decimal": "Précision : {{precision}} chiffres après la virgule", "Sync as {{userDisplayName}}": "Synchroniser en tant que {{userDisplayName}}", "Custom Fonts": "Polices Personnalisées", "Cancel Delete": "Annuler la Suppression", @@ -521,5 +518,7 @@ "Select Video": "Sélectionner une Vidéo", "Select Audio": "Sélectionner un Audio", "Select Fonts": "Sélectionner des Polices", - "Supported font formats: .ttf, .otf, .woff, .woff2": "Formats de police pris en charge : .ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "Formats de police pris en charge : .ttf, .otf, .woff, .woff2", + "Push Progress": "Pousser la Progression", + "Pull Progress": "Tirer la Progression" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index daaece4f..aa779e36 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -443,7 +443,6 @@ "Google Books": "गूगल बुक्स", "Open Library": "ओपन लाइब्रेरी", "Fiction, Science, History": "काल्पनिक, विज्ञान, इतिहास", - "Select Cover Image": "कवर इमेज चुनें", "Open Book in New Window": "नई विंडो में पुस्तक खोलें", "Voices for {{lang}}": "{{lang}} के लिए आवाज़ें", "Yandex Translate": "यांडेक्स अनुवाद", @@ -484,7 +483,6 @@ "File Content (recommended)": "फाइल सामग्री (अनुशंसित)", "File Name": "फाइल नाम", "Device Name": "डिवाइस नाम", - "Sync Tolerance": "सिंक सहिष्णुता", "Connect to your KOReader Sync server.": "अपने KOReader सिंक सर्वर से कनेक्ट करें।", "Server URL": "सर्वर यूआरएल", "Username": "उपयोगकर्ता नाम", @@ -503,7 +501,6 @@ "Approximately {{percentage}}%": "लगभग {{percentage}}%", "Failed to connect": "कनेक्ट करने में विफल", "Sync Server Connected": "सिंक सर्वर कनेक्टेड", - "Precision: {{precision}} digits after the decimal": "सटीकता: {{precision}} दशमलव के बाद अंक", "Sync as {{userDisplayName}}": "सिंक के रूप में {{userDisplayName}}", "Custom Fonts": "कस्टम फ़ॉन्ट", "Cancel Delete": "डिलीट रद्द करें", @@ -517,5 +514,7 @@ "Select Video": "वीडियो चुनें", "Select Audio": "ऑडियो चुनें", "Select Fonts": "फ़ॉन्ट चुनें", - "Supported font formats: .ttf, .otf, .woff, .woff2": "समर्थित फ़ॉन्ट प्रारूप: .ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "समर्थित फ़ॉन्ट प्रारूप: .ttf, .otf, .woff, .woff2", + "Push progress": "पुश प्रगति", + "Pull progress": "पुल प्रगति" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index 3e69ccca..00ac517d 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -439,7 +439,6 @@ "Google Books": "Google Books", "Open Library": "Open Library", "Fiction, Science, History": "Fiksi, Sains, Sejarah", - "Select Cover Image": "Pilihan Gambar Sampul", "Open Book in New Window": "Buka Buku di Jendela Baru", "Voices for {{lang}}": "Suara untuk {{lang}}", "Yandex Translate": "Yandex Translate", @@ -480,7 +479,6 @@ "File Content (recommended)": "Konten File (disarankan)", "File Name": "Nama File", "Device Name": "Nama Perangkat", - "Sync Tolerance": "Toleransi Sinkronisasi", "Connect to your KOReader Sync server.": "Hubungkan ke server Sinkronisasi KOReader Anda.", "Server URL": "URL Server", "Username": "Nama Pengguna", @@ -499,7 +497,6 @@ "Approximately {{percentage}}%": "Sekitar {{percentage}}%", "Failed to connect": "Gagal terhubung", "Sync Server Connected": "Server sinkronisasi terhubung", - "Precision: {{precision}} digits after the decimal": "Presisi: {{precision}} digit setelah desimal", "Sync as {{userDisplayName}}": "Sinkronkan sebagai {{userDisplayName}}", "Custom Fonts": "Font Kustom", "Cancel Delete": "Batal Hapus", @@ -513,5 +510,7 @@ "Select Video": "Pilih Video", "Select Audio": "Pilih Audio", "Select Fonts": "Pilih Font", - "Supported font formats: .ttf, .otf, .woff, .woff2": "Format font yang didukung: .ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "Format font yang didukung: .ttf, .otf, .woff, .woff2", + "Push Progress": "Dorong Progres", + "Pull Progress": "Tarik Progres" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index fd2ec7e3..a84f4717 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -447,7 +447,6 @@ "Google Books": "Google Books", "Open Library": "Open Library", "Fiction, Science, History": "Narrativa, Scienza, Storia", - "Select Cover Image": "Seleziona immagine di copertina", "Open Book in New Window": "Apri libro in una nuova finestra", "Voices for {{lang}}": "Voci per {{lang}}", "Yandex Translate": "Yandex Translate", @@ -488,7 +487,6 @@ "File Content (recommended)": "Contenuto del File (consigliato)", "File Name": "Nome del File", "Device Name": "Nome del Dispositivo", - "Sync Tolerance": "Tolleranza di Sincronizzazione", "Connect to your KOReader Sync server.": "Connettiti al tuo server KOReader Sync.", "Server URL": "URL del Server", "Username": "Nome Utente", @@ -507,7 +505,6 @@ "Approximately {{percentage}}%": "Circa {{percentage}}%", "Failed to connect": "Impossibile connettersi", "Sync Server Connected": "Server di sincronizzazione connesso", - "Precision: {{precision}} digits after the decimal": "Precisione: {{precision}} cifre dopo la virgola", "Sync as {{userDisplayName}}": "Sincronizza come {{userDisplayName}}", "Custom Fonts": "Font Personalizzati", "Cancel Delete": "Annulla Eliminazione", @@ -521,5 +518,7 @@ "Select Video": "Seleziona Video", "Select Audio": "Seleziona Audio", "Select Fonts": "Seleziona Font", - "Supported font formats: .ttf, .otf, .woff, .woff2": "Formati di font supportati: .ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "Formati di font supportati: .ttf, .otf, .woff, .woff2", + "Push progress": "Progresso push", + "Pull progress": "Progresso pull" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index 1f659d81..d00a125b 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -439,7 +439,6 @@ "Google Books": "Google Books", "Open Library": "Open Library", "Fiction, Science, History": "小説、科学、歴史", - "Select Cover Image": "表紙画像を選択", "Open Book in New Window": "新しいウィンドウで本を開く", "Voices for {{lang}}": "{{lang}}の音声", "Yandex Translate": "Yandex翻訳", @@ -480,7 +479,6 @@ "File Content (recommended)": "ファイル内容(推奨)", "File Name": "ファイル名", "Device Name": "デバイス名", - "Sync Tolerance": "同期許容範囲", "Connect to your KOReader Sync server.": "KOReader Syncサーバーに接続します。", "Server URL": "サーバーURL", "Username": "ユーザー名", @@ -499,7 +497,6 @@ "Approximately {{percentage}}%": "おおよそ {{percentage}}%", "Failed to connect": "接続に失敗しました", "Sync Server Connected": "同期サーバーに接続されました", - "Precision: {{precision}} digits after the decimal": "精度: {{precision}} 桁の小数点以下", "Sync as {{userDisplayName}}": "{{userDisplayName}} として同期", "Custom Fonts": "カスタムフォント", "Cancel Delete": "削除をキャンセル", @@ -513,5 +510,7 @@ "Select Video": "動画を選択", "Select Audio": "音声を選択", "Select Fonts": "フォントを選択", - "Supported font formats: .ttf, .otf, .woff, .woff2": "サポートされているフォント形式:.ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "サポートされているフォント形式:.ttf, .otf, .woff, .woff2", + "Push Progress": "進捗をプッシュ", + "Pull Progress": "進捗をプル" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index a85db7fe..6d58c29d 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -439,7 +439,6 @@ "Google Books": "구글 북스", "Open Library": "오픈 라이브러리", "Fiction, Science, History": "소설, 과학, 역사", - "Select Cover Image": "표지 이미지 선택", "Open Book in New Window": "새 창에서 책 열기", "Voices for {{lang}}": "{{lang}}의 음성", "Yandex Translate": "Yandex 번역", @@ -480,7 +479,6 @@ "File Content (recommended)": "파일 내용 (권장)", "File Name": "파일 이름", "Device Name": "장치 이름", - "Sync Tolerance": "동기화 허용 오차", "Connect to your KOReader Sync server.": "KOReader Sync 서버에 연결합니다.", "Server URL": "서버 URL", "Username": "사용자 이름", @@ -499,7 +497,6 @@ "Approximately {{percentage}}%": "대략 {{percentage}}%", "Failed to connect": "연결 실패", "Sync Server Connected": "동기화 서버에 연결됨", - "Precision: {{precision}} digits after the decimal": "정밀도: {{precision}} 자리의 소수점 이하", "Sync as {{userDisplayName}}": "{{userDisplayName}}로 동기화", "Custom Fonts": "사용자 정의 글꼴", "Cancel Delete": "삭제 취소", @@ -513,5 +510,7 @@ "Select Video": "동영상 선택", "Select Audio": "오디오 선택", "Select Fonts": "글꼴 선택", - "Supported font formats: .ttf, .otf, .woff, .woff2": "지원되는 글꼴 형식: .ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "지원되는 글꼴 형식: .ttf, .otf, .woff, .woff2", + "Push Progress": "진행 상황 푸시", + "Pull Progress": "진행 상황 끌어오기" } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index 72eaf3b1..cc1f298a 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -443,7 +443,6 @@ "Google Books": "Google Books", "Open Library": "Open Library", "Fiction, Science, History": "Fictie, Wetenschap, Geschiedenis", - "Select Cover Image": "Selecteer omslagafbeelding", "Open Book in New Window": "Open boek in nieuw venster", "Voices for {{lang}}": "Stemmen voor {{lang}}", "Yandex Translate": "Yandex Vertalen", @@ -484,7 +483,6 @@ "File Content (recommended)": "Bestandsinhoud (aanbevolen)", "File Name": "Bestandsnaam", "Device Name": "Apparaatnaam", - "Sync Tolerance": "Synchronisatietolerantie", "Connect to your KOReader Sync server.": "Verbind met je KOReader Sync-server.", "Server URL": "Server-URL", "Username": "Gebruikersnaam", @@ -503,7 +501,6 @@ "Approximately {{percentage}}%": "Ongeveer {{percentage}}%", "Failed to connect": "Verbinding mislukt", "Sync Server Connected": "Synchronisatie server verbonden", - "Precision: {{precision}} digits after the decimal": "Precisie: {{precision}} cijfers na de komma", "Sync as {{userDisplayName}}": "Synchroniseren als {{userDisplayName}}", "Custom Fonts": "Aangepaste Lettertypen", "Cancel Delete": "Verwijderen Annuleren", @@ -517,5 +514,7 @@ "Select Video": "Video Selecteren", "Select Audio": "Audio Selecteren", "Select Fonts": "Lettertypen Selecteren", - "Supported font formats: .ttf, .otf, .woff, .woff2": "Ondersteunde lettertypeformaten: .ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "Ondersteunde lettertypeformaten: .ttf, .otf, .woff, .woff2", + "Push Progress": "Voortgang pushen", + "Pull Progress": "Voortgang pullen" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index 9ca541e3..25292d5b 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -451,7 +451,6 @@ "Google Books": "Google Books", "Open Library": "Open Library", "Fiction, Science, History": "Beletrystyka, Nauka, Historia", - "Select Cover Image": "Zaznacz obraz okładki", "Open Book in New Window": "Otwórz książkę w nowym oknie", "Voices for {{lang}}": "{{lang}} Głosy", "Yandex Translate": "Yandex Tłumacz", @@ -492,7 +491,6 @@ "File Content (recommended)": "Zawartość pliku (zalecane)", "File Name": "Nazwa pliku", "Device Name": "Nazwa urządzenia", - "Sync Tolerance": "Tolerancja synchronizacji", "Connect to your KOReader Sync server.": "Połącz z serwerem synchronizacji KOReader.", "Server URL": "Adres URL serwera", "Username": "Nazwa użytkownika", @@ -511,7 +509,6 @@ "Approximately {{percentage}}%": "Około {{percentage}}%", "Failed to connect": "Nie udało się połączyć", "Sync Server Connected": "Serwer synchronizacji połączony", - "Precision: {{precision}} digits after the decimal": "Precyzja: {{precision}} cyfr po przecinku", "Sync as {{userDisplayName}}": "Synchronizuj jako {{userDisplayName}}", "Custom Fonts": "Niestandardowe Czcionki", "Cancel Delete": "Anuluj Usuwanie", @@ -525,5 +522,7 @@ "Select Video": "Wybierz Wideo", "Select Audio": "Wybierz Audio", "Select Fonts": "Wybierz Czcionki", - "Supported font formats: .ttf, .otf, .woff, .woff2": "Obsługiwane formaty czcionek: .ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "Obsługiwane formaty czcionek: .ttf, .otf, .woff, .woff2", + "Push Progress": "Wyślij postęp", + "Pull Progress": "Pobierz postęp" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index 63501973..3757b122 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -447,7 +447,6 @@ "Google Books": "Google Books", "Open Library": "Open Library", "Fiction, Science, History": "Ficção, Ciência, História", - "Select Cover Image": "Selecionar imagem da capa", "Open Book in New Window": "Abrir livro em nova janela", "Voices for {{lang}}": "Vozes para {{lang}}", "Yandex Translate": "Yandex Translate", @@ -488,7 +487,6 @@ "File Content (recommended)": "Conteúdo do Arquivo (recomendado)", "File Name": "Nome do Arquivo", "Device Name": "Nome do Dispositivo", - "Sync Tolerance": "Tolerância de Sincronização", "Connect to your KOReader Sync server.": "Conectar ao seu servidor KOReader Sync.", "Server URL": "URL do Servidor", "Username": "Nome de Usuário", @@ -507,7 +505,6 @@ "Approximately {{percentage}}%": "Aproximadamente {{percentage}}%", "Failed to connect": "Falha ao conectar", "Sync Server Connected": "Servidor de Sincronização Conectado", - "Precision: {{precision}} digits after the decimal": "Precisão: {{precision}} dígitos após a vírgula", "Sync as {{userDisplayName}}": "Sincronizar como {{userDisplayName}}", "Custom Fonts": "Fontes Personalizadas", "Cancel Delete": "Cancelar Exclusão", @@ -521,5 +518,7 @@ "Select Video": "Selecionar Vídeo", "Select Audio": "Selecionar Áudio", "Select Fonts": "Selecionar Fontes", - "Supported font formats: .ttf, .otf, .woff, .woff2": "Formatos de fonte suportados: .ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "Formatos de fonte suportados: .ttf, .otf, .woff, .woff2", + "Push Progress": "Enviar Progresso", + "Pull Progress": "Obter Progresso" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index 6180b621..70dde58b 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -451,7 +451,6 @@ "Google Books": "Google Книги", "Open Library": "Open Library", "Fiction, Science, History": "Художественная литература, Наука, История", - "Select Cover Image": "Выбрать изображение обложки", "Open Book in New Window": "Открыть книгу в новом окне", "Voices for {{lang}}": "Голоса для {{lang}}", "Yandex Translate": "Yandex Translate", @@ -492,7 +491,6 @@ "File Content (recommended)": "Содержимое файла (рекомендуется)", "File Name": "Имя файла", "Device Name": "Имя устройства", - "Sync Tolerance": "Допуск синхронизации", "Connect to your KOReader Sync server.": "Подключитесь к вашему серверу синхронизации KOReader.", "Server URL": "URL сервера", "Username": "Имя пользователя", @@ -511,7 +509,6 @@ "Approximately {{percentage}}%": "Приблизительно {{percentage}}%", "Failed to connect": "Не удалось подключиться", "Sync Server Connected": "Сервер синхронизации подключен", - "Precision: {{precision}} digits after the decimal": "Точность: {{precision}} знаков после запятой", "Sync as {{userDisplayName}}": "Синхронизировать как {{userDisplayName}}", "Custom Fonts": "Пользовательские Шрифты", "Cancel Delete": "Отменить Удаление", @@ -525,5 +522,7 @@ "Select Video": "Выбрать Видео", "Select Audio": "Выбрать Аудио", "Select Fonts": "Выбрать Шрифты", - "Supported font formats: .ttf, .otf, .woff, .woff2": "Поддерживаемые форматы шрифтов: .ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "Поддерживаемые форматы шрифтов: .ttf, .otf, .woff, .woff2", + "Push Progress": "Отправить прогресс", + "Pull Progress": "Получить прогресс" } diff --git a/apps/readest-app/public/locales/th/translation.json b/apps/readest-app/public/locales/th/translation.json index a5dfc50b..5b3be7c8 100644 --- a/apps/readest-app/public/locales/th/translation.json +++ b/apps/readest-app/public/locales/th/translation.json @@ -439,7 +439,6 @@ "Google Books": "Google Books", "Open Library": "Open Library", "Fiction, Science, History": "นิยาย, วิทยาศาสตร์, ประวัติศาสตร์", - "Select Cover Image": "เลือกภาพปก", "Open Book in New Window": "เปิดหนังสือในหน้าต่างใหม่", "Voices for {{lang}}": "เสียงสำหรับ {{lang}}", "Yandex Translate": "Yandex Translate", @@ -480,7 +479,6 @@ "File Content (recommended)": "เนื้อหาไฟล์ (แนะนำ)", "File Name": "ชื่อไฟล์", "Device Name": "ชื่ออุปกรณ์", - "Sync Tolerance": "ความทนทานต่อการซิงค์", "Connect to your KOReader Sync server.": "เชื่อมต่อกับเซิร์ฟเวอร์ KOReader Sync ของคุณ", "Server URL": "URL ของเซิร์ฟเวอร์", "Username": "ชื่อผู้ใช้", @@ -499,7 +497,6 @@ "Approximately {{percentage}}%": "ประมาณ {{percentage}}%", "Failed to connect": "ไม่สามารถเชื่อมต่อได้", "Sync Server Connected": "เซิร์ฟเวอร์ซิงค์เชื่อมต่อแล้ว", - "Precision: {{precision}} digits after the decimal": "ความแม่นยำ: {{precision}} หลักหลังจุดทศนิยม", "Sync as {{userDisplayName}}": "ซิงค์เป็น {{userDisplayName}}", "Custom Fonts": "ฟอนต์กำหนดเอง", "Cancel Delete": "ยกเลิกการลบ", @@ -513,5 +510,7 @@ "Select Video": "เลือกวิดีโอ", "Select Audio": "เลือกเสียง", "Select Fonts": "เลือกฟอนต์", - "Supported font formats: .ttf, .otf, .woff, .woff2": "รูปแบบฟอนต์ที่รองรับ: .ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "รูปแบบฟอนต์ที่รองรับ: .ttf, .otf, .woff, .woff2", + "Push Progress": "ส่งความคืบหน้า", + "Pull Progress": "ดึงความคืบหน้า" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index 17bee544..c6385e7e 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -443,7 +443,6 @@ "Google Books": "Google Books", "Open Library": "Open Library", "Fiction, Science, History": "Kurgu, Bilim, Tarih", - "Select Cover Image": "Kapak Resmi Seç", "Open Book in New Window": "Kitabı Yeni Pencerede Aç", "Voices for {{lang}}": "{{lang}} için Sesler", "Yandex Translate": "Yandex Çeviri", @@ -484,7 +483,6 @@ "File Content (recommended)": "Dosya İçeriği (önerilen)", "File Name": "Dosya Adı", "Device Name": "Cihaz Adı", - "Sync Tolerance": "Senkronizasyon Toleransı", "Connect to your KOReader Sync server.": "KOReader Senkronizasyon sunucunuza bağlanın.", "Server URL": "Sunucu URL'si", "Username": "Kullanıcı Adı", @@ -503,7 +501,6 @@ "Approximately {{percentage}}%": "Yaklaşık {{percentage}}%", "Failed to connect": "Bağlanma başarısız oldu", "Sync Server Connected": "Senkronizasyon Sunucusu Bağlandı", - "Precision: {{precision}} digits after the decimal": "Hassasiyet: {{precision}} ondalık basamaktan sonra", "Sync as {{userDisplayName}}": "Senkronize et {{userDisplayName}} olarak", "Custom Fonts": "Özel Yazı Tipleri", "Cancel Delete": "Silmeyi İptal Et", @@ -517,5 +514,7 @@ "Select Video": "Video Seç", "Select Audio": "Ses Seç", "Select Fonts": "Yazı Tiplerini Seç", - "Supported font formats: .ttf, .otf, .woff, .woff2": "Desteklenen yazı tipi formatları: .ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "Desteklenen yazı tipi formatları: .ttf, .otf, .woff, .woff2", + "Push Progress": "İlerlemeyi Gönder", + "Pull Progress": "İlerlemeyi Al" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index 8b87717e..84a61d31 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -451,7 +451,6 @@ "Google Books": "Google Книги", "Open Library": "Open Library", "Fiction, Science, History": "Художня література, Наукова фантастика, Історія", - "Select Cover Image": "Оберіть зображення обкладинки", "Open Book in New Window": "Відкрити книгу в новому вікні", "Voices for {{lang}}": "Голоси для {{lang}}", "Yandex Translate": "Yandex Перекладач (росія)", @@ -492,7 +491,6 @@ "File Content (recommended)": "Вміст файлу (рекомендується)", "File Name": "Ім'я файлу", "Device Name": "Ім'я пристрою", - "Sync Tolerance": "Допустима похибка синхронізації", "Connect to your KOReader Sync server.": "Підключіться до свого сервера синхронізації KOReader.", "Server URL": "URL сервера", "Username": "Ім'я користувача", @@ -511,7 +509,6 @@ "Approximately {{percentage}}%": "Приблизно {{percentage}}%", "Failed to connect": "Не вдалося під'єднатися", "Sync Server Connected": "Сервер синхронізації під'єднано", - "Precision: {{precision}} digits after the decimal": "Точність: {{precision}} знаків після коми", "Sync as {{userDisplayName}}": "Синхронізувати як {{userDisplayName}}", "Custom Fonts": "Користувацькі Шрифти", "Cancel Delete": "Скасувати Видалення", @@ -525,5 +522,7 @@ "Select Video": "Вибрати Відео", "Select Audio": "Вибрати Аудіо", "Select Fonts": "Вибрати Шрифти", - "Supported font formats: .ttf, .otf, .woff, .woff2": "Підтримувані формати шрифтів: .ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "Підтримувані формати шрифтів: .ttf, .otf, .woff, .woff2", + "Push Progress": "Надіслати прогрес", + "Pull Progress": "Отримати прогрес" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index 0cb0c07b..cd3fd3f2 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -439,7 +439,6 @@ "Google Books": "Google Books", "Open Library": "Open Library", "Fiction, Science, History": "Tiểu thuyết, Khoa học, Lịch sử", - "Select Cover Image": "Chọn ảnh bìa", "Open Book in New Window": "Mở Sách trong Cửa sổ Mới", "Voices for {{lang}}": "Giọng nói cho {{lang}}", "Yandex Translate": "Yandex Dịch", @@ -480,7 +479,6 @@ "File Content (recommended)": "Nội dung tệp (được khuyến nghị)", "File Name": "Tên tệp", "Device Name": "Tên thiết bị", - "Sync Tolerance": "Độ dung sai đồng bộ", "Connect to your KOReader Sync server.": "Kết nối với máy chủ đồng bộ KOReader của bạn.", "Server URL": "URL máy chủ", "Username": "Tên người dùng", @@ -499,7 +497,6 @@ "Approximately {{percentage}}%": "Khoảng {{percentage}}%", "Failed to connect": "Không thể kết nối", "Sync Server Connected": "Máy chủ đồng bộ đã kết nối", - "Precision: {{precision}} digits after the decimal": "Độ chính xác: {{precision}} chữ số sau dấu thập phân", "Sync as {{userDisplayName}}": "Đồng bộ dưới tên {{userDisplayName}}", "Custom Fonts": "Phông Chữ Tùy Chỉnh", "Cancel Delete": "Hủy Xóa", @@ -513,5 +510,7 @@ "Select Video": "Chọn Video", "Select Audio": "Chọn Âm Thanh", "Select Fonts": "Chọn Phông Chữ", - "Supported font formats: .ttf, .otf, .woff, .woff2": "Định dạng phông chữ được hỗ trợ: .ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "Định dạng phông chữ được hỗ trợ: .ttf, .otf, .woff, .woff2", + "Push Progress": "Đẩy tiến độ", + "Pull Progress": "Kéo tiến độ" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index a3ba8531..cc6f991e 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -439,7 +439,6 @@ "Google Books": "谷歌图书", "Open Library": "开放图书馆", "Fiction, Science, History": "小说,科学,历史", - "Select Cover Image": "选择封面图片", "Open Book in New Window": "在新窗口中打开书籍", "Voices for {{lang}}": "{{lang}} 的语音", "Yandex Translate": "Yandex 翻译", @@ -480,7 +479,6 @@ "File Content (recommended)": "文件内容(推荐)", "File Name": "文件名", "Device Name": "设备名称", - "Sync Tolerance": "同步容忍度", "Connect to your KOReader Sync server.": "连接到您的 KOReader 同步服务器。", "Server URL": "服务器 URL", "Username": "用户名", @@ -499,7 +497,6 @@ "Approximately {{percentage}}%": "大约 {{percentage}}%", "Failed to connect": "连接失败", "Sync Server Connected": "同步服务器已连接", - "Precision: {{precision}} digits after the decimal": "精度:{{precision}} 位小数", "Sync as {{userDisplayName}}": "以 {{userDisplayName}} 身份同步", "Custom Fonts": "自定义字体", "Cancel Delete": "取消删除", @@ -513,5 +510,7 @@ "Select Video": "选择视频", "Select Audio": "选择音频", "Select Fonts": "选择字体", - "Supported font formats: .ttf, .otf, .woff, .woff2": "支持的字体格式:.ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "支持的字体格式:.ttf, .otf, .woff, .woff2", + "Push Progress": "推送阅读进度", + "Pull Progress": "拉取阅读进度" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index 88411043..fd50196a 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -439,7 +439,6 @@ "Google Books": "Google 圖書", "Open Library": "開放圖書館", "Fiction, Science, History": "小說,科學,歷史", - "Select Cover Image": "選擇封面圖片", "Open Book in New Window": "在新視窗中打開書籍", "Voices for {{lang}}": "{{lang}} 的語音", "Yandex Translate": "Yandex 翻譯", @@ -480,7 +479,6 @@ "File Content (recommended)": "文件內容(推薦)", "File Name": "文件名", "Device Name": "設備名稱", - "Sync Tolerance": "同步容忍度", "Connect to your KOReader Sync server.": "連接到您的 KOReader 同步伺服器。", "Server URL": "伺服器 URL", "Username": "用戶名", @@ -499,7 +497,6 @@ "Approximately {{percentage}}%": "大約 {{percentage}}%", "Failed to connect": "連接失敗", "Sync Server Connected": "同步伺服器已連接", - "Precision: {{precision}} digits after the decimal": "精度: {{precision}} 位小數", "Sync as {{userDisplayName}}": "以 {{userDisplayName}} 身份同步", "Custom Fonts": "自訂字型", "Cancel Delete": "取消刪除", @@ -513,5 +510,7 @@ "Select Video": "選擇影片", "Select Audio": "選擇音訊", "Select Fonts": "選擇字型", - "Supported font formats: .ttf, .otf, .woff, .woff2": "支援的字型格式:.ttf, .otf, .woff, .woff2" + "Supported font formats: .ttf, .otf, .woff, .woff2": "支援的字型格式:.ttf, .otf, .woff, .woff2", + "Push Progress": "推送閱讀進度", + "Pull Progress": "拉取閱讀進度" } diff --git a/apps/readest-app/src/app/library/components/SettingsMenu.tsx b/apps/readest-app/src/app/library/components/SettingsMenu.tsx index dbae7fc5..cf258794 100644 --- a/apps/readest-app/src/app/library/components/SettingsMenu.tsx +++ b/apps/readest-app/src/app/library/components/SettingsMenu.tsx @@ -8,7 +8,6 @@ import { TbSunMoon } from 'react-icons/tb'; import { BiMoon, BiSun } from 'react-icons/bi'; import { setAboutDialogVisible } from '@/components/AboutWindow'; -import { setKOSyncSettingsWindowVisible } from './KOSyncSettings'; import { isTauriAppPlatform, isWebAppPlatform } from '@/services/environment'; import { DOWNLOAD_READEST_URL } from '@/services/constants'; import { useAuth } from '@/context/AuthContext'; @@ -159,11 +158,6 @@ const SettingsMenu: React.FC = ({ setIsDropdownOpen }) => { setIsTelemetryEnabled(settings.telemetryEnabled); }; - const showKoSyncSettingsWindow = () => { - setKOSyncSettingsWindowVisible(true); - setIsDropdownOpen?.(false); - }; - const handleUpgrade = () => { navigateToProfile(router); setIsDropdownOpen?.(false); @@ -274,8 +268,6 @@ const SettingsMenu: React.FC = ({ setIsDropdownOpen }) => { onClick={cycleThemeMode} />
- -
{user && userPlan === 'free' && !appService?.isIOSApp && ( )} diff --git a/apps/readest-app/src/app/library/page.tsx b/apps/readest-app/src/app/library/page.tsx index 3633a2ba..77ea4b50 100644 --- a/apps/readest-app/src/app/library/page.tsx +++ b/apps/readest-app/src/app/library/page.tsx @@ -44,7 +44,6 @@ import { } from '@/utils/window'; import { AboutWindow } from '@/components/AboutWindow'; -import { KOSyncSettingsWindow } from './components/KOSyncSettings'; import { UpdaterWindow } from '@/components/UpdaterWindow'; import { BookMetadata } from '@/libs/document'; import { BookDetailModal } from '@/components/metadata'; @@ -730,7 +729,6 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP /> )} - diff --git a/apps/readest-app/src/app/library/components/KOSyncSettings.tsx b/apps/readest-app/src/app/reader/components/KOSyncSettings.tsx similarity index 71% rename from apps/readest-app/src/app/library/components/KOSyncSettings.tsx rename to apps/readest-app/src/app/reader/components/KOSyncSettings.tsx index 2b5772a5..5bb4ccda 100644 --- a/apps/readest-app/src/app/library/components/KOSyncSettings.tsx +++ b/apps/readest-app/src/app/reader/components/KOSyncSettings.tsx @@ -1,17 +1,16 @@ +import clsx from 'clsx'; import React, { useState, useEffect, useMemo, useCallback } from 'react'; import { md5 } from 'js-md5'; -import clsx from 'clsx'; import { type as osType } from '@tauri-apps/plugin-os'; -import Dialog from '@/components/Dialog'; +import { useEnv } from '@/context/EnvContext'; import { useTranslation } from '@/hooks/useTranslation'; import { useSettingsStore } from '@/store/settingsStore'; -import { useEnv } from '@/context/EnvContext'; import { eventDispatcher } from '@/utils/event'; import { KOSyncClient } from '@/services/sync/KOSyncClient'; -import { KoreaderSyncChecksumMethod, KoreaderSyncStrategy } from '@/types/settings'; -import { v4 as uuidv4 } from 'uuid'; +import { KOSyncChecksumMethod, KOSyncStrategy } from '@/types/settings'; import { debounce } from '@/utils/debounce'; import { getOSPlatform } from '@/utils/misc'; +import Dialog from '@/components/Dialog'; type Option = { value: string; @@ -69,19 +68,14 @@ export const KOSyncSettingsWindow: React.FC = () => { const { envConfig, appService } = useEnv(); const [isOpen, setIsOpen] = useState(false); - const [url, setUrl] = useState(settings.koreaderSyncServerUrl || ''); - const [username, setUsername] = useState(settings.koreaderSyncUsername || ''); + const [url, setUrl] = useState(settings.kosync.serverUrl || ''); + const [username, setUsername] = useState(settings.kosync.username || ''); const [password, setPassword] = useState(''); const [isConnecting, setIsConnecting] = useState(false); const [connectionStatus, setConnectionStatus] = useState(''); const [deviceName, setDeviceName] = useState(''); const [osName, setOsName] = useState(''); - const [toleranceSliderValue, setToleranceSliderValue] = useState(() => { - const tolerance = settings.koreaderSyncPercentageTolerance; - return tolerance && tolerance > 0 ? Math.round(-Math.log10(tolerance)) : 4; - }); - // Get the OS name once useEffect(() => { const formatOsName = (name: string): string => { @@ -108,13 +102,10 @@ export const KOSyncSettingsWindow: React.FC = () => { useEffect(() => { const defaultName = osName ? `Readest (${osName})` : 'Readest'; - setDeviceName(settings.koreaderSyncDeviceName || defaultName); - }, [settings.koreaderSyncDeviceName, osName]); + setDeviceName(settings.kosync.deviceName || defaultName); + }, [settings.kosync.deviceName, osName]); - const isConfigured = useMemo( - () => !!settings.koreaderSyncUserkey, - [settings.koreaderSyncUserkey], - ); + const isConfigured = useMemo(() => !!settings.kosync.userkey, [settings.kosync.userkey]); // eslint-disable-next-line react-hooks/exhaustive-deps const debouncedSaveDeviceName = useCallback( @@ -136,15 +127,10 @@ export const KOSyncSettingsWindow: React.FC = () => { const handleCustomEvent = (event: CustomEvent) => { setIsOpen(event.detail.visible); if (event.detail.visible) { - setUrl(settings.koreaderSyncServerUrl || ''); - setUsername(settings.koreaderSyncUsername || ''); + setUrl(settings.kosync.serverUrl || ''); + setUsername(settings.kosync.username || ''); setPassword(''); setConnectionStatus(''); - // Sync the slider with the current settings when opening - const tolerance = settings.koreaderSyncPercentageTolerance; - setToleranceSliderValue( - tolerance && tolerance > 0 ? Math.round(-Math.log10(tolerance)) : 4, - ); } }; const el = document.getElementById('kosync_settings_window'); @@ -152,40 +138,26 @@ export const KOSyncSettingsWindow: React.FC = () => { return () => { el?.removeEventListener('setKOSyncSettingsVisibility', handleCustomEvent as EventListener); }; - }, [ - settings.koreaderSyncServerUrl, - settings.koreaderSyncUsername, - settings.koreaderSyncPercentageTolerance, - ]); + }, [settings.kosync.serverUrl, settings.kosync.username]); const handleConnect = async () => { setIsConnecting(true); - let deviceId = settings.koreaderSyncDeviceId; - if (!deviceId) { - deviceId = uuidv4().replace(/-/g, '').toUpperCase(); - } - - const client = new KOSyncClient( - url, + const config = { + ...settings.kosync, + serverUrl: url, username, - md5(password), - settings.koreaderSyncChecksumMethod, - deviceId, + userkey: md5(password), deviceName, - ); + enabled: true, + }; + const client = new KOSyncClient(config); const result = await client.connect(username, password); if (result.success) { const newSettings = { ...settings, - koreaderSyncServerUrl: url, - koreaderSyncUsername: username, - koreaderSyncUserkey: md5(password), - koreaderSyncDeviceId: deviceId, - koreaderSyncDeviceName: deviceName, - koreaderSyncStrategy: - settings.koreaderSyncStrategy === 'disabled' ? 'prompt' : settings.koreaderSyncStrategy, + kosync: config, }; setSettings(newSettings); await saveSettings(envConfig, newSettings); @@ -201,11 +173,12 @@ export const KOSyncSettingsWindow: React.FC = () => { }; const handleDisconnect = async () => { - const newSettings = { - ...settings, - koreaderSyncStrategy: 'disabled' as KoreaderSyncStrategy, - koreaderSyncUserkey: '', + const kosync = { + ...settings.kosync, + userkey: '', + enabled: false, }; + const newSettings = { ...settings, kosync }; setSettings(newSettings); await saveSettings(envConfig, newSettings); setUsername(''); @@ -213,26 +186,23 @@ export const KOSyncSettingsWindow: React.FC = () => { }; const handleStrategyChange = async (e: React.ChangeEvent) => { - const newStrategy = e.target.value as KoreaderSyncStrategy; - const newSettings = { ...settings, koreaderSyncStrategy: newStrategy }; + const kosync = { + ...settings.kosync, + strategy: e.target.value as KOSyncStrategy, + }; + + const newSettings = { ...settings, kosync }; setSettings(newSettings); await saveSettings(envConfig, newSettings); }; const handleChecksumMethodChange = async (e: React.ChangeEvent) => { - const newMethod = e.target.value as KoreaderSyncChecksumMethod; - const newSettings = { ...settings, koreaderSyncChecksumMethod: newMethod }; - setSettings(newSettings); - await saveSettings(envConfig, newSettings); - }; + const kosync = { + ...settings.kosync, + checksumMethod: e.target.value as KOSyncChecksumMethod, + }; - const handleToleranceChange = async (e: React.ChangeEvent) => { - const sliderValue = parseInt(e.target.value, 10); - setToleranceSliderValue(sliderValue); - // Calculate the actual tolerance from the slider value (e.g., 4 -> 0.0001) - const newTolerance = Math.pow(10, -sliderValue); - - const newSettings = { ...settings, koreaderSyncPercentageTolerance: newTolerance }; + const newSettings = { ...settings, kosync }; setSettings(newSettings); await saveSettings(envConfig, newSettings); }; @@ -251,18 +221,16 @@ export const KOSyncSettingsWindow: React.FC = () => {

{_('Sync as {{userDisplayName}}', { - userDisplayName: settings.koreaderSyncUsername, + userDisplayName: settings.kosync.username, })}

- - {_('Sync Server Connected', { username: settings.koreaderSyncUsername })} - + {_('Sync Server Connected')} handleDisconnect()} />
@@ -271,14 +239,13 @@ export const KOSyncSettingsWindow: React.FC = () => { {_('Sync Strategy')} @@ -287,7 +254,7 @@ export const KOSyncSettingsWindow: React.FC = () => { {_('Checksum Method')} { onChange={handleDeviceNameChange} /> - {/* Hidden to avoid confusing users with technical details */} - {false && ( -
- - -
- {_('Precision: {{precision}} digits after the decimal', { - precision: toleranceSliderValue, - })} -
-
- )} ) : ( <> diff --git a/apps/readest-app/src/app/reader/components/Reader.tsx b/apps/readest-app/src/app/reader/components/Reader.tsx index db5af035..dbc66c41 100644 --- a/apps/readest-app/src/app/reader/components/Reader.tsx +++ b/apps/readest-app/src/app/reader/components/Reader.tsx @@ -21,6 +21,7 @@ import { isTauriAppPlatform } from '@/services/environment'; import { getSysFontsList, setSystemUIVisibility } from '@/utils/bridge'; import { AboutWindow } from '@/components/AboutWindow'; import { UpdaterWindow } from '@/components/UpdaterWindow'; +import { KOSyncSettingsWindow } from './KOSyncSettings'; import { Toast } from '@/components/Toast'; import { getLocale } from '@/utils/misc'; import { initDayjs } from '@/utils/time'; @@ -120,6 +121,7 @@ const Reader: React.FC<{ ids?: string }> = ({ ids }) => { + diff --git a/apps/readest-app/src/app/reader/components/ReaderContent.tsx b/apps/readest-app/src/app/reader/components/ReaderContent.tsx index cb25a6ad..88f7f610 100644 --- a/apps/readest-app/src/app/reader/components/ReaderContent.tsx +++ b/apps/readest-app/src/app/reader/components/ReaderContent.tsx @@ -111,7 +111,7 @@ const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ i if (isPrimary && book && config) { const settings = useSettingsStore.getState().settings; eventDispatcher.dispatch('sync-book-progress', { bookKey }); - eventDispatcher.dispatch('flush-koreader-sync', { bookKey }); + eventDispatcher.dispatch('flush-kosync', { bookKey }); await saveConfig(envConfig, bookKey, config, settings); } }; diff --git a/apps/readest-app/src/app/reader/components/sidebar/BookMenu.tsx b/apps/readest-app/src/app/reader/components/sidebar/BookMenu.tsx index 9ef295b8..1b332f6e 100644 --- a/apps/readest-app/src/app/reader/components/sidebar/BookMenu.tsx +++ b/apps/readest-app/src/app/reader/components/sidebar/BookMenu.tsx @@ -8,10 +8,12 @@ import { useReaderStore } from '@/store/readerStore'; import { useLibraryStore } from '@/store/libraryStore'; import { useSidebarStore } from '@/store/sidebarStore'; import { useTranslation } from '@/hooks/useTranslation'; +import { useSettingsStore } from '@/store/settingsStore'; import { useParallelViewStore } from '@/store/parallelViewStore'; import { isWebAppPlatform } from '@/services/environment'; import { eventDispatcher } from '@/utils/event'; import { DOWNLOAD_READEST_URL } from '@/services/constants'; +import { setKOSyncSettingsWindowVisible } from '@/app/reader/components/KOSyncSettings'; import useBooksManager from '../../hooks/useBooksManager'; import MenuItem from '@/components/MenuItem'; @@ -22,6 +24,7 @@ interface BookMenuProps { const BookMenu: React.FC = ({ menuClassName, setIsDropdownOpen }) => { const _ = useTranslation(); + const { settings } = useSettingsStore(); const { bookKeys, getViewSettings, setViewSettings } = useReaderStore(); const { getVisibleLibrary } = useLibraryStore(); const { openParallelView } = useBooksManager(); @@ -69,6 +72,18 @@ const BookMenu: React.FC = ({ menuClassName, setIsDropdownOpen }) unsetParallel(bookKeys); setIsDropdownOpen?.(false); }; + const showKoSyncSettingsWindow = () => { + setKOSyncSettingsWindowVisible(true); + setIsDropdownOpen?.(false); + }; + const handlePullKOSync = () => { + eventDispatcher.dispatch('pull-kosync', { bookKey: sideBarBookKey }); + setIsDropdownOpen?.(false); + }; + const handlePushKOSync = () => { + eventDispatcher.dispatch('push-kosync', { bookKey: sideBarBookKey }); + setIsDropdownOpen?.(false); + }; const isWebApp = isWebAppPlatform(); @@ -118,6 +133,14 @@ const BookMenu: React.FC = ({ menuClassName, setIsDropdownOpen })

+ + {settings.kosync.enabled && ( + <> + + + + )} +
{ const _ = useTranslation(); + const { appService } = useEnv(); const { settings } = useSettingsStore(); const { getProgress, getView } = useReaderStore(); const { getBookData } = useBookDataStore(); - const { appService } = useEnv(); - const progress = getProgress(bookKey); + const [kosyncClient, setKOSyncClient] = useState(null); const [syncState, setSyncState] = useState('idle'); const [conflictDetails, setConflictDetails] = useState(null); const [errorMessage] = useState(null); + const hasPulledOnce = useRef(false); - const syncCompletedForKey = useRef(null); - const lastPushedCfiRef = useRef(null); - const bookData = getBookData(bookKey); - const book = bookData?.book; - const bookDoc = bookData?.bookDoc; + const progress = getProgress(bookKey); useEffect(() => { - lastPushedCfiRef.current = null; - syncCompletedForKey.current = null; - setSyncState('idle'); - }, [bookKey]); + if (!settings.kosync.username || !settings.kosync.userkey) { + setKOSyncClient(null); + return; + } + const client = new KOSyncClient(settings.kosync); + setKOSyncClient(client); + }, [settings]); - const mapProgressToServerFormat = useCallback(() => { - const currentProgress = getProgress(bookKey); - const currentBook = getBookData(bookKey)?.book; - if (!currentProgress || !currentBook) return null; + const generateKOProgress = useCallback(() => { + const progress = getProgress(bookKey); + const book = getBookData(bookKey)?.book; + if (!progress || !book) return null; - let progressStr: string; + let koProgress = ''; let percentage: number; - - if (FIXED_LAYOUT_FORMATS.has(currentBook.format)) { - const page = (currentProgress.section?.current ?? 0) + 1; - const totalPages = currentProgress.section?.total ?? 0; - progressStr = page.toString(); - percentage = totalPages > 0 ? page / totalPages : 0; + if (FIXED_LAYOUT_FORMATS.has(book.format)) { + const page = progress.section?.current ?? 0; + const totalPages = progress.section?.total ?? 0; + koProgress = page.toString(); + percentage = totalPages > 0 ? (page + 1) / totalPages : 0; } else { - progressStr = currentProgress.location; const view = getView(bookKey); - - if (view && progressStr) { - try { - const content = view.renderer.getContents()[0]; - if (content) { - const { doc, index: spineIndex } = content; - const converter = new XCFI(doc, spineIndex || 0); - const xpointerResult = converter.cfiToXPointer(progressStr); - - progressStr = xpointerResult.xpointer; - } - } catch (error) { - console.error( - 'Failed to convert CFI to XPointer. Progress will be sent as percentage only.', - error, - ); + const cfi = progress.location; + if (!view || !cfi) return null; + try { + const content = view.renderer.getContents()[0]; + if (content) { + const { doc, index: spineIndex } = content; + const converter = new XCFI(doc, spineIndex || 0); + const xpointerResult = converter.cfiToXPointer(cfi); + koProgress = xpointerResult.xpointer; } + } catch (error) { + console.error('Failed to convert CFI to XPointer', error); } - const page = currentProgress.pageinfo?.current ?? 0; - const totalPages = currentProgress.pageinfo?.total ?? 0; + const page = progress.pageinfo?.current ?? 0; + const totalPages = progress.pageinfo?.total ?? 0; percentage = totalPages > 0 ? (page + 1) / totalPages : 0; } - return { progressStr, percentage }; + return { koProgress, percentage }; }, [bookKey, getProgress, getBookData, getView]); + const applyRemoteProgress = async (book: Book, bookDoc: BookDoc, remote: KoSyncProgress) => { + const view = getView(bookKey); + if (FIXED_LAYOUT_FORMATS.has(book.format)) { + const pageToGo = parseInt(remote.progress!, 10); + if (isNaN(pageToGo)) return; + view?.select(pageToGo - 1); + } else { + if (!remote.progress?.startsWith('/body')) return; + try { + const content = view?.renderer.getContents()[0]; + const cfi = await getCFIFromXPointer( + remote.progress!, + content?.doc, + content?.index, + bookDoc, + ); + view?.goTo(cfi); + } catch (error) { + console.error('Failed to convert XPointer to CFI', error); + return; + } + } + eventDispatcher.dispatch('toast', { message: _('Reading Progress Synced'), type: 'info' }); + }; + + const promptedSync = async ( + book: Book, + bookDoc: BookDoc, + local: BookProgress, + remote: KoSyncProgress, + ) => { + let localPreview = ''; + let remotePreview = ''; + const remotePercentage = remote.percentage || 0; + + if (FIXED_LAYOUT_FORMATS.has(book.format)) { + const localPageInfo = local.section; + const localPercentage = + localPageInfo && localPageInfo.total > 0 + ? Math.round(((localPageInfo.current + 1) / localPageInfo.total) * 100) + : 0; + localPreview = localPageInfo + ? _('Page {{page}} of {{total}} ({{percentage}}%)', { + page: localPageInfo.current + 1, + total: localPageInfo.total, + percentage: localPercentage, + }) + : _('Current position'); + + const remotePage = parseInt(remote.progress!, 10); + if (!isNaN(remotePage) && remotePercentage > 0) { + const localTotalPages = localPageInfo?.total ?? 0; + const remoteTotalPages = Math.round(remotePage / remotePercentage); + const pagesMatch = Math.abs(localTotalPages - remoteTotalPages) <= 1; + + if (pagesMatch) { + remotePreview = _('Page {{page}} of {{total}} ({{percentage}}%)', { + page: remotePage, + total: remoteTotalPages, + percentage: Math.round(remotePercentage * 100), + }); + } else { + remotePreview = _('Approximately page {{page}} of {{total}} ({{percentage}}%)', { + page: remotePage, + total: remoteTotalPages, + percentage: Math.round(remotePercentage * 100), + }); + } + } else { + remotePreview = _('Approximately {{percentage}}%', { + percentage: Math.round(remotePercentage * 100), + }); + } + } else { + const localPageInfo = local.pageinfo; + const localPercentage = + localPageInfo && localPageInfo.total > 0 + ? Math.round(((localPageInfo.current + 1) / localPageInfo.total) * 100) + : 0; + localPreview = `${local.sectionLabel} (${localPercentage}%)`; + + remotePreview = _('Approximately {{percentage}}%', { + percentage: Math.round(remotePercentage * 100), + }); + } + + setConflictDetails({ + book, + bookDoc, + local: { cfi: local.location, preview: localPreview }, + remote: { ...remote, preview: remotePreview }, + }); + }; + const pushProgress = useMemo( () => debounce(async () => { - const { settings: currentSettings } = useSettingsStore.getState(); + if (!bookKey || !appService || !kosyncClient || !hasPulledOnce.current) return; + const { settings } = useSettingsStore.getState(); + if (['receive', 'disable'].includes(settings.kosync.strategy)) return; + const currentBook = getBookData(bookKey)?.book; + const progress = generateKOProgress(); + if (!currentBook || !progress || !progress.koProgress) return; - const { koreaderSyncUsername, koreaderSyncUserkey, koreaderSyncStrategy } = currentSettings; - if ( - !koreaderSyncUsername || - !koreaderSyncUserkey || - ['receive', 'disable'].includes(koreaderSyncStrategy) || - !currentBook - ) - return; - - const getDocumentDigest = (bookToDigest: Book): string => { - if (currentSettings.koreaderSyncChecksumMethod === 'filename') { - const filename = bookToDigest.sourceTitle || bookToDigest.title; - const normalizedPath = filename.replace(/\\/g, '/'); - return md5( - normalizedPath.split('/').pop()?.split('.').slice(0, -1).join('.') || normalizedPath, - ); - } - return bookToDigest.hash; - }; - - const getDeviceName = async () => { - if (currentSettings.koreaderSyncDeviceName) return currentSettings.koreaderSyncDeviceName; - if (appService?.appPlatform === 'tauri') { - const name = await osType(); - return `Readest (${name.charAt(0).toUpperCase() + name.slice(1)})`; - } - return 'Readest'; - }; - - const digest = getDocumentDigest(currentBook); - const progressData = mapProgressToServerFormat(); - if (!digest || !progressData) return; - - if (progressData.progressStr === lastPushedCfiRef.current) return; - - const deviceName = await getDeviceName(); - const client = new KOSyncClient( - currentSettings.koreaderSyncServerUrl, - currentSettings.koreaderSyncUsername, - currentSettings.koreaderSyncUserkey, - currentSettings.koreaderSyncChecksumMethod, - currentSettings.koreaderSyncDeviceId, - deviceName, - ); - - await client.updateProgress(currentBook, progressData.progressStr, progressData.percentage); - lastPushedCfiRef.current = progressData.progressStr; + await kosyncClient.updateProgress(currentBook, progress.koProgress, progress.percentage); }, 5000), - [bookKey, appService, getBookData, mapProgressToServerFormat], + // eslint-disable-next-line react-hooks/exhaustive-deps + [bookKey, appService, kosyncClient, generateKOProgress], ); - useEffect(() => { - const handleFlush = (event: CustomEvent) => { - const { bookKey: syncBookKey } = event.detail; - if (syncBookKey === bookKey) { - pushProgress.flush(); - } - }; - eventDispatcher.on('flush-koreader-sync', handleFlush); - return () => { - eventDispatcher.off('flush-koreader-sync', handleFlush); - pushProgress.flush(); - }; - }, [bookKey, pushProgress]); + const pullProgress = useCallback( + async () => { + if (!progress?.location || !appService || !kosyncClient) return; - useEffect(() => { - const performInitialSync = async () => { - const { koreaderSyncUsername, koreaderSyncUserkey, koreaderSyncStrategy } = settings; - if ( - !book || - !bookDoc || - !progress || - !koreaderSyncUsername || - !koreaderSyncUserkey || - koreaderSyncStrategy === 'disabled' - ) - return; + const bookData = getBookData(bookKey); + const book = bookData?.book; + const bookDoc = bookData?.bookDoc; + if (!book || !bookDoc) return; - if (koreaderSyncStrategy === 'send') { - syncCompletedForKey.current = bookKey; + const { strategy, enabled } = settings.kosync; + if (!enabled) return; + + hasPulledOnce.current = true; + if (strategy === 'send') { setSyncState('synced'); return; } setSyncState('checking'); - - const getDeviceName = async () => { - if (settings.koreaderSyncDeviceName) return settings.koreaderSyncDeviceName; - if (appService?.appPlatform === 'tauri') { - const name = await osType(); - return `Readest (${name.charAt(0).toUpperCase() + name.slice(1)})`; - } - return 'Readest'; - }; - - const deviceName = await getDeviceName(); - const client = new KOSyncClient( - settings.koreaderSyncServerUrl, - settings.koreaderSyncUsername, - settings.koreaderSyncUserkey, - settings.koreaderSyncChecksumMethod, - settings.koreaderSyncDeviceId, - deviceName, - ); - const remote = await client.getProgress(book); - lastPushedCfiRef.current = progress.location; - - if (!remote?.progress || !remote?.timestamp) { - syncCompletedForKey.current = bookKey; + const remoteProgress = await kosyncClient.getProgress(book); + if (!remoteProgress || !remoteProgress.progress || !remoteProgress.timestamp) { setSyncState('synced'); - if (settings.koreaderSyncStrategy !== 'receive') { - pushProgress(); - pushProgress.flush(); - } return; } const localTimestamp = bookData?.config?.updatedAt || book.updatedAt; - const remoteIsNewer = remote.timestamp * 1000 > localTimestamp; - - const localIdentifier = FIXED_LAYOUT_FORMATS.has(book.format) - ? progress.section?.current.toString() - : progress.location; - const isLocalCFI = localIdentifier?.startsWith('epubcfi'); - - const remoteIdentifier = FIXED_LAYOUT_FORMATS.has(book.format) - ? (parseInt(remote.progress, 10) - 1).toString() - : remote.progress.startsWith('epubcfi') - ? remote.progress - : null; - const isRemoteCFI = remoteIdentifier?.startsWith('epubcfi'); - - let isProgressIdentical = false; - if (isLocalCFI && isRemoteCFI) { - isProgressIdentical = localIdentifier === remoteIdentifier; - } - - if (!isProgressIdentical) { - const localPercentage = mapProgressToServerFormat()?.percentage ?? 0; - const remotePercentage = remote.percentage; - - if (remotePercentage !== undefined && remotePercentage !== null) { - const tolerance = settings.koreaderSyncPercentageTolerance; - const percentageDifference = Math.abs(localPercentage - remotePercentage); - isProgressIdentical = percentageDifference < tolerance; - } - } - - if (isProgressIdentical) { - lastPushedCfiRef.current = localIdentifier; - syncCompletedForKey.current = bookKey; + const remoteIsNewer = remoteProgress.timestamp * 1000 > localTimestamp; + if (strategy === 'receive' || (strategy === 'silent' && remoteIsNewer)) { + applyRemoteProgress(book, bookDoc, remoteProgress); setSyncState('synced'); - return; - } - - if ( - settings.koreaderSyncStrategy === 'receive' || - (settings.koreaderSyncStrategy === 'silent' && remoteIsNewer) - ) { - const applyRemoteProgress = async () => { - const view = getView(bookKey); - if (view && remote.progress) { - if (FIXED_LAYOUT_FORMATS.has(book.format)) { - const pageToGo = parseInt(remote.progress, 10); - if (!isNaN(pageToGo)) view.select(pageToGo - 1); - } else { - const isXPointer = remote.progress.startsWith('/body'); - if (isXPointer) { - try { - const content = view.renderer.getContents()[0]; - if (content) { - const { doc, index } = content; - const cfi = await getCFIFromXPointer(remote.progress, doc, index || 0, bookDoc); - view.goTo(cfi); - eventDispatcher.dispatch('toast', { - message: _('Reading Progress Synced'), - type: 'info', - }); - } - } catch (error) { - console.error( - 'Failed to convert XPointer to CFI, falling back to percentage.', - error, - ); - if (remote.percentage !== undefined && remote.percentage !== null) { - view.goToFraction(remote.percentage); - } - } - } else { - if (remote.percentage !== undefined && remote.percentage !== null) { - view.goToFraction(remote.percentage); - } - } - } - eventDispatcher.dispatch('toast', { - message: _('Reading Progress Synced'), - type: 'info', - }); - } - }; - - applyRemoteProgress(); - syncCompletedForKey.current = bookKey; - setSyncState('synced'); - } else if (settings.koreaderSyncStrategy === 'prompt') { - let localPreview = ''; - let remotePreview = ''; - const remotePercentage = remote.percentage || 0; - - if (FIXED_LAYOUT_FORMATS.has(book.format)) { - const localPageInfo = progress.section; - const localPercentage = - localPageInfo && localPageInfo.total > 0 - ? Math.round(((localPageInfo.current + 1) / localPageInfo.total) * 100) - : 0; - localPreview = localPageInfo - ? _('Page {{page}} of {{total}} ({{percentage}}%)', { - page: localPageInfo.current + 1, - total: localPageInfo.total, - percentage: localPercentage, - }) - : _('Current position'); - - const remotePage = parseInt(remote.progress, 10); - if (!isNaN(remotePage) && remotePercentage > 0) { - const localTotalPages = localPageInfo?.total ?? 0; - const remoteTotalPages = Math.round(remotePage / remotePercentage); - const pagesMatch = Math.abs(localTotalPages - remoteTotalPages) <= 1; - - if (pagesMatch) { - remotePreview = _('Page {{page}} of {{total}} ({{percentage}}%)', { - page: remotePage, - total: remoteTotalPages, - percentage: Math.round(remotePercentage * 100), - }); - } else { - remotePreview = _('Approximately page {{page}} of {{total}} ({{percentage}}%)', { - page: remotePage, - total: remoteTotalPages, - percentage: Math.round(remotePercentage * 100), - }); - } - } else { - remotePreview = _('Approximately {{percentage}}%', { - percentage: Math.round(remotePercentage * 100), - }); - } - } else { - const localPageInfo = progress.pageinfo; - const localPercentage = - localPageInfo && localPageInfo.total > 0 - ? Math.round(((localPageInfo.current + 1) / localPageInfo.total) * 100) - : 0; - localPreview = `${progress.sectionLabel} (${localPercentage}%)`; - - remotePreview = _('Approximately {{percentage}}%', { - percentage: Math.round(remotePercentage * 100), - }); - } - - setConflictDetails({ - book, - bookDoc, - local: { cfi: progress.location, preview: localPreview }, - remote: { ...remote, preview: remotePreview, percentage: remote.percentage }, - }); + } else if (strategy === 'prompt') { + promptedSync(book, bookDoc, progress, remoteProgress); setSyncState('conflict'); } else { - syncCompletedForKey.current = bookKey; setSyncState('synced'); } - }; - - if (bookKey && book && progress && syncCompletedForKey.current !== bookKey) { - syncCompletedForKey.current = bookKey; - performInitialSync(); - } - }, [ - bookKey, - book, - bookDoc, - progress, - settings, - appService, - getBookData, - getProgress, - getView, - mapProgressToServerFormat, - pushProgress, - _, - bookData?.config?.updatedAt, - ]); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [bookKey, appService, kosyncClient, settings.kosync, progress], + ); + useEffect(() => { + const handlePullProgress = (event: CustomEvent) => { + if (event.detail.bookKey !== bookKey) return; + pullProgress(); + }; + const handlePushProgress = (event: CustomEvent) => { + if (event.detail.bookKey !== bookKey) return; + pushProgress(); + }; + const handleFlush = (event: CustomEvent) => { + if (event.detail.bookKey !== bookKey) return; + pushProgress.flush(); + }; + eventDispatcher.on('pull-kosync', handlePullProgress); + eventDispatcher.on('push-kosync', handlePushProgress); + eventDispatcher.on('flush-kosync', handleFlush); + return () => { + eventDispatcher.off('pull-kosync', handlePullProgress); + eventDispatcher.off('push-kosync', handlePushProgress); + eventDispatcher.off('flush-kosync', handleFlush); + pushProgress.flush(); + }; + }, [bookKey, pushProgress, pullProgress]); + + // Pull: pull progress once when the book is opened + useEffect(() => { + if (!appService || !kosyncClient || !progress?.location) return; + if (hasPulledOnce.current) return; + + pullProgress(); + }, [appService, kosyncClient, progress?.location, pushProgress, pullProgress]); + + // Push: auto-push progress when progress changes with a debounce useEffect(() => { if (syncState === 'synced' && progress) { - if ( - settings.koreaderSyncStrategy !== 'receive' && - settings.koreaderSyncStrategy !== 'disabled' - ) { + const { strategy, enabled } = settings.kosync; + if (strategy !== 'receive' && enabled) { pushProgress(); } } - }, [progress, syncState, settings.koreaderSyncStrategy, pushProgress]); - - useEffect(() => { - return () => { - pushProgress.flush(); - }; - }, [pushProgress]); + }, [progress, syncState, settings.kosync, pushProgress]); const resolveConflictWithLocal = () => { pushProgress(); @@ -421,50 +290,12 @@ export const useKOSync = (bookKey: string) => { const resolveConflictWithRemote = async () => { const view = getView(bookKey); const remote = conflictDetails?.remote; - const currentBook = conflictDetails?.book; + const book = conflictDetails?.book; const bookDoc = conflictDetails?.bookDoc; - if (view && remote?.progress && currentBook) { - if (FIXED_LAYOUT_FORMATS.has(currentBook.format)) { - const localTotalPages = getProgress(bookKey)?.section?.total ?? 0; - const remotePage = parseInt(remote.progress, 10); - const remotePercentage = remote.percentage || 0; - const remoteTotalPages = - remotePercentage > 0 ? Math.round(remotePage / remotePercentage) : 0; + if (!book || !bookDoc || !remote || !remote.progress || !view) return; - if (!isNaN(remotePage) && Math.abs(localTotalPages - remoteTotalPages) <= 1) { - console.log('Going to remote page:', remotePage); - view.select(remotePage - 1); - } else if (remote.percentage !== undefined && remote.percentage !== null) { - console.log('Going to remote percentage:', remote.percentage); - view.goToFraction(remote.percentage); - } - } else { - const isXPointer = remote.progress.startsWith('/body'); - const isCFI = remote.progress.startsWith('epubcfi'); - - if (isXPointer) { - try { - const content = view.renderer.getContents()[0]; - if (content) { - const { doc, index } = content; - const cfi = await getCFIFromXPointer(remote.progress, doc, index || 0, bookDoc); - view.goTo(cfi); - } - } catch (error) { - console.error('Failed to convert XPointer to CFI, falling back to percentage.', error); - if (remote.percentage !== undefined && remote.percentage !== null) { - view.goToFraction(remote.percentage); - } - } - } else if (isCFI) { - view.goTo(remote.progress); - } else if (remote.percentage !== undefined && remote.percentage !== null) { - view.goToFraction(remote.percentage); - } - } - eventDispatcher.dispatch('toast', { message: _('Reading Progress Synced'), type: 'info' }); - } + applyRemoteProgress(book, bookDoc, remote); setSyncState('synced'); setConflictDetails(null); }; @@ -473,8 +304,9 @@ export const useKOSync = (bookKey: string) => { syncState, conflictDetails, errorMessage, + pushProgress, + pullProgress, resolveConflictWithLocal, resolveConflictWithRemote, - pushProgress, }; }; diff --git a/apps/readest-app/src/services/appService.ts b/apps/readest-app/src/services/appService.ts index f5b0533b..a618f5e5 100644 --- a/apps/readest-app/src/services/appService.ts +++ b/apps/readest-app/src/services/appService.ts @@ -116,8 +116,8 @@ export abstract class BaseAppService implements AppService { ...settings.globalViewSettings, }; - if (!settings.koreaderSyncDeviceId) { - settings.koreaderSyncDeviceId = uuidv4(); + if (!settings.kosync.deviceId) { + settings.kosync.deviceId = uuidv4(); await this.fs.writeFile(fp, base, JSON.stringify(settings)); } } catch { diff --git a/apps/readest-app/src/services/constants.ts b/apps/readest-app/src/services/constants.ts index 530bdfd9..cbaab2d6 100644 --- a/apps/readest-app/src/services/constants.ts +++ b/apps/readest-app/src/services/constants.ts @@ -10,7 +10,7 @@ import { ViewConfig, ViewSettings, } from '@/types/book'; -import { ReadSettings, SystemSettings } from '@/types/settings'; +import { KOSyncSettings, ReadSettings, SystemSettings } from '@/types/settings'; import { UserStorageQuota, UserDailyTranslationQuota } from '@/types/user'; import { getDefaultMaxBlockSize, getDefaultMaxInlineSize } from '@/utils/config'; import { stubTranslation as _ } from '@/utils/misc'; @@ -37,6 +37,17 @@ export const BOOK_UNGROUPED_ID = ''; export const SUPPORTED_IMAGE_EXTS = ['png', 'jpg', 'jpeg']; export const IMAGE_ACCEPT_FORMATS = SUPPORTED_IMAGE_EXTS.map((ext) => `.${ext}`).join(', '); +export const DEFAULT_KOSYNC_SETTINGS = { + serverUrl: 'https://sync.koreader.rocks/', // https://kosync.ak-team.com:3042/ + username: '', + userkey: '', + deviceId: '', + deviceName: '', + checksumMethod: 'binary', + strategy: 'prompt', + enabled: false, +} as KOSyncSettings; + export const DEFAULT_SYSTEM_SETTINGS: Partial = { keepLogin: false, autoUpload: true, @@ -54,14 +65,7 @@ export const DEFAULT_SYSTEM_SETTINGS: Partial = { librarySortAscending: false, libraryCoverFit: 'crop', - koreaderSyncServerUrl: 'https://sync.koreader.rocks/', // https://kosync.ak-team.com:3042/ - koreaderSyncUsername: '', - koreaderSyncUserkey: '', - koreaderSyncDeviceId: '', - koreaderSyncDeviceName: '', - koreaderSyncChecksumMethod: 'binary', - koreaderSyncStrategy: 'prompt', - koreaderSyncPercentageTolerance: 0.00001, + kosync: DEFAULT_KOSYNC_SETTINGS, lastSyncedAtBooks: 0, lastSyncedAtConfigs: 0, diff --git a/apps/readest-app/src/services/sync/KOSyncClient.ts b/apps/readest-app/src/services/sync/KOSyncClient.ts index 117d6a6e..d5adc7b7 100644 --- a/apps/readest-app/src/services/sync/KOSyncClient.ts +++ b/apps/readest-app/src/services/sync/KOSyncClient.ts @@ -1,10 +1,9 @@ import { md5 } from 'js-md5'; import { Book } from '@/types/book'; -import { KoreaderSyncChecksumMethod } from '@/types/settings'; +import { KOSyncSettings } from '@/types/settings'; import { fetch as tauriFetch } from '@tauri-apps/plugin-http'; import { KoSyncProxyPayload } from '@/types/kosync'; import { isLanAddress } from '@/utils/network'; -import { getBaseFilename } from '@/utils/path'; import { getAPIBaseUrl, isTauriAppPlatform } from '../environment'; /** @@ -20,29 +19,13 @@ export interface KoSyncProgress { } export class KOSyncClient { - private serverUrl: string; - private username: string; - private userkey: string; - private checksumMethod: KoreaderSyncChecksumMethod; - private deviceId: string; - private deviceName: string; + private config: KOSyncSettings; private isLanServer: boolean; - constructor( - serverUrl: string, - username: string, - userkey: string, - checksumMethod: KoreaderSyncChecksumMethod, - deviceId: string, - deviceName: string, - ) { - this.serverUrl = serverUrl.replace(/\/$/, ''); - this.username = username; - this.userkey = userkey; - this.checksumMethod = checksumMethod; - this.deviceId = deviceId; - this.deviceName = deviceName; - this.isLanServer = isLanAddress(this.serverUrl); + constructor(config: KOSyncSettings) { + this.config = config; + this.config.serverUrl = config.serverUrl.replace(/\/$/, ''); + this.isLanServer = isLanAddress(this.config.serverUrl); } private async request( @@ -58,13 +41,13 @@ export class KOSyncClient { const headers = new Headers(additionalHeaders || {}); if (useAuth) { - headers.set('X-Auth-User', this.username); - headers.set('X-Auth-Key', this.userkey); + headers.set('X-Auth-User', this.config.username); + headers.set('X-Auth-Key', this.config.userkey); } if (this.isLanServer) { const fetch = isTauriAppPlatform() ? tauriFetch : window.fetch; - const directUrl = `${this.serverUrl}${endpoint}`; + const directUrl = `${this.config.serverUrl}${endpoint}`; return fetch(directUrl, { method, @@ -83,7 +66,7 @@ export class KOSyncClient { const proxyUrl = `${getAPIBaseUrl()}/kosync`; const proxyBody: KoSyncProxyPayload = { - serverUrl: this.serverUrl, + serverUrl: this.config.serverUrl, endpoint, method, headers: Object.fromEntries(headers.entries()), @@ -159,7 +142,7 @@ export class KOSyncClient { * @returns Promise with the progress data or null if not found */ async getProgress(book: Book): Promise { - if (!this.userkey) return null; + if (!this.config.userkey) return null; const documentHash = this.getDocumentDigest(book); if (!documentHash) return null; @@ -190,17 +173,17 @@ export class KOSyncClient { * @returns Promise with boolean indicating success */ async updateProgress(book: Book, progress: string, percentage: number): Promise { - if (!this.userkey) return false; + if (!this.config.userkey) return false; const documentHash = this.getDocumentDigest(book); if (!documentHash) return false; const payload = { document: documentHash, - progress: progress.toString(), + progress, percentage, - device: this.deviceName, - device_id: this.deviceId, + device: this.config.deviceName, + device_id: this.config.deviceId, }; try { @@ -222,10 +205,9 @@ export class KOSyncClient { } } - private getDocumentDigest(book: Book): string | undefined { - if (this.checksumMethod === 'filename') { - const filename = getBaseFilename(book.sourceTitle || book.title); - return md5(filename); + getDocumentDigest(book: Book): string { + if (this.config.checksumMethod === 'filename') { + console.warn('This is not possible anymore, using md5 instead.'); } return book.hash; } diff --git a/apps/readest-app/src/types/settings.ts b/apps/readest-app/src/types/settings.ts index be8d4aa5..7c954857 100644 --- a/apps/readest-app/src/types/settings.ts +++ b/apps/readest-app/src/types/settings.ts @@ -7,8 +7,8 @@ export type LibraryViewModeType = 'grid' | 'list'; export type LibrarySortByType = 'title' | 'author' | 'updated' | 'created' | 'size' | 'format'; export type LibraryCoverFitType = 'crop' | 'fit'; -export type KoreaderSyncChecksumMethod = 'binary' | 'filename'; -export type KoreaderSyncStrategy = 'prompt' | 'silent' | 'send' | 'receive' | 'disabled'; +export type KOSyncChecksumMethod = 'binary' | 'filename'; +export type KOSyncStrategy = 'prompt' | 'silent' | 'send' | 'receive'; export interface ReadSettings { sideBarWidth: string; @@ -24,6 +24,17 @@ export interface ReadSettings { customThemes: CustomTheme[]; } +export interface KOSyncSettings { + enabled: boolean; + serverUrl: string; + username: string; + userkey: string; + deviceId: string; + deviceName: string; + checksumMethod: KOSyncChecksumMethod; + strategy: KOSyncStrategy; +} + export interface SystemSettings { version: number; localBooksDir: string; @@ -45,14 +56,7 @@ export interface SystemSettings { libraryCoverFit: LibraryCoverFitType; customFonts: CustomFont[]; - koreaderSyncServerUrl: string; - koreaderSyncUsername: string; - koreaderSyncUserkey: string; - koreaderSyncDeviceId: string; - koreaderSyncDeviceName: string; - koreaderSyncChecksumMethod: KoreaderSyncChecksumMethod; - koreaderSyncStrategy: KoreaderSyncStrategy; - koreaderSyncPercentageTolerance: number; + kosync: KOSyncSettings; lastSyncedAtBooks: number; lastSyncedAtConfigs: number;