From 9556e1305fd6b74d838cec11db7493ff1b9ebafa Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Tue, 8 Jul 2025 18:13:47 +0800 Subject: [PATCH] updater: show update notes for new releases (#1552) --- .../public/locales/ar/translation.json | 3 +- .../public/locales/de/translation.json | 3 +- .../public/locales/el/translation.json | 3 +- .../public/locales/es/translation.json | 3 +- .../public/locales/fr/translation.json | 3 +- .../public/locales/hi/translation.json | 3 +- .../public/locales/id/translation.json | 3 +- .../public/locales/it/translation.json | 3 +- .../public/locales/ja/translation.json | 3 +- .../public/locales/ko/translation.json | 3 +- .../public/locales/nl/translation.json | 3 +- .../public/locales/pl/translation.json | 3 +- .../public/locales/pt/translation.json | 3 +- .../public/locales/ru/translation.json | 3 +- .../public/locales/th/translation.json | 3 +- .../public/locales/tr/translation.json | 3 +- .../public/locales/uk/translation.json | 3 +- .../public/locales/vi/translation.json | 3 +- .../public/locales/zh-CN/translation.json | 3 +- .../public/locales/zh-TW/translation.json | 3 +- apps/readest-app/release-notes.json | 2 +- apps/readest-app/src/app/library/page.tsx | 8 +- .../src/app/reader/components/Reader.tsx | 2 +- apps/readest-app/src/app/reader/page.tsx | 6 +- .../src/components/AboutWindow.tsx | 24 +- .../src/components/UpdaterWindow.tsx | 256 ++++++++++++------ apps/readest-app/src/helpers/updater.ts | 47 +++- 27 files changed, 284 insertions(+), 121 deletions(-) diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index 7593bb8a..0a2232a5 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -433,5 +433,6 @@ "Securely store and access your entire reading collection with up to 10 GB of cloud storage.": "احفظ واوصل لمجموعة قراءتك كاملة بـ 10 جيجابايت تخزين سحابي آمن.", "Deleted cloud backup of the book: {{title}}": "تم حذف النسخة الاحتياطية السحابية للكتاب: {{title}}", "Failed to delete cloud backup of the book": "فشل في حذف النسخة الاحتياطية السحابية للكتاب", - "Are you sure to delete the cloud backup of the selected book?": "هل أنت متأكد من حذف النسخة الاحتياطية السحابية للكتاب المحدد؟" + "Are you sure to delete the cloud backup of the selected book?": "هل أنت متأكد من حذف النسخة الاحتياطية السحابية للكتاب المحدد؟", + "What's New in Readest": "ما الجديد في Readest" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index 4ee82a4f..b1b52115 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -417,5 +417,6 @@ "Securely store and access your entire reading collection with up to 10 GB of cloud storage.": "Speichern und greifen Sie sicher auf Ihre gesamte Lesekollektion mit bis zu 10 GB Cloud-Speicher zu.", "Deleted cloud backup of the book: {{title}}": "Gelöschte Cloud-Sicherung des Buches: {{title}}", "Failed to delete cloud backup of the book": "Fehler beim Löschen der Cloud-Sicherung des Buches", - "Are you sure to delete the cloud backup of the selected book?": "Sind Sie sicher, dass Sie die Cloud-Sicherung des ausgewählten Buches löschen möchten?" + "Are you sure to delete the cloud backup of the selected book?": "Sind Sie sicher, dass Sie die Cloud-Sicherung des ausgewählten Buches löschen möchten?", + "What's New in Readest": "Was gibt es Neues in Readest" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index a3cf8278..618dffd5 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -417,5 +417,6 @@ "Securely store and access your entire reading collection with up to 10 GB of cloud storage.": "Αποθηκεύστε και αποκτήστε πρόσβαση στη συλλογή ανάγνωσης με έως 10 GB ασφαλή cloud αποθήκευση.", "Deleted cloud backup of the book: {{title}}": "Διαγράφηκε το αντίγραφο ασφαλείας του βιβλίου: {{title}}", "Failed to delete cloud backup of the book": "Αποτυχία διαγραφής του αντιγράφου ασφαλείας του βιβλίου", - "Are you sure to delete the cloud backup of the selected book?": "Είστε σίγουροι ότι θέλετε να διαγράψετε το αντίγραφο ασφαλείας του επιλεγμένου βιβλίου;" + "Are you sure to delete the cloud backup of the selected book?": "Είστε σίγουροι ότι θέλετε να διαγράψετε το αντίγραφο ασφαλείας του επιλεγμένου βιβλίου;", + "What's New in Readest": "Τι νέο υπάρχει στο Readest" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index 3e2ba74a..c8222b7f 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -421,5 +421,6 @@ "Securely store and access your entire reading collection with up to 10 GB of cloud storage.": "Almacena y accede de forma segura a toda tu colección de lectura con hasta 10 GB de almacenamiento en la nube.", "Deleted cloud backup of the book: {{title}}": "Respaldo en la nube del libro eliminado: {{title}}", "Failed to delete cloud backup of the book": "Fallo al eliminar el respaldo en la nube del libro", - "Are you sure to delete the cloud backup of the selected book?": "¿Estás seguro de que deseas eliminar el respaldo en la nube del libro seleccionado?" + "Are you sure to delete the cloud backup of the selected book?": "¿Estás seguro de que deseas eliminar el respaldo en la nube del libro seleccionado?", + "What's New in Readest": "¿Qué hay de nuevo en Readest" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index 6209de93..4e5383cb 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -421,5 +421,6 @@ "Deleted cloud backup of the book: {{title}}": "Suppression de la sauvegarde cloud du livre : {{title}}", "Failed to delete cloud backup of the book": "Échec de la suppression de la sauvegarde cloud du livre", "Are you sure to delete the cloud backup of the selected book?": "Êtes-vous sûr de vouloir supprimer la sauvegarde cloud du livre sélectionné ?", - "Don't have an account? Sign up": "Vous n'avez pas de compte ? Inscrivez-vous" + "Don't have an account? Sign up": "Vous n'avez pas de compte ? Inscrivez-vous", + "What's New in Readest": "Quoi de neuf dans Readest" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index 81126142..c011ad07 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -417,5 +417,6 @@ "Securely store and access your entire reading collection with up to 10 GB of cloud storage.": "10 GB सुरक्षित क्लाउड स्टोरेज के साथ अपना संपूर्ण पठन संग्रह संग्रहीत करें।", "Deleted cloud backup of the book: {{title}}": "पुस्तक का क्लाउड बैकअप हटाया गया: {{title}}", "Failed to delete cloud backup of the book": "पुस्तक का क्लाउड बैकअप हटाने में विफल", - "Are you sure to delete the cloud backup of the selected book?": "क्या आप सुनिश्चित हैं कि आप चयनित पुस्तक का क्लाउड बैकअप हटाना चाहते हैं?" + "Are you sure to delete the cloud backup of the selected book?": "क्या आप सुनिश्चित हैं कि आप चयनित पुस्तक का क्लाउड बैकअप हटाना चाहते हैं?", + "What's New in Readest": "Readest में नया क्या है" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index 1d195bb2..a6ca91d8 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -413,5 +413,6 @@ "Securely store and access your entire reading collection with up to 10 GB of cloud storage.": "Simpan dan akses koleksi bacaan lengkap dengan aman menggunakan penyimpanan cloud 10 GB.", "Deleted cloud backup of the book: {{title}}": "Cadangan cloud buku dihapus: {{title}}", "Failed to delete cloud backup of the book": "Cadangan cloud buku gagal dihapus", - "Are you sure to delete the cloud backup of the selected book?": "Apakah Anda yakin ingin menghapus cadangan cloud dari buku yang dipilih?" + "Are you sure to delete the cloud backup of the selected book?": "Apakah Anda yakin ingin menghapus cadangan cloud dari buku yang dipilih?", + "What's New in Readest": "Yang Baru di Readest" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index f04e5a68..a1889545 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -421,5 +421,6 @@ "Securely store and access your entire reading collection with up to 10 GB of cloud storage.": "Archivia e accedi in sicurezza alla tua intera collezione di lettura con fino a 10 GB di storage cloud.", "Deleted cloud backup of the book: {{title}}": "Backup cloud del libro eliminato: {{title}}", "Failed to delete cloud backup of the book": "Impossibile eliminare il backup cloud del libro", - "Are you sure to delete the cloud backup of the selected book?": "Sei sicuro di voler eliminare il backup cloud del libro selezionato?" + "Are you sure to delete the cloud backup of the selected book?": "Sei sicuro di voler eliminare il backup cloud del libro selezionato?", + "What's New in Readest": "Novità in Readest" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index d4c04da2..905796cb 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -413,5 +413,6 @@ "Securely store and access your entire reading collection with up to 10 GB of cloud storage.": "最大10GBの安全なクラウドストレージで読書コレクション全体を保存・アクセス。", "Deleted cloud backup of the book: {{title}}": "書籍のクラウドバックアップを削除しました:{{title}}", "Failed to delete cloud backup of the book": "書籍のクラウドバックアップを削除できませんでした", - "Are you sure to delete the cloud backup of the selected book?": "選択した書籍のクラウドバックアップを削除してもよろしいですか?" + "Are you sure to delete the cloud backup of the selected book?": "選択した書籍のクラウドバックアップを削除してもよろしいですか?", + "What's New in Readest": "Readestの新機能" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index c0a0081d..faf341b9 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -413,5 +413,6 @@ "Securely store and access your entire reading collection with up to 10 GB of cloud storage.": "최대 10GB의 안전한 클라우드 저장소로 전체 독서 컬렉션을 저장하고 액세스하세요.", "Deleted cloud backup of the book: {{title}}": "책의 클라우드 백업이 삭제됨: {{title}}", "Failed to delete cloud backup of the book": "책의 클라우드 백업을 삭제하지 못했습니다.", - "Are you sure to delete the cloud backup of the selected book?": "선택한 책의 클라우드 백업을 삭제하시겠습니까?" + "Are you sure to delete the cloud backup of the selected book?": "선택한 책의 클라우드 백업을 삭제하시겠습니까?", + "What's New in Readest": "Readest의 새로운 기능" } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index 104b761d..70f29368 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -417,5 +417,6 @@ "Securely store and access your entire reading collection with up to 10 GB of cloud storage.": "Sla je hele leesverzameling veilig op en benader deze met tot 10 GB cloudopslag.", "Deleted cloud backup of the book: {{title}}": "Verwijderde cloudback-up van het boek: {{title}}", "Failed to delete cloud backup of the book": "Verwijderen van cloudback-up van het boek is mislukt.", - "Are you sure to delete the cloud backup of the selected book?": "Weet je zeker dat je de cloudback-up van het geselecteerde boek wilt verwijderen?" + "Are you sure to delete the cloud backup of the selected book?": "Weet je zeker dat je de cloudback-up van het geselecteerde boek wilt verwijderen?", + "What's New in Readest": "Wat is er nieuw in Readest" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index b55b0342..cef20bb0 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -425,5 +425,6 @@ "Securely store and access your entire reading collection with up to 10 GB of cloud storage.": "Bezpiecznie przechowuj i uzyskuj dostęp do całej kolekcji czytelniczej z do 10 GB pamięci w chmurze.", "Deleted cloud backup of the book: {{title}}": "Usunięto kopię zapasową książki w chmurze: {{title}}", "Failed to delete cloud backup of the book": "Nie udało się usunąć kopii zapasowej książki w chmurze.", - "Are you sure to delete the cloud backup of the selected book?": "Czy na pewno chcesz usunąć kopię zapasową chmury wybranej książki?" + "Are you sure to delete the cloud backup of the selected book?": "Czy na pewno chcesz usunąć kopię zapasową chmury wybranej książki?", + "What's New in Readest": "Co nowego w Readest" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index f563a40a..921a64a2 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -421,5 +421,6 @@ "Securely store and access your entire reading collection with up to 10 GB of cloud storage.": "Armazene e acesse com segurança sua coleção completa de leitura com até 10 GB de armazenamento na nuvem.", "Deleted cloud backup of the book: {{title}}": "Backup na nuvem do livro excluído: {{title}}", "Failed to delete cloud backup of the book": "Falha ao excluir o backup na nuvem do livro.", - "Are you sure to delete the cloud backup of the selected book?": "Tem certeza de que deseja excluir o backup na nuvem do livro selecionado?" + "Are you sure to delete the cloud backup of the selected book?": "Tem certeza de que deseja excluir o backup na nuvem do livro selecionado?", + "What's New in Readest": "Novidades no Readest" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index d137e748..71250768 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -425,5 +425,6 @@ "Securely store and access your entire reading collection with up to 10 GB of cloud storage.": "Безопасно храните и получайте доступ ко всей коллекции чтения с до 10 ГБ облачного хранилища.", "Deleted cloud backup of the book: {{title}}": "Удалено облачное резервное копирование книги: {{title}}", "Failed to delete cloud backup of the book": "Не удалось удалить облачное резервное копирование книги.", - "Are you sure to delete the cloud backup of the selected book?": "Вы уверены, что хотите удалить облачное резервное копирование выбранной книги?" + "Are you sure to delete the cloud backup of the selected book?": "Вы уверены, что хотите удалить облачное резервное копирование выбранной книги?", + "What's New in Readest": "Что нового в Readest" } diff --git a/apps/readest-app/public/locales/th/translation.json b/apps/readest-app/public/locales/th/translation.json index f579645f..b7aa1df4 100644 --- a/apps/readest-app/public/locales/th/translation.json +++ b/apps/readest-app/public/locales/th/translation.json @@ -413,5 +413,6 @@ "Reveal in File Explorer": "แสดงใน File Explorer", "Reveal in Folder": "แสดงในโฟลเดอร์", "Don't have an account? Sign up": "ยังไม่มีบัญชี? สมัครสมาชิก", - "All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "โลกทั้งใบเป็นเวที\nและชายหญิงทั้งปวงเป็นนักแสดง\nมีทางเข้าและทางออก\nแต่ละคนในชีวิตเล่นหลายบท\nการกระทำของเขาแบ่งเป็นเจ็ดวัย\n\n— วิลเลียม เชกสเปียร์" + "All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "โลกทั้งใบเป็นเวที\nและชายหญิงทั้งปวงเป็นนักแสดง\nมีทางเข้าและทางออก\nแต่ละคนในชีวิตเล่นหลายบท\nการกระทำของเขาแบ่งเป็นเจ็ดวัย\n\n— วิลเลียม เชกสเปียร์", + "What's New in Readest": "มีอะไรใหม่ใน Readest" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index 68e66809..2c95c1d3 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -417,5 +417,6 @@ "Securely store and access your entire reading collection with up to 10 GB of cloud storage.": "Tüm okuma koleksiyonunuzu 10 GB'a kadar güvenli bulut depolamayla saklayın ve erişin.", "Deleted cloud backup of the book: {{title}}": "Kitabın bulut yedeği silindi: {{title}}", "Failed to delete cloud backup of the book": "Kitabın bulut yedeğini silme işlemi başarısız oldu.", - "Are you sure to delete the cloud backup of the selected book?": "Seçilen kitabın bulut yedeğini silmek istediğinize emin misiniz?" + "Are you sure to delete the cloud backup of the selected book?": "Seçilen kitabın bulut yedeğini silmek istediğinize emin misiniz?", + "What's New in Readest": "Readest'te Neler Yeni" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index ce86549c..ad1b3bca 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -425,5 +425,6 @@ "Securely store and access your entire reading collection with up to 10 GB of cloud storage.": "Безпечно зберігайте та отримуйте доступ до всієї колекції читання з до 10 ГБ хмарного сховища.", "Deleted cloud backup of the book: {{title}}": "Видалено хмарну резервну копію книги: {{title}}", "Failed to delete cloud backup of the book": "Не вдалося видалити хмарну резервну копію книги.", - "Are you sure to delete the cloud backup of the selected book?": "Ви впевнені, що хочете видалити хмарну резервну копію вибраної книги?" + "Are you sure to delete the cloud backup of the selected book?": "Ви впевнені, що хочете видалити хмарну резервну копію вибраної книги?", + "What's New in Readest": "Що нового в Readest" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index a76d87d4..97128291 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -413,5 +413,6 @@ "Securely store and access your entire reading collection with up to 10 GB of cloud storage.": "Lưu trữ và truy cập an toàn toàn bộ bộ sưu tập đọc với tới 10 GB lưu trữ đám mây.", "Deleted cloud backup of the book: {{title}}": "Đã xóa bản sao lưu đám mây của sách: {{title}}", "Failed to delete cloud backup of the book": "Không thể xóa bản sao lưu đám mây của sách.", - "Are you sure to delete the cloud backup of the selected book?": "Bạn có chắc chắn muốn xóa bản sao lưu đám mây của sách đã chọn không?" + "Are you sure to delete the cloud backup of the selected book?": "Bạn có chắc chắn muốn xóa bản sao lưu đám mây của sách đã chọn không?", + "What's New in Readest": "Những gì mới trong Readest" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index 89c2e1e2..0a723ade 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -413,5 +413,6 @@ "Securely store and access your entire reading collection with up to 10 GB of cloud storage.": "使用多达 10GB 的安全云存储保存和访问您的整个阅读收藏。", "Deleted cloud backup of the book: {{title}}": "已删除书籍云备份:{{title}}", "Failed to delete cloud backup of the book": "无法删除书籍云备份。", - "Are you sure to delete the cloud backup of the selected book?": "您确定要删除所选书籍的云备份吗?" + "Are you sure to delete the cloud backup of the selected book?": "您确定要删除所选书籍的云备份吗?", + "What's New in Readest": "Readest 更新功能" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index 0e0bd25d..f5fd445a 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -413,5 +413,6 @@ "Securely store and access your entire reading collection with up to 10 GB of cloud storage.": "使用多達 10GB 的安全雲端儲存保存和存取您的整個閱讀收藏。", "Deleted cloud backup of the book: {{title}}": "已刪除書籍的雲端備份:{{title}}", "Failed to delete cloud backup of the book": "無法刪除書籍的雲端備份。", - "Are you sure to delete the cloud backup of the selected book?": "您確定要刪除所選書籍的雲端備份嗎?" + "Are you sure to delete the cloud backup of the selected book?": "您確定要刪除所選書籍的雲端備份嗎?", + "What's New in Readest": "Readest 更新功能" } diff --git a/apps/readest-app/release-notes.json b/apps/readest-app/release-notes.json index 34c39e85..d10e8c73 100644 --- a/apps/readest-app/release-notes.json +++ b/apps/readest-app/release-notes.json @@ -8,7 +8,7 @@ "Library: You can now delete only the cloud backup of a book without removing the local copy", "Performance: Fonts in the font list now load only when displayed for faster startup", "Performance: Opening large MOBI books is now significantly faster", - "Layout: Fixed overlay scrollbar display in virtualized book lists", + "Layout: Fixed overlay scrollbar display in longer TOC items list", "Layout: Improved grid cover image height in crop mode", "Layout: Removed unsupported screen orientation lock option on iPad", "Layout: System context menu no longer appears when selecting text on iPad", diff --git a/apps/readest-app/src/app/library/page.tsx b/apps/readest-app/src/app/library/page.tsx index 25bf99bf..0b58074e 100644 --- a/apps/readest-app/src/app/library/page.tsx +++ b/apps/readest-app/src/app/library/page.tsx @@ -16,7 +16,7 @@ import { ProgressPayload } from '@/utils/transfer'; import { throttle } from '@/utils/throttle'; import { parseOpenWithFiles } from '@/helpers/openWith'; import { isTauriAppPlatform } from '@/services/environment'; -import { checkForAppUpdates } from '@/helpers/updater'; +import { checkForAppUpdates, checkAppReleaseNotes } from '@/helpers/updater'; import { FILE_ACCEPT_FORMATS, SUPPORTED_FILE_EXTS } from '@/services/constants'; import { impactFeedback } from '@tauri-apps/plugin-haptics'; import { getCurrentWebview } from '@tauri-apps/api/webview'; @@ -118,6 +118,8 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP const doCheckAppUpdates = async () => { if (appService?.hasUpdater && settings.autoCheckUpdates) { await checkForAppUpdates(_); + } else if (appService?.hasUpdater === false) { + checkAppReleaseNotes(); } }; if (settings.alwaysOnTop) { @@ -125,7 +127,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP } doCheckAppUpdates(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [settings]); + }, [appService?.hasUpdater, settings]); useEffect(() => { if (appService?.isMobileApp) { @@ -683,7 +685,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP /> )} - {appService?.isAndroidApp && } + ); diff --git a/apps/readest-app/src/app/reader/components/Reader.tsx b/apps/readest-app/src/app/reader/components/Reader.tsx index 5d656829..61dc2aee 100644 --- a/apps/readest-app/src/app/reader/components/Reader.tsx +++ b/apps/readest-app/src/app/reader/components/Reader.tsx @@ -118,7 +118,7 @@ const Reader: React.FC<{ ids?: string }> = ({ ids }) => { - {appService?.isAndroidApp && } + diff --git a/apps/readest-app/src/app/reader/page.tsx b/apps/readest-app/src/app/reader/page.tsx index 0c4184bd..4f7f1ffb 100644 --- a/apps/readest-app/src/app/reader/page.tsx +++ b/apps/readest-app/src/app/reader/page.tsx @@ -5,7 +5,7 @@ import { useEnv } from '@/context/EnvContext'; import { useTranslation } from '@/hooks/useTranslation'; import { useOpenWithBooks } from '@/hooks/useOpenWithBooks'; import { useSettingsStore } from '@/store/settingsStore'; -import { checkForAppUpdates } from '@/helpers/updater'; +import { checkForAppUpdates, checkAppReleaseNotes } from '@/helpers/updater'; import Reader from './components/Reader'; // This is only used for the Tauri app in the app router @@ -20,11 +20,13 @@ export default function Page() { const doCheckAppUpdates = async () => { if (appService?.hasUpdater && settings.autoCheckUpdates) { await checkForAppUpdates(_); + } else if (appService?.hasUpdater === false) { + checkAppReleaseNotes(); } }; doCheckAppUpdates(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [settings]); + }, [appService?.hasUpdater, settings.autoCheckUpdates]); return ; } diff --git a/apps/readest-app/src/components/AboutWindow.tsx b/apps/readest-app/src/components/AboutWindow.tsx index 0bc54761..fc573eb0 100644 --- a/apps/readest-app/src/components/AboutWindow.tsx +++ b/apps/readest-app/src/components/AboutWindow.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import Image from 'next/image'; import { useEnv } from '@/context/EnvContext'; import { useTranslation } from '@/hooks/useTranslation'; -import { checkForAppUpdates } from '@/helpers/updater'; +import { checkForAppUpdates, checkAppReleaseNotes } from '@/helpers/updater'; import { parseWebViewVersion } from '@/utils/ua'; import { getAppVersion } from '@/utils/version'; import Dialog from './Dialog'; @@ -50,9 +50,9 @@ export const AboutWindow = () => { const handleCheckUpdate = async () => { setUpdateStatus('checking'); try { - const update = await checkForAppUpdates(_, false); - if (update) { - setIsOpen(false); + const hasUpdate = await checkForAppUpdates(_, false); + if (hasUpdate) { + handleClose(); } else { setUpdateStatus('updated'); } @@ -62,6 +62,15 @@ export const AboutWindow = () => { } }; + const handleShowRecentUpdates = async () => { + const hasNotes = await checkAppReleaseNotes(false); + if (hasNotes) { + handleClose(); + } else { + setUpdateStatus('error'); + } + }; + const handleClose = () => { setIsOpen(false); setUpdateStatus(null); @@ -87,8 +96,11 @@ export const AboutWindow = () => {

- {appService?.hasUpdater && !updateStatus && ( - + {!updateStatus && ( + {_('Check Update')} )} diff --git a/apps/readest-app/src/components/UpdaterWindow.tsx b/apps/readest-app/src/components/UpdaterWindow.tsx index 636e769c..9d2e59ae 100644 --- a/apps/readest-app/src/components/UpdaterWindow.tsx +++ b/apps/readest-app/src/components/UpdaterWindow.tsx @@ -6,7 +6,8 @@ import { useEffect, useState } from 'react'; import { type as osType, arch as osArch } from '@tauri-apps/plugin-os'; import { check, Update } from '@tauri-apps/plugin-updater'; import { relaunch } from '@tauri-apps/plugin-process'; -import { fetch } from '@tauri-apps/plugin-http'; +import { fetch as tauriFetch } from '@tauri-apps/plugin-http'; +import { isTauriAppPlatform } from '@/services/environment'; import { useTranslator } from '@/hooks/useTranslator'; import { useTranslation } from '@/hooks/useTranslation'; import { useSearchParams } from 'next/navigation'; @@ -14,8 +15,10 @@ import { getAppVersion } from '@/utils/version'; import { tauriDownload } from '@/utils/transfer'; import { installPackage } from '@/utils/bridge'; import { getLocale } from '@/utils/misc'; +import { setLastShownReleaseNotesVersion } from '@/helpers/updater'; import { READEST_UPDATER_FILE, READEST_CHANGELOG_FILE } from '@/services/constants'; import Dialog from '@/components/Dialog'; +import Link from './Link'; interface ReleaseNotes { releases: Record< @@ -58,7 +61,15 @@ interface GenericUpdate { downloadAndInstall?(onEvent?: (progress: DownloadEvent) => void): Promise; } -export const UpdaterContent = ({ version }: { version?: string }) => { +export const UpdaterContent = ({ + latestVersion, + lastVersion, + checkUpdate = true, +}: { + latestVersion?: string; + lastVersion?: string; + checkUpdate?: boolean; +}) => { const _ = useTranslation(); const [targetLang, setTargetLang] = useState('EN'); const { translate } = useTranslator({ @@ -69,15 +80,19 @@ export const UpdaterContent = ({ version }: { version?: string }) => { const { appService } = useEnv(); const searchParams = useSearchParams(); const currentVersion = getAppVersion(); - const resolvedVersion = version ?? searchParams?.get('version') ?? ''; + const [newVersion, setNewVersion] = useState( + latestVersion ?? searchParams?.get('latestVersion') ?? '', + ); + const [oldVersion, setOldVersion] = useState( + lastVersion ?? searchParams?.get('lastVersion') ?? '', + ); const [update, setUpdate] = useState(null); - const [isMounted, setIsMounted] = useState(false); - const [newVersion, setNewVersion] = useState(resolvedVersion); const [changelogs, setChangelogs] = useState([]); const [progress, setProgress] = useState(null); const [contentLength, setContentLength] = useState(null); const [isDownloading, setIsDownloading] = useState(false); const [downloaded, setDownloaded] = useState(null); + const [isMounted, setIsMounted] = useState(false); useEffect(() => { setTargetLang(getLocale()); @@ -91,6 +106,7 @@ export const UpdaterContent = ({ version }: { version?: string }) => { } }; const checkAndroidUpdate = async () => { + const fetch = isTauriAppPlatform() ? tauriFetch : window.fetch; const response = await fetch(READEST_UPDATER_FILE); const data = await response.json(); if (semver.gt(data.version, currentVersion)) { @@ -156,30 +172,46 @@ export const UpdaterContent = ({ version }: { version?: string }) => { checkAndroidUpdate(); } }; - checkForUpdates(); + if (appService?.hasUpdater && checkUpdate) { + checkForUpdates(); + } // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [appService?.hasUpdater]); useEffect(() => { - const fetchChangelogs = async ( - fromVersion: string, - toVersion: string, - ): Promise => { + if (latestVersion) { + setNewVersion(latestVersion); + } + if (lastVersion) { + setOldVersion(lastVersion); + } + if (!checkUpdate) { + setUpdate({ + currentVersion, + version: latestVersion, + date: '', + body: '', + } as GenericUpdate); + } + }, [latestVersion, lastVersion, checkUpdate]); + + useEffect(() => { + const fetchChangelogs = async (fromVersion: string): Promise => { try { + const fetch = isTauriAppPlatform() ? tauriFetch : window.fetch; const res = await fetch(READEST_CHANGELOG_FILE); const data: ReleaseNotes = await res.json(); const releases = data.releases; - const entries = Object.entries(releases) - .filter(([ver]) => semver.gt(ver, fromVersion) && semver.lte(ver, toVersion)) - .sort(([a], [b]) => semver.rcompare(a, b)) - .map(([version, info]) => ({ - version, - date: new Date(info.date).toLocaleDateString(), - notes: info.notes, - })); - - return entries; + let entries = Object.entries(releases) + .filter(([ver]) => semver.gt(ver, fromVersion)) + .sort(([a], [b]) => semver.rcompare(a, b)); + entries = entries.length ? entries : Object.entries(releases).slice(0, 3); + return entries.map(([version, info]) => ({ + version, + date: new Date(info.date).toLocaleDateString(), + notes: info.notes, + })); } catch (error) { console.error('Failed to fetch changelog:', error); return []; @@ -193,25 +225,28 @@ export const UpdaterContent = ({ version }: { version?: string }) => { }; const updateChangelogs = async (update: GenericUpdate) => { setNewVersion(update.version); - let changelogs = await fetchChangelogs(currentVersion, update.version); - if (changelogs.length === 0) { + let changelogs = await fetchChangelogs(oldVersion || currentVersion); + if (changelogs.length === 0 && update.date && update.body) { changelogs = [ { version: update.version, - date: new Date(update.date!).toLocaleDateString(), + date: new Date(update.date).toLocaleDateString(), notes: parseNumberedList(update.body ?? ''), }, ]; } - for (const entry of changelogs) { - try { - entry.notes = await translate(entry.notes, { useCache: true }); - } catch (error) { - console.log('Failed to translate changelog:', error); + if (!targetLang.toLowerCase().startsWith('en')) { + for (const entry of changelogs) { + try { + entry.notes = await translate(entry.notes, { useCache: true }); + } catch (error) { + console.log('Failed to translate changelog:', error); + } } } setChangelogs(changelogs); + setLastShownReleaseNotesVersion(newVersion); }; if (update) { updateChangelogs(update); @@ -272,54 +307,94 @@ export const UpdaterContent = ({ version }: { version?: string }) => { Logo
-
-

- {_('A new version of Readest is available!')} -

-

- {_('Readest {{newVersion}} is available (installed version {{currentVersion}}).', { - newVersion, - currentVersion, - })} -

-

{_('Download and install now?')}

+ {checkUpdate ? ( +
+

+ {_('A new version of Readest is available!')} +

+

+ {_('Readest {{newVersion}} is available (installed version {{currentVersion}}).', { + newVersion, + currentVersion, + })} +

+

{_('Download and install now?')}

-
- {progress !== null && ( -
- -

- {progress < 100 - ? _('Downloading {{downloaded}} of {{contentLength}}', { - downloaded: downloaded - ? `${Math.floor(downloaded / 1024 / 1024)} MB` - : '0 MB', - contentLength: contentLength - ? `${Math.floor(contentLength / 1024 / 1024)} MB` - : '0 MB', - }) - : _('Download finished')} -

+
+ {progress !== null && ( +
+ +

+ {progress < 100 + ? _('Downloading {{downloaded}} of {{contentLength}}', { + downloaded: downloaded + ? `${Math.floor(downloaded / 1024 / 1024)} MB` + : '0 MB', + contentLength: contentLength + ? `${Math.floor(contentLength / 1024 / 1024)} MB` + : '0 MB', + }) + : _('Download finished')} +

+
+ )} + +
+
- )} - -
-
-
+ ) : ( +
+
+

+ {_('Version {{version}}', { version: currentVersion })} +

+ + {changelogs.length > 0 && semver.gt(changelogs[0]!.version, currentVersion) ? ( +
+ {(appService?.isIOSApp || appService?.isMacOSApp) && ( + + {_('Check Update')} + + )} + + {appService?.isAndroidApp && ( + + {_('Check Update')} + + )} +
+ ) : ( +
+

{_('Already the latest version')}

+
+ )} +
+
+ )}

{_('Changelog:')}

@@ -351,11 +426,16 @@ export const UpdaterContent = ({ version }: { version?: string }) => { ); }; -export const setUpdaterWindowVisible = (visible: boolean, newVersion?: string) => { +export const setUpdaterWindowVisible = ( + visible: boolean, + latestVersion: string, + lastVersion?: string, + checkUpdate = true, +) => { const dialog = document.getElementById('updater_window'); if (dialog) { const event = new CustomEvent('setDialogVisibility', { - detail: { visible, newVersion }, + detail: { visible, latestVersion, lastVersion, checkUpdate }, }); dialog.dispatchEvent(event); } @@ -363,15 +443,21 @@ export const setUpdaterWindowVisible = (visible: boolean, newVersion?: string) = export const UpdaterWindow = () => { const _ = useTranslation(); - const [newVersion, setNewVersion] = useState(''); + const [latestVersion, setLatestVersion] = useState(''); + const [lastVersion, setLastVersion] = useState(''); + const [checkUpdate, setCheckUpdate] = useState(true); const [isOpen, setIsOpen] = useState(false); useEffect(() => { const handleCustomEvent = (event: CustomEvent) => { - const { visible, newVersion } = event.detail; + const { visible, latestVersion, lastVersion, checkUpdate } = event.detail; setIsOpen(visible); - if (newVersion) { - setNewVersion(newVersion); + setCheckUpdate(checkUpdate); + if (latestVersion) { + setLatestVersion(latestVersion); + } + if (lastVersion) { + setLastVersion(lastVersion); } }; @@ -391,11 +477,17 @@ export const UpdaterWindow = () => { setIsOpen(false)} - boxClassName='sm:!w-[80%] sm:h-auto' + boxClassName='sm:!w-[75%] sm:h-auto sm:!max-h-[85vh] sm:!max-w-2xl' > - + {isOpen && ( + + )} ); }; diff --git a/apps/readest-app/src/helpers/updater.ts b/apps/readest-app/src/helpers/updater.ts index e64cce1c..e00ee43f 100644 --- a/apps/readest-app/src/helpers/updater.ts +++ b/apps/readest-app/src/helpers/updater.ts @@ -5,14 +5,19 @@ import { fetch } from '@tauri-apps/plugin-http'; import { WebviewWindow } from '@tauri-apps/api/webviewWindow'; import { TranslationFunc } from '@/hooks/useTranslation'; import { setUpdaterWindowVisible } from '@/components/UpdaterWindow'; +import { isTauriAppPlatform } from '@/services/environment'; import { getAppVersion } from '@/utils/version'; -import { CHECK_UPDATE_INTERVAL_SEC, READEST_UPDATER_FILE } from '@/services/constants'; +import { + CHECK_UPDATE_INTERVAL_SEC, + READEST_CHANGELOG_FILE, + READEST_UPDATER_FILE, +} from '@/services/constants'; const LAST_CHECK_KEY = 'lastAppUpdateCheck'; -const showUpdateWindow = (newVersion: string) => { +const showUpdateWindow = (latestVersion: string) => { const win = new WebviewWindow('updater', { - url: `/updater?version=${newVersion}`, + url: `/updater?latestVersion=${latestVersion}`, title: 'Software Update', width: 626, height: 406, @@ -29,11 +34,11 @@ const showUpdateWindow = (newVersion: string) => { export const checkForAppUpdates = async ( _: TranslationFunc, - autoCheck = true, + isAutoCheck = true, ): Promise => { const lastCheck = localStorage.getItem(LAST_CHECK_KEY); const now = Date.now(); - if (autoCheck && lastCheck && now - parseInt(lastCheck, 10) < CHECK_UPDATE_INTERVAL_SEC * 1000) + if (isAutoCheck && lastCheck && now - parseInt(lastCheck, 10) < CHECK_UPDATE_INTERVAL_SEC * 1000) return false; localStorage.setItem(LAST_CHECK_KEY, now.toString()); @@ -51,7 +56,7 @@ export const checkForAppUpdates = async ( const data = await response.json(); const isNewer = semver.gt(data.version, getAppVersion()); if (isNewer && ('android-arm64' in data.platforms || 'android-universal' in data.platforms)) { - setUpdaterWindowVisible(true, data.version); + setUpdaterWindowVisible(true, data.version!, getAppVersion()); } return isNewer; } catch (err) { @@ -62,3 +67,33 @@ export const checkForAppUpdates = async ( return false; }; + +const LAST_SHOWN_RELEASE_NOTES_KEY = 'lastShownReleaseNotesVersion'; + +export const setLastShownReleaseNotesVersion = (version: string) => { + localStorage.setItem(LAST_SHOWN_RELEASE_NOTES_KEY, version); +}; + +export const getLastShownReleaseNotesVersion = () => { + return localStorage.getItem(LAST_SHOWN_RELEASE_NOTES_KEY) || ''; +}; + +export const checkAppReleaseNotes = async (isAutoCheck = true) => { + const currentVersion = getAppVersion(); + const lastShownVersion = getLastShownReleaseNotesVersion(); + if ((lastShownVersion && semver.gt(currentVersion, lastShownVersion)) || !isAutoCheck) { + try { + const fetchFunc = isTauriAppPlatform() ? fetch : window.fetch; + const res = await fetchFunc(READEST_CHANGELOG_FILE); + if (res.ok) { + setUpdaterWindowVisible(true, currentVersion, lastShownVersion, false); + return true; + } + } catch (err) { + console.warn('Failed to fetch release notes', err); + } + } else if (!lastShownVersion) { + setLastShownReleaseNotesVersion(currentVersion); + } + return false; +};