diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index 89aed9a0..eb15eb7d 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -1104,5 +1104,8 @@ "Highlights synced to Readwise": "تمت مزامنة التمييزات مع Readwise", "Readwise sync failed: no internet connection": "فشلت مزامنة Readwise: لا يوجد اتصال بالإنترنت", "Readwise sync failed: {{error}}": "فشلت مزامنة Readwise: {{error}}", - "System Screen Brightness": "سطوع شاشة النظام" + "System Screen Brightness": "سطوع شاشة النظام", + "Page:": "صفحة:", + "Page: {{number}}": "صفحة: {{number}}", + "Annotation page number": "رقم صفحة التعליق" } diff --git a/apps/readest-app/public/locales/bn/translation.json b/apps/readest-app/public/locales/bn/translation.json index 5f1c40d6..421c8476 100644 --- a/apps/readest-app/public/locales/bn/translation.json +++ b/apps/readest-app/public/locales/bn/translation.json @@ -1056,5 +1056,8 @@ "Highlights synced to Readwise": "হাইলাইটগুলি Readwise এ সিঙ্ক করা হয়েছে", "Readwise sync failed: no internet connection": "Readwise সিঙ্ক ব্যর্থ হয়েছে: ইন্টারনেট সংযোগ নেই", "Readwise sync failed: {{error}}": "Readwise সিঙ্ক ব্যর্থ হয়েছে: {{error}}", - "System Screen Brightness": "সিস্টেম স্ক্রিন উজ্জ্বলতা" + "System Screen Brightness": "সিস্টেম স্ক্রিন উজ্জ্বলতা", + "Page:": "পৃষ্ঠা:", + "Page: {{number}}": "পৃষ্ঠা: {{number}}", + "Annotation page number": "টীকা পৃষ্ঠা নম্বর" } diff --git a/apps/readest-app/public/locales/bo/translation.json b/apps/readest-app/public/locales/bo/translation.json index a7ca0dea..f22e0bf8 100644 --- a/apps/readest-app/public/locales/bo/translation.json +++ b/apps/readest-app/public/locales/bo/translation.json @@ -1044,5 +1044,8 @@ "Highlights synced to Readwise": "བཀོད་མཆན་ Readwise ལ་མཉམ་བྱུང་བྱས་ཟིན།", "Readwise sync failed: no internet connection": "Readwise མཉམ་བྱུང་མ་ཐུབ། དྲ་རྒྱའི་མཐུད་ལམ་མི་འདུག", "Readwise sync failed: {{error}}": "Readwise མཉམ་བྱུང་མ་ཐུབ། {{error}}", - "System Screen Brightness": "མ་ལག་ཤེལ་སྒོའི་གསལ་ཚད།" + "System Screen Brightness": "མ་ལག་ཤེལ་སྒོའི་གསལ་ཚད།", + "Page:": "ཤོག་ལྷེ།:", + "Page: {{number}}": "ཤོག་ལྷེ།: {{number}}", + "Annotation page number": "མཆན་འགྲེལ་ཤོག་ཨང་།" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index 9c12c370..252466fb 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -1056,5 +1056,8 @@ "Highlights synced to Readwise": "Highlights mit Readwise synchronisiert", "Readwise sync failed: no internet connection": "Readwise-Synchronisierung fehlgeschlagen: Keine Internetverbindung", "Readwise sync failed: {{error}}": "Readwise-Synchronisierung fehlgeschlagen: {{error}}", - "System Screen Brightness": "System-Bildschirmhelligkeit" + "System Screen Brightness": "System-Bildschirmhelligkeit", + "Page:": "Seite:", + "Page: {{number}}": "Seite: {{number}}", + "Annotation page number": "Seitenzahl der Anmerkung" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index 15eb5ade..e177a3ce 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -1056,5 +1056,8 @@ "Highlights synced to Readwise": "Οι επισημάνσεις συγχρονίστηκαν στο Readwise", "Readwise sync failed: no internet connection": "Ο συγχρονισμός Readwise απέτυχε: δεν υπάρχει σύνδεση στο διαδίκτυο", "Readwise sync failed: {{error}}": "Ο συγχρονισμός Readwise απέτυχε: {{error}}", - "System Screen Brightness": "Φωτεινότητα οθόνης συστήματος" + "System Screen Brightness": "Φωτεινότητα οθόνης συστήματος", + "Page:": "Σελίδα:", + "Page: {{number}}": "Σελίδα: {{number}}", + "Annotation page number": "Αριθμός σελίδας σχολίου" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index 4dfc7f9b..6929e7b0 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -1068,5 +1068,8 @@ "Highlights synced to Readwise": "Resaltados sincronizados con Readwise", "Readwise sync failed: no internet connection": "Error en la sincronización con Readwise: sin conexión a internet", "Readwise sync failed: {{error}}": "Error en la sincronización con Readwise: {{error}}", - "System Screen Brightness": "Brillo de pantalla del sistema" + "System Screen Brightness": "Brillo de pantalla del sistema", + "Page:": "Página:", + "Page: {{number}}": "Página: {{number}}", + "Annotation page number": "Número de página de la anotación" } diff --git a/apps/readest-app/public/locales/fa/translation.json b/apps/readest-app/public/locales/fa/translation.json index 56ec1498..4836b390 100644 --- a/apps/readest-app/public/locales/fa/translation.json +++ b/apps/readest-app/public/locales/fa/translation.json @@ -1056,5 +1056,8 @@ "Highlights synced to Readwise": "هایلایت‌ها با Readwise همگام‌سازی شدند", "Readwise sync failed: no internet connection": "همگام‌سازی Readwise ناموفق بود: اتصال اینترنت برقرار نیست", "Readwise sync failed: {{error}}": "همگام‌سازی Readwise ناموفق بود: {{error}}", - "System Screen Brightness": "روشنایی صفحه نمایش سیستم" + "System Screen Brightness": "روشنایی صفحه نمایش سیستم", + "Page:": "صفحه:", + "Page: {{number}}": "صفحه: {{number}}", + "Annotation page number": "شماره صفحه یادداشت" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index 20e41455..e58f8d9d 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -1068,5 +1068,8 @@ "Highlights synced to Readwise": "Surlignages synchronisés avec Readwise", "Readwise sync failed: no internet connection": "Échec de la synchronisation Readwise : pas de connexion internet", "Readwise sync failed: {{error}}": "Échec de la synchronisation Readwise : {{error}}", - "System Screen Brightness": "Luminosité de l'écran système" + "System Screen Brightness": "Luminosité de l'écran système", + "Page:": "Page :", + "Page: {{number}}": "Page : {{number}}", + "Annotation page number": "Numéro de page de l'annotation" } diff --git a/apps/readest-app/public/locales/he/translation.json b/apps/readest-app/public/locales/he/translation.json index fb6ccecd..cc127a5e 100644 --- a/apps/readest-app/public/locales/he/translation.json +++ b/apps/readest-app/public/locales/he/translation.json @@ -1068,5 +1068,8 @@ "Highlights synced to Readwise": "ההדגשות סונכרנו ל-Readwise", "Readwise sync failed: no internet connection": "סנכרון Readwise נכשל: אין חיבור לאינטרנט", "Readwise sync failed: {{error}}": "סנכרון Readwise נכשל: {{error}}", - "System Screen Brightness": "בהירות מסך המערכת" + "System Screen Brightness": "בהירות מסך המערכת", + "Page:": "עמוד:", + "Page: {{number}}": "עמוד: {{number}}", + "Annotation page number": "מספר עמוד הערה" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index 40811ee8..f4612df6 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -1056,5 +1056,8 @@ "Highlights synced to Readwise": "हाइलाइट्स Readwise में सिंक हो गए", "Readwise sync failed: no internet connection": "Readwise सिंक विफल रहा: कोई इंटरनेट कनेक्शन नहीं", "Readwise sync failed: {{error}}": "Readwise सिंक विफल रहा: {{error}}", - "System Screen Brightness": "सिस्टम स्क्रीन चमक" + "System Screen Brightness": "सिस्टम स्क्रीन चमक", + "Page:": "पृष्ठ:", + "Page: {{number}}": "पृष्ठ: {{number}}", + "Annotation page number": "व्याख्या पृष्ठ संख्या" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index ce0f9c6d..491e5167 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -1044,5 +1044,8 @@ "Highlights synced to Readwise": "Sorotan disinkronkan ke Readwise", "Readwise sync failed: no internet connection": "Sinkronisasi Readwise gagal: tidak ada koneksi internet", "Readwise sync failed: {{error}}": "Sinkronisasi Readwise gagal: {{error}}", - "System Screen Brightness": "Kecerahan Layar Sistem" + "System Screen Brightness": "Kecerahan Layar Sistem", + "Page:": "Halaman:", + "Page: {{number}}": "Halaman: {{number}}", + "Annotation page number": "Nomor halaman anotasi" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index dacad684..f192bc9a 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -1068,5 +1068,8 @@ "Highlights synced to Readwise": "Evidenziazioni sincronizzate su Readwise", "Readwise sync failed: no internet connection": "Sincronizzazione Readwise fallita: nessuna connessione internet", "Readwise sync failed: {{error}}": "Sincronizzazione Readwise fallita: {{error}}", - "System Screen Brightness": "Luminosità dello schermo del sistema" + "System Screen Brightness": "Luminosità dello schermo del sistema", + "Page:": "Pagina:", + "Page: {{number}}": "Pagina: {{number}}", + "Annotation page number": "Numero di pagina dell'annotazione" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index cbebfe89..7b1f1c1e 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -1044,5 +1044,8 @@ "Highlights synced to Readwise": "ハイライトが Readwise に同期されました", "Readwise sync failed: no internet connection": "Readwise 同期失敗:インターネット接続がありません", "Readwise sync failed: {{error}}": "Readwise 同期失敗:{{error}}", - "System Screen Brightness": "システムの画面の明るさ" + "System Screen Brightness": "システムの画面の明るさ", + "Page:": "ページ:", + "Page: {{number}}": "ページ: {{number}}", + "Annotation page number": "注釈のページ番号" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index 138d8300..7003a941 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -1044,5 +1044,8 @@ "Highlights synced to Readwise": "하이라이트가 Readwise에 동기화되었습니다", "Readwise sync failed: no internet connection": "Readwise 동기화 실패: 인터넷 연결 없음", "Readwise sync failed: {{error}}": "Readwise 동기화 실패: {{error}}", - "System Screen Brightness": "시스템 화면 밝기" + "System Screen Brightness": "시스템 화면 밝기", + "Page:": "페이지:", + "Page: {{number}}": "페이지: {{number}}", + "Annotation page number": "주석 페이지 번호" } diff --git a/apps/readest-app/public/locales/ms/translation.json b/apps/readest-app/public/locales/ms/translation.json index 4a10986b..3560e651 100644 --- a/apps/readest-app/public/locales/ms/translation.json +++ b/apps/readest-app/public/locales/ms/translation.json @@ -1044,5 +1044,8 @@ "Highlights synced to Readwise": "Sorotan disinkronkan ke Readwise", "Readwise sync failed: no internet connection": "Penyinkronan Readwise gagal: tiada sambungan internet", "Readwise sync failed: {{error}}": "Penyinkronan Readwise gagal: {{error}}", - "System Screen Brightness": "Kecerahan Skrin Sistem" + "System Screen Brightness": "Kecerahan Skrin Sistem", + "Page:": "Halaman:", + "Page: {{number}}": "Halaman: {{number}}", + "Annotation page number": "Nombor halaman anotasi" } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index 687d4f43..6a92ac57 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -1056,5 +1056,8 @@ "Highlights synced to Readwise": "Markeringen gesynchroniseerd met Readwise", "Readwise sync failed: no internet connection": "Readwise-synchronisatie mislukt: geen internetverbinding", "Readwise sync failed: {{error}}": "Readwise-synchronisatie mislukt: {{error}}", - "System Screen Brightness": "Systeemhelderheid van het scherm" + "System Screen Brightness": "Systeemhelderheid van het scherm", + "Page:": "Pagina:", + "Page: {{number}}": "Pagina: {{number}}", + "Annotation page number": "Annotatiepaginanummer" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index a7802b2b..2dc92b92 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -1080,5 +1080,8 @@ "Highlights synced to Readwise": "Wyróżnienia zsynchronizowane z Readwise", "Readwise sync failed: no internet connection": "Synchronizacja Readwise nie powiodła się: brak połączenia z Internetem", "Readwise sync failed: {{error}}": "Synchronizacja Readwise nie powiodła się: {{error}}", - "System Screen Brightness": "Jasność ekranu systemowego" + "System Screen Brightness": "Jasność ekranu systemowego", + "Page:": "Strona:", + "Page: {{number}}": "Strona: {{number}}", + "Annotation page number": "Numer strony adnotacji" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index c88ef0de..66f096ba 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -1068,5 +1068,8 @@ "Highlights synced to Readwise": "Destaques sincronizados com o Readwise", "Readwise sync failed: no internet connection": "Sincronização com o Readwise falhou: sem conexão com a internet", "Readwise sync failed: {{error}}": "Sincronização com o Readwise falhou: {{error}}", - "System Screen Brightness": "Brilho da tela do sistema" + "System Screen Brightness": "Brilho da tela do sistema", + "Page:": "Página:", + "Page: {{number}}": "Página: {{number}}", + "Annotation page number": "Número de página da anotação" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index 16273b23..18ef3bba 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -1080,5 +1080,8 @@ "Highlights synced to Readwise": "Выделения синхронизированы с Readwise", "Readwise sync failed: no internet connection": "Синхронизация Readwise не удалась: нет интернет-соединения", "Readwise sync failed: {{error}}": "Синхронизация Readwise не удалась: {{error}}", - "System Screen Brightness": "Систেমная яркость экрана" + "System Screen Brightness": "Систেমная яркость экрана", + "Page:": "Страница:", + "Page: {{number}}": "Страница: {{number}}", + "Annotation page number": "Номер страницы аннотации" } diff --git a/apps/readest-app/public/locales/si/translation.json b/apps/readest-app/public/locales/si/translation.json index 851e1179..86330272 100644 --- a/apps/readest-app/public/locales/si/translation.json +++ b/apps/readest-app/public/locales/si/translation.json @@ -1056,5 +1056,8 @@ "Highlights synced to Readwise": "විශේෂ අවස්ථා Readwise සමඟ සමමුහුර්ත විය", "Readwise sync failed: no internet connection": "Readwise සමමුහුර්තකරණය අසාර්ථක විය: අන්තර්ජාල සම්බන්ධතාවයක් නොමැත", "Readwise sync failed: {{error}}": "Readwise සමමුහුර්තකරණය අසාර්ථක විය: {{error}}", - "System Screen Brightness": "පද්ධති තිර දීප්තිය" + "System Screen Brightness": "පද්ධති තිර දීප්තිය", + "Page:": "පිටුව:", + "Page: {{number}}": "පිටුව: {{number}}", + "Annotation page number": "විවරණ පිටු අංකය" } diff --git a/apps/readest-app/public/locales/sv/translation.json b/apps/readest-app/public/locales/sv/translation.json index b133dee2..9f9a0d86 100644 --- a/apps/readest-app/public/locales/sv/translation.json +++ b/apps/readest-app/public/locales/sv/translation.json @@ -1056,5 +1056,8 @@ "Highlights synced to Readwise": "Markeringar synkroniserade till Readwise", "Readwise sync failed: no internet connection": "Readwise-synkronisering misslyckades: ingen internetanslutning", "Readwise sync failed: {{error}}": "Readwise-synkronisering misslyckades: {{error}}", - "System Screen Brightness": "Systemets skärmljusstyrka" + "System Screen Brightness": "Systemets skärmljusstyrka", + "Page:": "Sida:", + "Page: {{number}}": "Sida: {{number}}", + "Annotation page number": "Annoteringssidnummer" } diff --git a/apps/readest-app/public/locales/ta/translation.json b/apps/readest-app/public/locales/ta/translation.json index 6d9865b8..797af81f 100644 --- a/apps/readest-app/public/locales/ta/translation.json +++ b/apps/readest-app/public/locales/ta/translation.json @@ -1056,5 +1056,8 @@ "Highlights synced to Readwise": "சிறப்பம்சங்கள் Readwise உடன் ஒத்திசைக்கப்பட்டன", "Readwise sync failed: no internet connection": "Readwise ஒத்திசைவு தோல்வியடைந்தது: இணைய இணைப்பு இல்லை", "Readwise sync failed: {{error}}": "Readwise ஒத்திசைவு தோல்வியடைந்தது: {{error}}", - "System Screen Brightness": "கணினித் திரை பிரகாசம்" + "System Screen Brightness": "கணினித் திரை பிரகாசம்", + "Page:": "பக்கம்:", + "Page: {{number}}": "பக்கம்: {{number}}", + "Annotation page number": "சிறுகுறிப்புப் பக்க எண்" } diff --git a/apps/readest-app/public/locales/th/translation.json b/apps/readest-app/public/locales/th/translation.json index 1a91eba6..2b12630e 100644 --- a/apps/readest-app/public/locales/th/translation.json +++ b/apps/readest-app/public/locales/th/translation.json @@ -1044,5 +1044,8 @@ "Highlights synced to Readwise": "ซิงค์ไฮไลต์ไปยัง Readwise แล้ว", "Readwise sync failed: no internet connection": "การซิงค์ Readwise ล้มเหลว: ไม่มีการเชื่อมต่ออินเทอร์เน็ต", "Readwise sync failed: {{error}}": "การซิงค์ Readwise ล้มเหลว: {{error}}", - "System Screen Brightness": "ความสว่างหน้าจอระบบ" + "System Screen Brightness": "ความสว่างหน้าจอระบบ", + "Page:": "หน้า:", + "Page: {{number}}": "หน้า: {{number}}", + "Annotation page number": "หมายเลขหน้าคำอธิบายประกอบ" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index eeb3f623..73b4652b 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -1056,5 +1056,8 @@ "Highlights synced to Readwise": "Vurgular Readwise ile senkronize edildi", "Readwise sync failed: no internet connection": "Readwise senkronizasyonu başarısız: internet bağlantısı yok", "Readwise sync failed: {{error}}": "Readwise senkronizasyonu başarısız: {{error}}", - "System Screen Brightness": "Sistem Ekran Parlaklığı" + "System Screen Brightness": "Sistem Ekran Parlaklığı", + "Page:": "Sayfa:", + "Page: {{number}}": "Sayfa: {{number}}", + "Annotation page number": "Açıklama sayfa numarası" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index 646155e2..d3d9b445 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -1080,5 +1080,8 @@ "Highlights synced to Readwise": "Виділення синхронізовано з Readwise", "Readwise sync failed: no internet connection": "Синхронізація Readwise не вдалася: немає інтернет-з'єднання", "Readwise sync failed: {{error}}": "Синхронізація Readwise не вдалася: {{error}}", - "System Screen Brightness": "Системна яскравість екрана" + "System Screen Brightness": "Системна яскравість екрана", + "Page:": "Сторінка:", + "Page: {{number}}": "Сторінка: {{number}}", + "Annotation page number": "Номер сторінки анотації" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index 543f5dd4..76d87431 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -1044,5 +1044,8 @@ "Highlights synced to Readwise": "Các phần đánh dấu đã được đồng bộ với Readwise", "Readwise sync failed: no internet connection": "Đồng bộ Readwise thất bại: không có kết nối internet", "Readwise sync failed: {{error}}": "Đồng bộ Readwise thất bại: {{error}}", - "System Screen Brightness": "Độ sáng màn hình hệ thống" + "System Screen Brightness": "Độ sáng màn hình hệ thống", + "Page:": "Trang:", + "Page: {{number}}": "Trang: {{number}}", + "Annotation page number": "Số trang chú thích" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index 59a4f050..f7d020b0 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -1044,5 +1044,8 @@ "Highlights synced to Readwise": "高亮内容已同步到 Readwise", "Readwise sync failed: no internet connection": "Readwise 同步失败:无网络连接", "Readwise sync failed: {{error}}": "Readwise 同步失败:{{error}}", - "System Screen Brightness": "系统屏幕亮度" + "System Screen Brightness": "系统屏幕亮度", + "Page:": "页面:", + "Page: {{number}}": "页面: {{number}}", + "Annotation page number": "批注页码" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index c27d62d7..8aa8747f 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -1044,5 +1044,8 @@ "Highlights synced to Readwise": "高亮內容已同步到 Readwise", "Readwise sync failed: no internet connection": "Readwise 同步失敗:無網路連線", "Readwise sync failed: {{error}}": "Readwise 同步失敗:{{error}}", - "System Screen Brightness": "系統螢幕亮度" + "System Screen Brightness": "系統螢幕亮度", + "Page:": "頁面:", + "Page: {{number}}": "頁面: {{number}}", + "Annotation page number": "註解頁碼" } diff --git a/apps/readest-app/src/__tests__/document/page-progress-epub.test.ts b/apps/readest-app/src/__tests__/document/page-progress-epub.test.ts new file mode 100644 index 00000000..02db7807 --- /dev/null +++ b/apps/readest-app/src/__tests__/document/page-progress-epub.test.ts @@ -0,0 +1,315 @@ +import { describe, it, expect, beforeAll, vi } from 'vitest'; +import { readFileSync } from 'fs'; +import { resolve } from 'path'; +import { DocumentLoader } from '@/libs/document'; +import type { BookDoc } from '@/libs/document'; +import type { FoliateView } from '@/types/view'; +import { wrappedFoliateView } from '@/types/view'; + +// jsdom's Blob doesn't implement arrayBuffer(), polyfill it via FileReader +if (typeof Blob.prototype.arrayBuffer !== 'function') { + Blob.prototype.arrayBuffer = function () { + return new Promise((res, reject) => { + const reader = new FileReader(); + reader.onload = () => res(reader.result as ArrayBuffer); + reader.onerror = () => reject(reader.error); + reader.readAsArrayBuffer(this); + }); + }; +} + +// Register a stub paginator custom element so View.open() doesn't fail in jsdom +if (!customElements.get('foliate-paginator')) { + customElements.define( + 'foliate-paginator', + class extends HTMLElement { + override setAttribute() {} + override addEventListener() {} + open() {} + }, + ); +} + +// Mock the paginator module import so View doesn't try to load the real one +vi.mock('foliate-js/paginator.js', () => ({})); + +let book: BookDoc; +let view: FoliateView; +let totalSections: number; + +const loadEPUB = async () => { + const epubPath = resolve(__dirname, '../fixtures/sample-alice.epub'); + const buffer = readFileSync(epubPath); + const file = new File([buffer], 'sample-alice.epub', { type: 'application/epub+zip' }); + const loader = new DocumentLoader(file); + const { book } = await loader.open(); + return book; +}; + +describe('getCFIProgress with real EPUB', () => { + beforeAll(async () => { + book = await loadEPUB(); + totalSections = book.sections!.length; + + // Import View and create the element, wrapping it like the app does + await import('foliate-js/view.js'); + const rawView = document.createElement('foliate-view') as FoliateView; + view = wrappedFoliateView(rawView); + await view.open(book); + }, 30000); + + it('should load the EPUB with sections', () => { + expect(book.sections!.length).toBeGreaterThan(0); + expect(view.book).toBe(book); + }); + + it('should return a valid progress object with all fields', async () => { + const sectionIndex = book.sections!.findIndex((s) => s.linear !== 'no' && s.createDocument); + const doc = await book.sections![sectionIndex]!.createDocument(); + const body = doc.body ?? doc.documentElement; + const walker = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT); + const firstTextNode = walker.nextNode(); + expect(firstTextNode).not.toBeNull(); + + const range = doc.createRange(); + range.setStart(firstTextNode!, 0); + range.setEnd(firstTextNode!, 0); + + const cfi = view.getCFI(sectionIndex, range); + const result = await view.getCFIProgress(cfi); + + expect(result).not.toBeNull(); + // fraction is the overall book progress + expect(result!.fraction).toBeGreaterThanOrEqual(0); + expect(result!.fraction).toBeLessThanOrEqual(1); + // section info + expect(result!.section.current).toBe(sectionIndex); + expect(result!.section.total).toBe(totalSections); + // location info + expect(result!.location.current).toBeGreaterThanOrEqual(0); + expect(result!.location.next).toBeGreaterThanOrEqual(result!.location.current); + expect(result!.location.total).toBeGreaterThan(0); + // time info + expect(result!.time.section).toBeGreaterThanOrEqual(0); + expect(result!.time.total).toBeGreaterThanOrEqual(0); + }); + + it('should return the smallest fraction for a CFI at the start of a section', async () => { + const sectionIndex = book.sections!.findIndex((s) => s.linear !== 'no' && s.createDocument); + expect(sectionIndex).toBeGreaterThanOrEqual(0); + + const doc = await book.sections![sectionIndex]!.createDocument(); + const body = doc.body ?? doc.documentElement; + const walker = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT); + const firstTextNode = walker.nextNode(); + expect(firstTextNode).not.toBeNull(); + + const range = doc.createRange(); + range.setStart(firstTextNode!, 0); + range.setEnd(firstTextNode!, 0); + + const cfi = view.getCFI(sectionIndex, range); + const result = await view.getCFIProgress(cfi); + + expect(result).not.toBeNull(); + expect(result!.section.current).toBe(sectionIndex); + // At the start of a section, fraction should equal the section's start fraction + // (i.e. only prior sections' sizes contribute) + expect(result!.fraction).toBeGreaterThanOrEqual(0); + }); + + it('should return a larger fraction for a CFI at the end of a section', async () => { + const sectionIndex = book.sections!.findIndex((s) => s.linear !== 'no' && s.createDocument); + const doc = await book.sections![sectionIndex]!.createDocument(); + const body = doc.body ?? doc.documentElement; + + // Get start CFI + const walkerStart = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT); + const firstTextNode = walkerStart.nextNode()!; + const startRange = doc.createRange(); + startRange.setStart(firstTextNode, 0); + startRange.setEnd(firstTextNode, 0); + const startCfi = view.getCFI(sectionIndex, startRange); + const startResult = await view.getCFIProgress(startCfi); + + // Get end CFI + const walkerEnd = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT); + let lastTextNode: Node | null = null; + for (let node = walkerEnd.nextNode(); node; node = walkerEnd.nextNode()) { + lastTextNode = node; + } + expect(lastTextNode).not.toBeNull(); + const endRange = doc.createRange(); + endRange.setStart(lastTextNode!, lastTextNode!.textContent!.length); + endRange.setEnd(lastTextNode!, lastTextNode!.textContent!.length); + const endCfi = view.getCFI(sectionIndex, endRange); + const endResult = await view.getCFIProgress(endCfi); + + expect(startResult).not.toBeNull(); + expect(endResult).not.toBeNull(); + expect(endResult!.section.current).toBe(sectionIndex); + expect(endResult!.fraction).toBeGreaterThan(startResult!.fraction); + }); + + it('should return a fraction between start and end for a midpoint CFI', async () => { + const sectionIndex = book.sections!.findIndex((s) => s.linear !== 'no' && s.size > 1000); + expect(sectionIndex).toBeGreaterThanOrEqual(0); + + const doc = await book.sections![sectionIndex]!.createDocument(); + const body = doc.body ?? doc.documentElement; + + const walker = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT); + const textNodes: Node[] = []; + for (let node = walker.nextNode(); node; node = walker.nextNode()) { + textNodes.push(node); + } + expect(textNodes.length).toBeGreaterThan(0); + + const midNodeIndex = Math.floor(textNodes.length / 2); + const midNode = textNodes[midNodeIndex]!; + const midOffset = Math.floor((midNode.textContent?.length ?? 0) / 2); + + const range = doc.createRange(); + range.setStart(midNode, midOffset); + range.setEnd(midNode, midOffset); + + const cfi = view.getCFI(sectionIndex, range); + const result = await view.getCFIProgress(cfi); + + expect(result).not.toBeNull(); + expect(result!.section.current).toBe(sectionIndex); + expect(result!.fraction).toBeGreaterThan(0); + expect(result!.fraction).toBeLessThan(1); + }); + + it('should produce consistent results for the same CFI', async () => { + const sectionIndex = book.sections!.findIndex((s) => s.linear !== 'no' && s.size > 500); + const doc = await book.sections![sectionIndex]!.createDocument(); + const body = doc.body ?? doc.documentElement; + + const walker = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT); + const node = walker.nextNode(); + expect(node).not.toBeNull(); + + const range = doc.createRange(); + const offset = Math.min(3, node!.textContent!.length); + range.setStart(node!, offset); + range.setEnd(node!, offset); + + const cfi = view.getCFI(sectionIndex, range); + + const result1 = await view.getCFIProgress(cfi); + const result2 = await view.getCFIProgress(cfi); + + expect(result1).not.toBeNull(); + expect(result2).not.toBeNull(); + expect(result1!.fraction).toBe(result2!.fraction); + expect(result1!.section.current).toBe(result2!.section.current); + expect(result1!.location.current).toBe(result2!.location.current); + }); + + it('should return increasing fractions for successive CFIs in the same section', async () => { + const sectionIndex = book.sections!.findIndex((s) => s.linear !== 'no' && s.size > 2000); + expect(sectionIndex).toBeGreaterThanOrEqual(0); + + const doc = await book.sections![sectionIndex]!.createDocument(); + const body = doc.body ?? doc.documentElement; + + const walker = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT); + const textNodes: Node[] = []; + for (let node = walker.nextNode(); node; node = walker.nextNode()) { + textNodes.push(node); + } + + const positions = [0, Math.floor(textNodes.length / 4), Math.floor((textNodes.length * 3) / 4)]; + const fractions: number[] = []; + + for (const pos of positions) { + const node = textNodes[pos]!; + const range = doc.createRange(); + range.setStart(node, 0); + range.setEnd(node, 0); + const cfi = view.getCFI(sectionIndex, range); + const result = await view.getCFIProgress(cfi); + expect(result).not.toBeNull(); + expect(result!.section.current).toBe(sectionIndex); + fractions.push(result!.fraction); + } + + for (let i = 1; i < fractions.length; i++) { + expect(fractions[i]).toBeGreaterThan(fractions[i - 1]!); + } + }); + + it('should return increasing fractions across different sections', async () => { + const linearSections = book + .sections!.map((s, i) => ({ s, i })) + .filter(({ s }) => s.linear !== 'no' && s.size > 0); + + expect(linearSections.length).toBeGreaterThan(1); + + const fractions: number[] = []; + + for (const { s, i } of linearSections.slice(0, 3)) { + const doc = await s.createDocument(); + const body = doc.body ?? doc.documentElement; + const walker = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT); + const firstTextNode = walker.nextNode(); + if (!firstTextNode) continue; + + const range = doc.createRange(); + range.setStart(firstTextNode, 0); + range.setEnd(firstTextNode, 0); + + const cfi = view.getCFI(i, range); + const result = await view.getCFIProgress(cfi); + + expect(result).not.toBeNull(); + expect(result!.section.current).toBe(i); + expect(result!.fraction).toBeGreaterThanOrEqual(0); + expect(result!.fraction).toBeLessThanOrEqual(1); + fractions.push(result!.fraction); + } + + // Fractions across sections should be increasing + for (let i = 1; i < fractions.length; i++) { + expect(fractions[i]).toBeGreaterThan(fractions[i - 1]!); + } + }); + + it('should return null for an invalid CFI', async () => { + const result = await view.getCFIProgress('epubcfi(/99/999!/0)'); + expect(result).toBeNull(); + }); + + it('should return progress for real CFIs', async () => { + const expectedData = [ + { cfi: 'epubcfi(/99/999!/0)', fraction: null }, + { cfi: 'epubcfi(/6/4!/4/2/6,/1:0,/1:13)', fraction: 0.009, page: 1 }, + { cfi: 'epubcfi(/6/8!/4/2/2[chapter_458]/4/18,/1:0,/1:681)', fraction: 0.045, page: 5 }, + { cfi: 'epubcfi(/6/10!/4/2/2[chapter_460]/2,/3:1,/3:18)', fraction: 0.098, page: 11 }, + { cfi: 'epubcfi(/6/10!/4/2/2[chapter_460]/4/70,/1:0,/1:261)', fraction: 0.161, page: 19 }, + { cfi: 'epubcfi(/6/12!/4/2/2[chapter_462]/4/22,/1:0,/1:156)', fraction: 0.179, page: 21 }, + { cfi: 'epubcfi(/6/18!/4/2/2[chapter_468]/4/6,/1:0,/1:65)', fraction: 0.411, page: 46 }, + { cfi: 'epubcfi(/6/18!/4/2/2[chapter_468]/4/94,/1:1,/1:30)', fraction: 0.446, page: 51 }, + { cfi: 'epubcfi(/6/18!/4/2/2[chapter_468]/4/134,/1:0,/1:58)', fraction: 0.473, page: 54 }, + { cfi: 'epubcfi(/6/18!/4/2/2[chapter_468]/4/170,/1:0,/1:124)', fraction: 0.491, page: 55 }, + { cfi: 'epubcfi(/6/26!/4/2/2[chapter_476]/4/120,/1:0,/1:117)', fraction: 0.804, page: 91 }, + { cfi: 'epubcfi(/6/30!/4/2/2[chapter_480]/4,/82/1:0,/86/1:26)', fraction: 0.955, page: 107 }, + { cfi: 'epubcfi(/6/30!/4/2/2[chapter_480]/4/134,/1:1,/1:118)', fraction: 0.955, page: 108 }, + { cfi: 'epubcfi(/6/30!/4/2/2[chapter_480]/4/134,/1:119,/1:358)', fraction: 0.955, page: 108 }, + ]; + + for (const { cfi, fraction, page } of expectedData) { + const result = await view.getCFIProgress(cfi); + if (fraction === null) { + expect(result).toBeNull(); + } else { + expect(result).not.toBeNull(); + expect(result!.fraction).toBeCloseTo(fraction, 1); + expect(result!.location.current).toBe(page! - 1); + expect(Math.abs(result!.fraction - fraction)).toBeLessThan(0.006); + } + } + }); +}); diff --git a/apps/readest-app/src/__tests__/document/page-progress.test.ts b/apps/readest-app/src/__tests__/document/page-progress.test.ts new file mode 100644 index 00000000..24c1a463 --- /dev/null +++ b/apps/readest-app/src/__tests__/document/page-progress.test.ts @@ -0,0 +1,223 @@ +import { describe, it, expect, vi } from 'vitest'; +import { PageProgress } from 'foliate-js/progress.js'; + +const createHTML = (html: string): Document => { + const parser = new DOMParser(); + return parser.parseFromString(`${html}`, 'text/html'); +}; + +const makeBook = (sections: { html: string }[]) => ({ + sections: sections.map(({ html }) => ({ + createDocument: () => Promise.resolve(createHTML(html)), + })), +}); + +describe('PageProgress', () => { + describe('getProgress', () => { + it('should return fraction 0 when anchor targets the start of a section', async () => { + const book = makeBook([{ html: '

Hello world

' }]); + const resolveNavigation = () => ({ + index: 0, + anchor: (doc: Document) => { + const text = doc.body.querySelector('p')!.firstChild!; + const range = doc.createRange(); + range.setStart(text, 0); + range.setEnd(text, 0); + return range; + }, + }); + + const pp = new PageProgress(book, resolveNavigation); + const result = await pp.getProgress('cfi-start'); + expect(result).not.toBeNull(); + expect(result!.fraction).toBe(0); + expect(result!.index).toBe(0); + }); + + it('should return fraction 1 when anchor targets the end of a section', async () => { + const book = makeBook([{ html: '

Hello world

' }]); + const resolveNavigation = () => ({ + index: 0, + anchor: (doc: Document) => { + const text = doc.body.querySelector('p')!.firstChild!; + const range = doc.createRange(); + range.setStart(text, text.textContent!.length); + range.setEnd(text, text.textContent!.length); + return range; + }, + }); + + const pp = new PageProgress(book, resolveNavigation); + const result = await pp.getProgress('cfi-end'); + expect(result).not.toBeNull(); + expect(result!.fraction).toBe(1); + expect(result!.index).toBe(0); + }); + + it('should return correct fraction for a midpoint in a section', async () => { + const book = makeBook([{ html: '

abcdefghij

' }]); + const resolveNavigation = () => ({ + index: 0, + anchor: (doc: Document) => { + const text = doc.body.querySelector('p')!.firstChild!; + const range = doc.createRange(); + range.setStart(text, 5); + range.setEnd(text, 5); + return range; + }, + }); + + const pp = new PageProgress(book, resolveNavigation); + const result = await pp.getProgress('cfi-mid'); + expect(result).not.toBeNull(); + expect(result!.fraction).toBe(0.5); + expect(result!.index).toBe(0); + }); + + it('should handle multiple text nodes across elements', async () => { + const book = makeBook([{ html: '

aaaa

bbbb

cccc

' }]); + const resolveNavigation = () => ({ + index: 0, + anchor: (doc: Document) => { + // target start of second

, which is at offset 4 of 12 total chars + const text = doc.body.querySelectorAll('p')[1]!.firstChild!; + const range = doc.createRange(); + range.setStart(text, 0); + range.setEnd(text, 0); + return range; + }, + }); + + const pp = new PageProgress(book, resolveNavigation); + const result = await pp.getProgress('cfi-multi'); + expect(result).not.toBeNull(); + expect(result!.fraction).toBeCloseTo(4 / 12); + expect(result!.index).toBe(0); + }); + + it('should handle anchor returning an Element instead of a Range', async () => { + const book = makeBook([{ html: '

first

second

third

' }]); + const resolveNavigation = () => ({ + index: 0, + anchor: (doc: Document) => doc.getElementById('target')!, + }); + + const pp = new PageProgress(book, resolveNavigation); + const result = await pp.getProgress('cfi-element'); + expect(result).not.toBeNull(); + // "first" is 5 chars, "second" starts at offset 5 of 16 total + expect(result!.fraction).toBeCloseTo(5 / 16); + expect(result!.index).toBe(0); + }); + + it('should return correct index for non-zero section', async () => { + const book = makeBook([ + { html: '

chapter one

' }, + { html: '

chapter two content

' }, + ]); + const resolveNavigation = () => ({ + index: 1, + anchor: (doc: Document) => { + const text = doc.body.querySelector('p')!.firstChild!; + const range = doc.createRange(); + range.setStart(text, 0); + range.setEnd(text, 0); + return range; + }, + }); + + const pp = new PageProgress(book, resolveNavigation); + const result = await pp.getProgress('cfi-section1'); + expect(result).not.toBeNull(); + expect(result!.fraction).toBe(0); + expect(result!.index).toBe(1); + }); + + it('should return null when resolveNavigation returns no index', async () => { + const book = makeBook([{ html: '

text

' }]); + const resolveNavigation = (): { index: undefined; anchor: undefined } => ({ + index: undefined, + anchor: undefined, + }); + + const pp = new PageProgress(book, resolveNavigation); + const result = await pp.getProgress('bad-cfi'); + expect(result).toBeNull(); + }); + + it('should return null when resolveNavigation returns no anchor', async () => { + const book = makeBook([{ html: '

text

' }]); + const resolveNavigation = (): { index: number; anchor: undefined } => ({ + index: 0, + anchor: undefined, + }); + + const pp = new PageProgress(book, resolveNavigation); + const result = await pp.getProgress('no-anchor'); + expect(result).toBeNull(); + }); + + it('should return null when section has no createDocument', async () => { + const book = { sections: [{}] }; + const resolveNavigation = () => ({ + index: 0, + anchor: () => null, + }); + + const pp = new PageProgress(book, resolveNavigation); + const result = await pp.getProgress('no-doc'); + expect(result).toBeNull(); + }); + + it('should return fraction 0 for an empty document', async () => { + const book = makeBook([{ html: '' }]); + const resolveNavigation = () => ({ + index: 0, + anchor: (doc: Document) => { + const range = doc.createRange(); + range.selectNodeContents(doc.body); + return range; + }, + }); + + const pp = new PageProgress(book, resolveNavigation); + const result = await pp.getProgress('cfi-empty'); + expect(result).not.toBeNull(); + expect(result!.fraction).toBe(0); + }); + + it('should return null and log error when resolveNavigation throws', async () => { + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + const book = makeBook([{ html: '

text

' }]); + const resolveNavigation = () => { + throw new Error('bad cfi'); + }; + + const pp = new PageProgress(book, resolveNavigation); + const result = await pp.getProgress('invalid'); + expect(result).toBeNull(); + expect(consoleSpy).toHaveBeenCalled(); + consoleSpy.mockRestore(); + }); + + it('should handle nested elements correctly', async () => { + const book = makeBook([{ html: '
abcd

ef

' }]); + const resolveNavigation = () => ({ + index: 0, + anchor: (doc: Document) => { + // target the text node "cd" at offset 0, which is at char 2 of 6 + const text = doc.body.querySelector('em')!.firstChild!; + const range = doc.createRange(); + range.setStart(text, 0); + range.setEnd(text, 2); + return range; + }, + }); + + const pp = new PageProgress(book, resolveNavigation); + const result = await pp.getProgress('cfi-nested'); + expect(result).not.toBeNull(); + expect(result!.fraction).toBeCloseTo(2 / 6); + }); + }); +}); diff --git a/apps/readest-app/src/__tests__/fixtures/sample-alice.epub b/apps/readest-app/src/__tests__/fixtures/sample-alice.epub new file mode 100644 index 00000000..ef6a3943 Binary files /dev/null and b/apps/readest-app/src/__tests__/fixtures/sample-alice.epub differ diff --git a/apps/readest-app/src/app/reader/components/BookmarkToggler.tsx b/apps/readest-app/src/app/reader/components/BookmarkToggler.tsx index ca1c5136..5bba1a00 100644 --- a/apps/readest-app/src/app/reader/components/BookmarkToggler.tsx +++ b/apps/readest-app/src/app/reader/components/BookmarkToggler.tsx @@ -47,6 +47,7 @@ const BookmarkToggler: React.FC = ({ bookKey }) => { cfi, text: truncatedText ? truncatedText : `${getCurrentPage(bookData.book!, progress)}`, note: '', + page: progress.page, createdAt: Date.now(), updatedAt: Date.now(), }; @@ -57,6 +58,7 @@ const BookmarkToggler: React.FC = ({ bookKey }) => { existingBookmark.deletedAt = null; existingBookmark.updatedAt = Date.now(); existingBookmark.text = bookmark.text; + existingBookmark.page = bookmark.page; } else { bookmarks.push(bookmark); } diff --git a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx index e7af1f96..be2b1fbf 100644 --- a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx +++ b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx @@ -421,6 +421,31 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + useEffect(() => { + const updateBooknotesPage = async () => { + const config = getConfig(bookKey); + const view = getView(bookKey); + if (!config || !view) return; + const { booknotes: annotations = [] } = config; + annotations.sort((a, b) => { + return CFI.compare(a.cfi, b.cfi); + }); + for (const annotation of annotations) { + if (annotation.deletedAt || annotation.page || !annotation.cfi) continue; + const progress = await view.getCFIProgress(annotation.cfi); + if (progress) { + annotation.page = progress.location.current + 1; + } + } + const updatedConfig = updateBooknotes(bookKey, annotations); + if (updatedConfig) { + saveConfig(envConfig, bookKey, updatedConfig, settings); + } + }; + setTimeout(updateBooknotesPage, 3000); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const handleQuickAction = () => { const action = viewSettings.annotationQuickAction; if (appService?.isAndroidApp && !androidTouchEndRef.current) return; @@ -585,6 +610,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { cfi, text: selection.text, note: '', + page: progress.page, createdAt: Date.now(), updatedAt: Date.now(), }; @@ -625,6 +651,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { color, text: selection.text, note: '', + page: progress.page, createdAt: Date.now(), updatedAt: Date.now(), }; diff --git a/apps/readest-app/src/app/reader/components/annotator/ExportMarkdownDialog.tsx b/apps/readest-app/src/app/reader/components/annotator/ExportMarkdownDialog.tsx index f10238da..6942e9cf 100644 --- a/apps/readest-app/src/app/reader/components/annotator/ExportMarkdownDialog.tsx +++ b/apps/readest-app/src/app/reader/components/annotator/ExportMarkdownDialog.tsx @@ -64,7 +64,7 @@ const ExportMarkdownDialog: React.FC = ({ {% if annotation.note %} **${_('Note:')}** {{ annotation.note }} {% endif %} -*${_('Time:')} {{ annotation.timestamp | date('%Y-%m-%d %H:%M') }}* +*${_('Page:')} {{ annotation.page }} · ${_('Time:')} {{ annotation.timestamp | date('%Y-%m-%d %H:%M') }}* {% endfor %} --- @@ -122,10 +122,12 @@ const ExportMarkdownDialog: React.FC = ({ chapters: sortedGroups.map((group) => ({ title: group.label || _('Untitled'), annotations: group.booknotes.map((note) => ({ + ...note, text: note.text || '', note: note.note || '', style: note.style, color: note.color, + page: note.page, timestamp: note.updatedAt, })), })), @@ -182,11 +184,19 @@ const ExportMarkdownDialog: React.FC = ({ lines.push(`**${_('Note')}**: ${note.note}`); } - // Add timestamp + let pageStr = ''; + if (exportConfig.includePageNumber && note.page) { + pageStr = `${_('Page: {{number}}', { number: note.page })}`; + } + let timestampStr = ''; if (exportConfig.includeTimestamp && note.updatedAt) { const timestamp = new Date(note.updatedAt).toLocaleString(); + timestampStr = `${_('Time:')} ${timestamp}`; + } + if (pageStr || timestampStr) { lines.push(''); - lines.push(`*${_('Time:')} ${timestamp}*`); + const infoStr = pageStr ? `${pageStr} · ${timestampStr}`.trim() : timestampStr; + lines.push(`*${infoStr}*`); } lines.push(exportConfig.noteSeparator); @@ -322,6 +332,17 @@ const ExportMarkdownDialog: React.FC = ({ {_('Notes')} + +