Compare commits

...

12 Commits

54 changed files with 966 additions and 192 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ labels: enhancement
assignees: ''
---
**Does your feature request involve difficulty completing a task? Please describe.**
**Does your feature request involve difficulty for you to complete a task? Please describe.**
A clear and concise description of what the problem is. Ex. I think it takes too many steps to [...]
**Describe the solution you'd like**
+12 -91
View File
@@ -2,6 +2,12 @@ name: Release Readest
on:
workflow_dispatch:
inputs:
test_ubuntu_arm:
description: 'Run only ubuntu-22.04-arm'
required: false
default: true
type: boolean
release:
types: [published]
@@ -59,6 +65,9 @@ jobs:
- os: ubuntu-22.04
arch: x86_64
rust_target: x86_64-unknown-linux-gnu
- os: ubuntu-22.04-arm
arch: aarch64
rust_target: aarch64-unknown-linux-gnu
- os: macos-latest
arch: aarch64
rust_target: x86_64-apple-darwin,aarch64-apple-darwin
@@ -73,7 +82,8 @@ jobs:
args: '--target aarch64-pc-windows-msvc --bundles nsis'
runs-on: ${{ matrix.config.os }}
timeout-minutes: 25
timeout-minutes: 30
if: ${{ github.event.inputs.test_arm_only }} != 'true' || matrix.config.os == 'ubuntu-22.04-arm'
steps:
- uses: actions/checkout@v4
@@ -118,7 +128,7 @@ jobs:
run: cp .env.local apps/readest-app/.env.local
- name: install dependencies (ubuntu only)
if: matrix.config.os == 'ubuntu-22.04'
if: contains(matrix.config.os, 'ubuntu')
run: |
sudo apt-get update
sudo apt-get install -y pkg-config libfontconfig-dev libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
@@ -168,95 +178,6 @@ jobs:
echo "Uploading $bin_file to GitHub release"
gh release upload ${{ needs.get-release.outputs.release_tag }} $bin_file --clobber
build-tauri-linux-arm:
if: false
permissions:
contents: write
runs-on: ubuntu-22.04
needs: [get-release]
strategy:
matrix:
arch: [aarch64, armv7l]
include:
- arch: aarch64
cpu: cortex-a72
base_image: https://dietpi.com/downloads/images/DietPi_RPi-ARMv8-Bookworm.img.xz
deb: arm64
appimage: aarch64
- arch: armv7l
cpu: cortex-a53
deb: armhfp
appimage: armhf
base_image: https://dietpi.com/downloads/images/DietPi_RPi-ARMv7-Bookworm.img.xz
steps:
- uses: actions/checkout@v4
- name: initialize git submodules
run: git submodule update --init --recursive
- name: create .env.local file for Next.js
run: |
echo "NEXT_PUBLIC_POSTHOG_KEY=${{ secrets.NEXT_PUBLIC_POSTHOG_KEY }}" >> .env.local
echo "NEXT_PUBLIC_POSTHOG_HOST=${{ secrets.NEXT_PUBLIC_POSTHOG_HOST }}" >> .env.local
echo "NEXT_PUBLIC_SUPABASE_URL=${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}" >> .env.local
echo "NEXT_PUBLIC_SUPABASE_ANON_KEY=${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}" >> .env.local
echo "NEXT_PUBLIC_APP_PLATFORM=tauri" >> .env.local
- name: copy .env.local to apps/readest-app
run: cp .env.local apps/readest-app/.env.local
- name: cache rust build artifacts
uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
- name: build app
uses: pguyot/arm-runner-action@v2.6.5
with:
base_image: ${{ matrix.base_image }}
cpu: ${{ matrix.cpu }}
bind_mount_repository: true
image_additional_mb: 10240
optimize_image: no
#exit_on_fail: no
commands: |
# Prevent Rust from complaining about $HOME not matching eid home
export HOME=/root
# Workaround to CI worker being stuck on Updating crates.io index
export CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
# Install setup prerequisites
apt-get update -y --allow-releaseinfo-change
apt-get autoremove -y
apt-get install -y --no-install-recommends --no-install-suggests pkg-config libfontconfig-dev curl libwebkit2gtk-4.1-dev build-essential libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev patchelf libfuse2 file
curl https://sh.rustup.rs -sSf | sh -s -- -y
. "$HOME/.cargo/env"
curl -fsSL https://deb.nodesource.com/setup_lts.x | bash
apt-get install -y nodejs
npm install -g pnpm@latest-10
# Install frontend dependencies
pnpm install
# Copy pdfjs-dist to public directory
pnpm --filter @readest/readest-app setup-pdfjs
# Build the application
pnpm tauri build
- name: upload arm bundles
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
version=${{ needs.get-release.outputs.release_version }}
gh release upload ${{ needs.get-release.outputs.release_tag }} target/release/bundle/deb/Readest_${version}_${{ matrix.deb }}.deb --clobber
gh release upload ${{ needs.get-release.outputs.release_tag }} target/release/bundle/appimage/Readest_${version}_${{ matrix.appimage }}.AppImage --clobber
update-release:
permissions:
contents: write
Generated
+13
View File
@@ -30,6 +30,7 @@ dependencies = [
"tauri-plugin-os",
"tauri-plugin-process",
"tauri-plugin-shell",
"tauri-plugin-sign-in-with-apple",
"tauri-plugin-single-instance",
"tauri-plugin-updater",
"tauri-plugin-window-state",
@@ -5336,6 +5337,18 @@ dependencies = [
"tokio",
]
[[package]]
name = "tauri-plugin-sign-in-with-apple"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9402eb8a70fdff5127fccc1f1666ebb06b5efc10e0eb656d8a70c798e26142e"
dependencies = [
"serde",
"tauri",
"tauri-plugin",
"thiserror 2.0.11",
]
[[package]]
name = "tauri-plugin-single-instance"
version = "2.2.1"
+2
View File
@@ -150,6 +150,8 @@ For Windows targets, “Build Tools for Visual Studio 2022” (or a higher editi
```bash
pnpm tauri dev
# or start development for the Web version only
pnpm dev-web
```
For Android:
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@readest/readest-app",
"version": "0.9.13",
"version": "0.9.15",
"private": true,
"scripts": {
"dev": "dotenv -e .env.tauri -- next dev",
@@ -165,5 +165,38 @@
"Download Book": "Buch herunterladen",
"Deselect Group": "Gruppe abwählen",
"Select Group": "Gruppe auswählen",
"Keep Screen Awake": "Bildschirm aktiv halten"
"Keep Screen Awake": "Bildschirm aktiv halten",
"Email address": "E-Mail-Adresse",
"Your Password": "Ihr Passwort",
"Your email address": "Ihre E-Mail-Adresse",
"Your password": "Ihr Passwort",
"Sign in": "Anmelden",
"Signing in...": "Anmeldung...",
"Sign in with {{provider}}": "Mit {{provider}} anmelden",
"Already have an account? Sign in": "Haben Sie bereits ein Konto? Anmelden",
"Create a Password": "Passwort erstellen",
"Sign up": "Registrieren",
"Signing up...": "Registrierung...",
"Dont have an account? Sign up": "Kein Konto? Registrieren",
"Check your email for the confirmation link": "Überprüfen Sie Ihre E-Mails auf den Bestätigungslink",
"Signing in ...": "Anmeldung ...",
"Send a magic link email": "Magic-Link-E-Mail senden",
"Check your email for the magic link": "Überprüfen Sie Ihre E-Mails auf den Magic-Link",
"Send reset password instructions": "Anweisungen zum Zurücksetzen des Passworts senden",
"Sending reset instructions ...": "Sende Anweisungen zum Zurücksetzen ...",
"Forgot your password?": "Passwort vergessen?",
"Check your email for the password reset link": "Überprüfen Sie Ihre E-Mails auf den Link zum Zurücksetzen des Passworts",
"New Password": "Neues Passwort",
"Your new password": "Ihr neues Passwort",
"Update password": "Passwort aktualisieren",
"Updating password ...": "Passwort wird aktualisiert ...",
"Your password has been updated": "Ihr Passwort wurde aktualisiert",
"Phone number": "Telefonnummer",
"Your phone number": "Ihre Telefonnummer",
"Token": "Token",
"Your OTP token": "Ihr OTP-Token",
"Verify token": "Token überprüfen",
"Sign in with Google": "Mit Google anmelden",
"Sign in with Apple": "Mit Apple anmelden",
"Sign in with GitHub": "Mit GitHub anmelden"
}
@@ -165,5 +165,38 @@
"Download Book": "Λήψη βιβλίου",
"Deselect Group": "Αποεπιλογή ομάδας",
"Select Group": "Επιλογή ομάδας",
"Keep Screen Awake": "Διατήρηση ενεργού οθόνης"
"Keep Screen Awake": "Διατήρηση ενεργού οθόνης",
"Email address": "Διεύθυνση email",
"Your Password": "Ο κωδικός σας",
"Your email address": "Η διεύθυνση email σας",
"Your password": "Ο κωδικός πρόσβασής σας",
"Sign in": "Σύνδεση",
"Signing in...": "Σύνδεση...",
"Sign in with {{provider}}": "Σύνδεση με {{provider}}",
"Already have an account? Sign in": "Έχετε ήδη λογαριασμό; Συνδεθείτε",
"Create a Password": "Δημιουργία κωδικού πρόσβασης",
"Sign up": "Εγγραφή",
"Signing up...": "Εγγραφή...",
"Dont have an account? Sign up": "Δεν έχετε λογαριασμό; Εγγραφείτε",
"Check your email for the confirmation link": "Ελέγξτε το email σας για τον σύνδεσμο επιβεβαίωσης",
"Signing in ...": "Σύνδεση ...",
"Send a magic link email": "Αποστολή email με μαγικό σύνδεσμο",
"Check your email for the magic link": "Ελέγξτε το email σας για τον μαγικό σύνδεσμο",
"Send reset password instructions": "Αποστολή οδηγιών επαναφοράς κωδικού πρόσβασης",
"Sending reset instructions ...": "Αποστολή οδηγιών επαναφοράς ...",
"Forgot your password?": "Ξεχάσατε τον κωδικό σας;",
"Check your email for the password reset link": "Ελέγξτε το email σας για τον σύνδεσμο επαναφοράς κωδικού",
"New Password": "Νέος κωδικός πρόσβασης",
"Your new password": "Ο νέος σας κωδικός",
"Update password": "Ενημέρωση κωδικού πρόσβασης",
"Updating password ...": "Ενημέρωση κωδικού ...",
"Your password has been updated": "Ο κωδικός πρόσβασής σας ενημερώθηκε",
"Phone number": "Αριθμός τηλεφώνου",
"Your phone number": "Ο αριθμός τηλεφώνου σας",
"Token": "Κωδικός επαλήθευσης",
"Your OTP token": "Ο κωδικός OTP σας",
"Verify token": "Επαλήθευση κωδικού",
"Sign in with Google": "Σύνδεση με Google",
"Sign in with Apple": "Σύνδεση με Apple",
"Sign in with GitHub": "Σύνδεση με GitHub"
}
@@ -165,5 +165,38 @@
"Download Book": "Descargar libro",
"Deselect Group": "Deseleccionar grupo",
"Select Group": "Seleccionar grupo",
"Keep Screen Awake": "Mantener pantalla activa"
"Keep Screen Awake": "Mantener pantalla activa",
"Email address": "Dirección de correo electrónico",
"Your Password": "Tu contraseña",
"Your email address": "Tu dirección de correo electrónico",
"Your password": "Tu contraseña",
"Sign in": "Iniciar sesión",
"Signing in...": "Iniciando sesión...",
"Sign in with {{provider}}": "Iniciar sesión con {{provider}}",
"Already have an account? Sign in": "¿Ya tienes una cuenta? Inicia sesión",
"Create a Password": "Crear una contraseña",
"Sign up": "Registrarse",
"Signing up...": "Registrándose...",
"Dont have an account? Sign up": "¿No tienes una cuenta? Regístrate",
"Check your email for the confirmation link": "Revisa tu correo electrónico para el enlace de confirmación",
"Signing in ...": "Iniciando sesión ...",
"Send a magic link email": "Enviar un enlace mágico por correo",
"Check your email for the magic link": "Revisa tu correo electrónico para el enlace mágico",
"Send reset password instructions": "Enviar instrucciones para restablecer la contraseña",
"Sending reset instructions ...": "Enviando instrucciones de restablecimiento ...",
"Forgot your password?": "¿Olvidaste tu contraseña?",
"Check your email for the password reset link": "Revisa tu correo electrónico para el enlace de restablecimiento de contraseña",
"New Password": "Nueva contraseña",
"Your new password": "Tu nueva contraseña",
"Update password": "Actualizar contraseña",
"Updating password ...": "Actualizando contraseña ...",
"Your password has been updated": "Tu contraseña ha sido actualizada",
"Phone number": "Número de teléfono",
"Your phone number": "Tu número de teléfono",
"Token": "Código de verificación",
"Your OTP token": "Tu código OTP",
"Verify token": "Verificar código",
"Sign in with Google": "Iniciar sesión con Google",
"Sign in with Apple": "Iniciar sesión con Apple",
"Sign in with GitHub": "Iniciar sesión con GitHub"
}
@@ -165,5 +165,38 @@
"Download Book": "Télécharger le livre",
"Deselect Group": "Désélectionner le groupe",
"Select Group": "Sélectionner le groupe",
"Keep Screen Awake": "Maintenir l'écran allumé"
"Keep Screen Awake": "Maintenir l'écran allumé",
"Email address": "Adresse e-mail",
"Your Password": "Votre mot de passe",
"Your email address": "Votre adresse e-mail",
"Your password": "Votre mot de passe",
"Sign in": "Se connecter",
"Signing in...": "Connexion en cours...",
"Sign in with {{provider}}": "Se connecter avec {{provider}}",
"Already have an account? Sign in": "Vous avez déjà un compte ? Connectez-vous",
"Create a Password": "Créer un mot de passe",
"Sign up": "S'inscrire",
"Signing up...": "Inscription en cours...",
"Dont have an account? Sign up": "Vous n'avez pas de compte ? Inscrivez-vous",
"Check your email for the confirmation link": "Vérifiez votre e-mail pour le lien de confirmation",
"Signing in ...": "Connexion en cours ...",
"Send a magic link email": "Envoyer un e-mail avec un lien magique",
"Check your email for the magic link": "Vérifiez votre e-mail pour le lien magique",
"Send reset password instructions": "Envoyer les instructions de réinitialisation du mot de passe",
"Sending reset instructions ...": "Envoi des instructions de réinitialisation ...",
"Forgot your password?": "Mot de passe oublié ?",
"Check your email for the password reset link": "Vérifiez votre e-mail pour le lien de réinitialisation du mot de passe",
"New Password": "Nouveau mot de passe",
"Your new password": "Votre nouveau mot de passe",
"Update password": "Mettre à jour le mot de passe",
"Updating password ...": "Mise à jour du mot de passe ...",
"Your password has been updated": "Votre mot de passe a été mis à jour",
"Phone number": "Numéro de téléphone",
"Your phone number": "Votre numéro de téléphone",
"Token": "Code de vérification",
"Your OTP token": "Votre code OTP",
"Verify token": "Vérifier le code",
"Sign in with Google": "Se connecter avec Google",
"Sign in with Apple": "Se connecter avec Apple",
"Sign in with GitHub": "Se connecter avec GitHub"
}
@@ -165,5 +165,38 @@
"Download Book": "पुस्तक डाउनलोड करें",
"Deselect Group": "समूह का चयन रद्द करें",
"Select Group": "समूह चुनें",
"Keep Screen Awake": "स्क्रीन जागरूक रखें"
"Keep Screen Awake": "स्क्रीन जागरूक रखें",
"Email address": "ईमेल पता",
"Your Password": "आपका पासवर्ड",
"Your email address": "आपका ईमेल पता",
"Your password": "आपका पासवर्ड",
"Sign in": "साइन इन करें",
"Signing in...": "साइन इन हो रहा है...",
"Sign in with {{provider}}": "{{provider}} के साथ साइन इन करें",
"Already have an account? Sign in": "पहले से ही एक खाता है? साइन इन करें",
"Create a Password": "पासवर्ड बनाएं",
"Sign up": "साइन अप करें",
"Signing up...": "साइन अप हो रहा है...",
"Dont have an account? Sign up": "कोई खाता नहीं है? साइन अप करें",
"Check your email for the confirmation link": "पुष्टि लिंक के लिए अपना ईमेल जांचें",
"Signing in ...": "साइन इन हो रहा है ...",
"Send a magic link email": "जादू लिंक ईमेल भेजें",
"Check your email for the magic link": "जादू लिंक के लिए अपना ईमेल जांचें",
"Send reset password instructions": "पासवर्ड रीसेट निर्देश भेजें",
"Sending reset instructions ...": "रीसेट निर्देश भेजे जा रहे हैं ...",
"Forgot your password?": "पासवर्ड भूल गए?",
"Check your email for the password reset link": "पासवर्ड रीसेट लिंक के लिए अपना ईमेल जांचें",
"New Password": "नया पासवर्ड",
"Your new password": "आपका नया पासवर्ड",
"Update password": "पासवर्ड अपडेट करें",
"Updating password ...": "पासवर्ड अपडेट किया जा रहा है ...",
"Your password has been updated": "आपका पासवर्ड अपडेट कर दिया गया है",
"Phone number": "फोन नंबर",
"Your phone number": "आपका फोन नंबर",
"Token": "टोकन",
"Your OTP token": "आपका ओटीपी टोकन",
"Verify token": "टोकन सत्यापित करें",
"Sign in with Google": "Google के साथ साइन इन करें",
"Sign in with Apple": "Apple के साथ साइन इन करें",
"Sign in with GitHub": "GitHub के साथ साइन इन करें"
}
@@ -165,5 +165,38 @@
"Download Book": "Unduh Buku",
"Deselect Group": "Batalkan Pilihan Grup",
"Select Group": "Pilih Grup",
"Keep Screen Awake": "Biarkan Layar Tetap Hidup"
"Keep Screen Awake": "Biarkan Layar Tetap Hidup",
"Email address": "Alamat email",
"Your Password": "Kata sandi Anda",
"Your email address": "Alamat email Anda",
"Your password": "Kata sandi Anda",
"Sign in": "Masuk",
"Signing in...": "Sedang masuk...",
"Sign in with {{provider}}": "Masuk dengan {{provider}}",
"Already have an account? Sign in": "Sudah punya akun? Masuk",
"Create a Password": "Buat kata sandi",
"Sign up": "Daftar",
"Signing up...": "Sedang mendaftar...",
"Dont have an account? Sign up": "Belum punya akun? Daftar",
"Check your email for the confirmation link": "Periksa email Anda untuk tautan konfirmasi",
"Signing in ...": "Sedang masuk ...",
"Send a magic link email": "Kirim email tautan ajaib",
"Check your email for the magic link": "Periksa email Anda untuk tautan ajaib",
"Send reset password instructions": "Kirim instruksi reset kata sandi",
"Sending reset instructions ...": "Mengirim instruksi reset ...",
"Forgot your password?": "Lupa kata sandi Anda?",
"Check your email for the password reset link": "Periksa email Anda untuk tautan reset kata sandi",
"New Password": "Kata sandi baru",
"Your new password": "Kata sandi baru Anda",
"Update password": "Perbarui kata sandi",
"Updating password ...": "Memperbarui kata sandi ...",
"Your password has been updated": "Kata sandi Anda telah diperbarui",
"Phone number": "Nomor telepon",
"Your phone number": "Nomor telepon Anda",
"Token": "Token",
"Your OTP token": "Token OTP Anda",
"Verify token": "Verifikasi token",
"Sign in with Google": "Masuk dengan Google",
"Sign in with Apple": "Masuk dengan Apple",
"Sign in with GitHub": "Masuk dengan GitHub"
}
@@ -165,5 +165,38 @@
"Download Book": "Scarica libro",
"Deselect Group": "Deseleziona gruppo",
"Select Group": "Seleziona gruppo",
"Keep Screen Awake": "Mantieni schermo attivo"
"Keep Screen Awake": "Mantieni schermo attivo",
"Email address": "Indirizzo email",
"Your Password": "La tua password",
"Your email address": "Il tuo indirizzo email",
"Your password": "La tua password",
"Sign in": "Accedi",
"Signing in...": "Accesso in corso...",
"Sign in with {{provider}}": "Accedi con {{provider}}",
"Already have an account? Sign in": "Hai già un account? Accedi",
"Create a Password": "Crea una password",
"Sign up": "Registrati",
"Signing up...": "Registrazione in corso...",
"Dont have an account? Sign up": "Non hai un account? Registrati",
"Check your email for the confirmation link": "Controlla la tua email per il link di conferma",
"Signing in ...": "Accesso in corso ...",
"Send a magic link email": "Invia un'email con link magico",
"Check your email for the magic link": "Controlla la tua email per il link magico",
"Send reset password instructions": "Invia istruzioni per reimpostare la password",
"Sending reset instructions ...": "Invio istruzioni di reimpostazione ...",
"Forgot your password?": "Hai dimenticato la password?",
"Check your email for the password reset link": "Controlla la tua email per il link di reimpostazione della password",
"New Password": "Nuova password",
"Your new password": "La tua nuova password",
"Update password": "Aggiorna password",
"Updating password ...": "Aggiornamento password ...",
"Your password has been updated": "La tua password è stata aggiornata",
"Phone number": "Numero di telefono",
"Your phone number": "Il tuo numero di telefono",
"Token": "Token",
"Your OTP token": "Il tuo token OTP",
"Verify token": "Verifica token",
"Sign in with Google": "Accedi con Google",
"Sign in with Apple": "Accedi con Apple",
"Sign in with GitHub": "Accedi con GitHub"
}
@@ -165,5 +165,38 @@
"Download Book": "書籍をダウンロード",
"Deselect Group": "グループの選択を解除",
"Select Group": "グループを選択",
"Keep Screen Awake": "画面をオンのままにする"
"Keep Screen Awake": "画面をオンのままにする",
"Email address": "メールアドレス",
"Your Password": "あなたのパスワード",
"Your email address": "あなたのメールアドレス",
"Your password": "あなたのパスワード",
"Sign in": "サインイン",
"Signing in...": "サインイン中...",
"Sign in with {{provider}}": "{{provider}}でサインイン",
"Already have an account? Sign in": "すでにアカウントがありますか? サインイン",
"Create a Password": "パスワードを作成",
"Sign up": "サインアップ",
"Signing up...": "サインアップ中...",
"Dont have an account? Sign up": "アカウントをお持ちでないですか? サインアップ",
"Check your email for the confirmation link": "確認リンクのためにメールを確認してください",
"Signing in ...": "サインイン中 ...",
"Send a magic link email": "マジックリンクメールを送信",
"Check your email for the magic link": "マジックリンクのためにメールを確認してください",
"Send reset password instructions": "パスワードリセットの指示を送信",
"Sending reset instructions ...": "リセット指示を送信中 ...",
"Forgot your password?": "パスワードを忘れましたか?",
"Check your email for the password reset link": "パスワードリセットリンクのためにメールを確認してください",
"New Password": "新しいパスワード",
"Your new password": "あなたの新しいパスワード",
"Update password": "パスワードを更新",
"Updating password ...": "パスワード更新中 ...",
"Your password has been updated": "あなたのパスワードは更新されました",
"Phone number": "電話番号",
"Your phone number": "あなたの電話番号",
"Token": "トークン",
"Your OTP token": "あなたのOTPトークン",
"Verify token": "トークンを確認",
"Sign in with Google": "Googleでサインイン",
"Sign in with Apple": "Appleでサインイン",
"Sign in with GitHub": "GitHubでサインイン"
}
@@ -165,5 +165,38 @@
"Download Book": "책 다운로드",
"Deselect Group": "그룹 선택 해제",
"Select Group": "그룹 선택",
"Keep Screen Awake": "화면 켜짐 유지"
"Keep Screen Awake": "화면 켜짐 유지",
"Email address": "이메일 주소",
"Your Password": "비밀번호",
"Your email address": "당신의 이메일 주소",
"Your password": "당신의 비밀번호",
"Sign in": "로그인",
"Signing in...": "로그인 중...",
"Sign in with {{provider}}": "{{provider}}로 로그인",
"Already have an account? Sign in": "이미 계정이 있습니까? 로그인",
"Create a Password": "비밀번호 만들기",
"Sign up": "회원가입",
"Signing up...": "회원가입 중...",
"Dont have an account? Sign up": "계정이 없습니까? 회원가입",
"Check your email for the confirmation link": "확인 링크를 위해 이메일을 확인하세요",
"Signing in ...": "로그인 중 ...",
"Send a magic link email": "마법의 링크 이메일 보내기",
"Check your email for the magic link": "마법의 링크를 위해 이메일을 확인하세요",
"Send reset password instructions": "비밀번호 재설정 지침 보내기",
"Sending reset instructions ...": "재설정 지침 보내는 중 ...",
"Forgot your password?": "비밀번호를 잊으셨나요?",
"Check your email for the password reset link": "비밀번호 재설정 링크를 위해 이메일을 확인하세요",
"New Password": "새로운 비밀번호",
"Your new password": "당신의 새로운 비밀번호",
"Update password": "비밀번호 업데이트",
"Updating password ...": "비밀번호 업데이트 중 ...",
"Your password has been updated": "비밀번호가 업데이트되었습니다",
"Phone number": "전화번호",
"Your phone number": "당신의 전화번호",
"Token": "토큰",
"Your OTP token": "당신의 OTP 토큰",
"Verify token": "토큰 확인",
"Sign in with Google": "Google로 로그인",
"Sign in with Apple": "Apple로 로그인",
"Sign in with GitHub": "GitHub로 로그인"
}
@@ -165,5 +165,38 @@
"Download Book": "Pobierz książkę",
"Deselect Group": "Odznacz grupę",
"Select Group": "Wybierz grupę",
"Keep Screen Awake": "Nie wygaszaj ekranu"
"Keep Screen Awake": "Nie wygaszaj ekranu",
"Email address": "Adres email",
"Your Password": "Twoje hasło",
"Your email address": "Twój adres email",
"Your password": "Twoje hasło",
"Sign in": "Zaloguj się",
"Signing in...": "Logowanie...",
"Sign in with {{provider}}": "Zaloguj się za pomocą {{provider}}",
"Already have an account? Sign in": "Masz już konto? Zaloguj się",
"Create a Password": "Utwórz hasło",
"Sign up": "Zarejestruj się",
"Signing up...": "Rejestracja...",
"Dont have an account? Sign up": "Nie masz konta? Zarejestruj się",
"Check your email for the confirmation link": "Sprawdź swój email, aby uzyskać link potwierdzający",
"Signing in ...": "Logowanie ...",
"Send a magic link email": "Wyślij link magiczny na email",
"Check your email for the magic link": "Sprawdź swój email, aby uzyskać link magiczny",
"Send reset password instructions": "Wyślij instrukcje resetowania hasła",
"Sending reset instructions ...": "Wysyłanie instrukcji resetowania hasła ...",
"Forgot your password?": "Zapomniałeś hasła?",
"Check your email for the password reset link": "Sprawdź swój email, aby uzyskać link resetujący hasło",
"New Password": "Nowe hasło",
"Your new password": "Twoje nowe hasło",
"Update password": "Zaktualizuj hasło",
"Updating password ...": "Aktualizacja hasła ...",
"Your password has been updated": "Twoje hasło zostało zaktualizowane",
"Phone number": "Numer telefonu",
"Your phone number": "Twój numer telefonu",
"Token": "Token",
"Your OTP token": "Twój OTP token",
"Verify token": "Weryfikacja tokena",
"Sign in with Google": "Zaloguj się za pomocą Google",
"Sign in with Apple": "Zaloguj się za pomocą Apple",
"Sign in with GitHub": "Zaloguj się za pomocą GitHub"
}
@@ -165,5 +165,38 @@
"Download Book": "Baixar Livro",
"Deselect Group": "Desmarcar Grupo",
"Select Group": "Selecionar Grupo",
"Keep Screen Awake": "Manter a Tela Ativa"
"Keep Screen Awake": "Manter a Tela Ativa",
"Email address": "Endereço de e-mail",
"Your Password": "Sua senha",
"Your email address": "Seu endereço de e-mail",
"Your password": "Sua senha",
"Sign in": "Entrar",
"Signing in...": "Entrando...",
"Sign in with {{provider}}": "Entrar com {{provider}}",
"Already have an account? Sign in": "Já tem uma conta? Entrar",
"Create a Password": "Criar uma senha",
"Sign up": "Cadastrar-se",
"Signing up...": "Cadastrando...",
"Dont have an account? Sign up": "Não tem uma conta? Cadastre-se",
"Check your email for the confirmation link": "Verifique seu e-mail para o link de confirmação",
"Signing in ...": "Entrando ...",
"Send a magic link email": "Enviar um link mágico por e-mail",
"Check your email for the magic link": "Verifique seu e-mail para o link mágico",
"Send reset password instructions": "Enviar instruções para redefinir a senha",
"Sending reset instructions ...": "Enviando instruções para redefinir a senha ...",
"Forgot your password?": "Esqueceu sua senha?",
"Check your email for the password reset link": "Verifique seu e-mail para o link de redefinição de senha",
"New Password": "Nova senha",
"Your new password": "Sua nova senha",
"Update password": "Atualizar senha",
"Updating password ...": "Atualizando senha ...",
"Your password has been updated": "Sua senha foi atualizada",
"Phone number": "Número de telefone",
"Your phone number": "Seu número de telefone",
"Token": "Token",
"Your OTP token": "Seu token OTP",
"Verify token": "Verificar token",
"Sign in with Google": "Entrar com o Google",
"Sign in with Apple": "Entrar com o Apple",
"Sign in with GitHub": "Entrar com o GitHub"
}
@@ -165,5 +165,38 @@
"Download Book": "Скачать книгу",
"Deselect Group": "Отменить выбор группы",
"Select Group": "Выбрать группу",
"Keep Screen Awake": "Не выключать экран"
"Keep Screen Awake": "Не выключать экран",
"Email address": "Адрес электронной почты",
"Your Password": "Ваш пароль",
"Your email address": "Ваш адрес электронной почты",
"Your password": "Ваш пароль",
"Sign in": "Войти",
"Signing in...": "Вход...",
"Sign in with {{provider}}": "Войти с помощью {{provider}}",
"Already have an account? Sign in": "Уже есть аккаунт? Войдите",
"Create a Password": "Создать пароль",
"Sign up": "Зарегистрироваться",
"Signing up...": "Регистрация...",
"Dont have an account? Sign up": "Нет аккаунта? Зарегистрируйтесь",
"Check your email for the confirmation link": "Проверьте свою почту для подтверждающей ссылки",
"Signing in ...": "Вход ...",
"Send a magic link email": "Отправить магическую ссылку по электронной почте",
"Check your email for the magic link": "Проверьте свою почту для магической ссылки",
"Send reset password instructions": "Отправить инструкции по сбросу пароля",
"Sending reset instructions ...": "Отправка инструкций по сбросу пароля ...",
"Forgot your password?": "Забыли пароль?",
"Check your email for the password reset link": "Проверьте свою почту для ссылки для сброса пароля",
"New Password": "Новый пароль",
"Your new password": "Ваш новый пароль",
"Update password": "Обновить пароль",
"Updating password ...": "Обновление пароля ...",
"Your password has been updated": "Ваш пароль был обновлен",
"Phone number": "Номер телефона",
"Your phone number": "Ваш номер телефона",
"Token": "Токен",
"Your OTP token": "Ваш OTP токен",
"Verify token": "Проверить токен",
"Sign in with Google": "Войти через Google",
"Sign in with Apple": "Войти через Apple",
"Sign in with GitHub": "Войти через GitHub"
}
@@ -165,5 +165,38 @@
"Download Book": "Kitabı İndir",
"Deselect Group": "Grubu Seçme",
"Select Group": "Grup Seç",
"Keep Screen Awake": "Ekranı Açık Tut"
"Keep Screen Awake": "Ekranı Açık Tut",
"Email address": "E-posta adresi",
"Your Password": "Şifreniz",
"Your email address": "E-posta adresiniz",
"Your password": "Şifreniz",
"Sign in": "Giriş yap",
"Signing in...": "Giriş yapılıyor...",
"Sign in with {{provider}}": "{{provider}} ile giriş yap",
"Already have an account? Sign in": "Hesabınız var mı? Giriş yapın",
"Create a Password": "Şifre oluştur",
"Sign up": "Kaydol",
"Signing up...": "Kaydolunuyor...",
"Dont have an account? Sign up": "Hesabınız yok mu? Kaydolun",
"Check your email for the confirmation link": "Onay bağlantısı için e-posta adresinizi kontrol edin",
"Signing in ...": "Giriş yapılıyor...",
"Send a magic link email": "Sihirli bağlantı e-postası gönder",
"Check your email for the magic link": "Sihirli bağlantı için e-posta adresinizi kontrol edin",
"Send reset password instructions": "Şifre sıfırlama talimatları gönder",
"Sending reset instructions ...": "Şifre sıfırlama talimatları gönderiliyor...",
"Forgot your password?": "Şifrenizi mi unuttunuz?",
"Check your email for the password reset link": "Şifre sıfırlama bağlantısı için e-posta adresinizi kontrol edin",
"New Password": "Yeni şifre",
"Your new password": "Yeni şifreniz",
"Update password": "Şifreyi güncelle",
"Updating password ...": "Şifre güncelleniyor...",
"Your password has been updated": "Şifreniz güncellendi",
"Phone number": "Telefon numarası",
"Your phone number": "Telefon numaranız",
"Token": "Token",
"Your OTP token": "OTP token'ınız",
"Verify token": "Token'ı doğrula",
"Sign in with Google": "Google ile giriş yap",
"Sign in with Apple": "Apple ile giriş yap",
"Sign in with GitHub": "GitHub ile giriş yap"
}
@@ -165,5 +165,38 @@
"Download Book": "Завантажити книгу",
"Deselect Group": "Скасувати вибір групи",
"Select Group": "Вибрати групу",
"Keep Screen Awake": "Не вимикати екран"
"Keep Screen Awake": "Не вимикати екран",
"Email address": "Адреса електронної пошти",
"Your Password": "Ваш пароль",
"Your email address": "Ваша електронна адреса",
"Your password": "Ваш пароль",
"Sign in": "Увійти",
"Signing in...": "Вхід...",
"Sign in with {{provider}}": "Увійти через {{provider}}",
"Already have an account? Sign in": "Уже є акаунт? Увійти",
"Create a Password": "Створити пароль",
"Sign up": "Зареєструватися",
"Signing up...": "Реєстрація...",
"Dont have an account? Sign up": "Немає акаунту? Зареєструйтеся",
"Check your email for the confirmation link": "Перевірте свою електронну пошту для підтвердження посилання",
"Signing in ...": "Увійти...",
"Send a magic link email": "Відправити магічне посилання на електронну пошту",
"Check your email for the magic link": "Перевірте свою електронну пошту для магічного посилання",
"Send reset password instructions": "Надіслати інструкції для скидання пароля",
"Sending reset instructions ...": "Надсилаються інструкції для скидання пароля...",
"Forgot your password?": "Забули пароль?",
"Check your email for the password reset link": "Перевірте свою електронну пошту для посилання на скидання пароля",
"New Password": "Новий пароль",
"Your new password": "Ваш новий пароль",
"Update password": "Оновити пароль",
"Updating password ...": "Оновлення пароля...",
"Your password has been updated": "Ваш пароль оновлено",
"Phone number": "Номер телефону",
"Your phone number": "Ваш номер телефону",
"Token": "Токен",
"Your OTP token": "Ваш OTP токен",
"Verify token": "Перевірити токен",
"Sign in with Google": "Увійти через Google",
"Sign in with Apple": "Увійти через Apple",
"Sign in with GitHub": "Увійти через GitHub"
}
@@ -165,5 +165,38 @@
"Download Book": "Tải sách",
"Deselect Group": "Bỏ chọn nhóm",
"Select Group": "Chọn nhóm",
"Keep Screen Awake": "Giữ màn hình luôn bật"
"Keep Screen Awake": "Giữ màn hình luôn bật",
"Email address": "Địa chỉ email",
"Your Password": "Mật khẩu của bạn",
"Your email address": "Địa chỉ email của bạn",
"Your password": "Mật khẩu của bạn",
"Sign in": "Đăng nhập",
"Signing in...": "Đang đăng nhập...",
"Sign in with {{provider}}": "Đăng nhập với {{provider}}",
"Already have an account? Sign in": "Đã có tài khoản? Đăng nhập",
"Create a Password": "Tạo mật khẩu",
"Sign up": "Đăng ký",
"Signing up...": "Đang đăng ký...",
"Dont have an account? Sign up": "Chưa có tài khoản? Đăng ký",
"Check your email for the confirmation link": "Kiểm tra email của bạn để nhận liên kết xác nhận",
"Signing in ...": "Đang đăng nhập ...",
"Send a magic link email": "Gửi email liên kết ma thuật",
"Check your email for the magic link": "Kiểm tra email của bạn để nhận liên kết ma thuật",
"Send reset password instructions": "Gửi hướng dẫn đặt lại mật khẩu",
"Sending reset instructions ...": "Đang gửi hướng dẫn đặt lại mật khẩu ...",
"Forgot your password?": "Quên mật khẩu?",
"Check your email for the password reset link": "Kiểm tra email của bạn để nhận liên kết đặt lại mật khẩu",
"New Password": "Mật khẩu mới",
"Your new password": "Mật khẩu mới của bạn",
"Update password": "Cập nhật mật khẩu",
"Updating password ...": "Đang cập nhật mật khẩu ...",
"Your password has been updated": "Mật khẩu của bạn đã được cập nhật",
"Phone number": "Số điện thoại",
"Your phone number": "Số điện thoại của bạn",
"Token": "Mã token",
"Your OTP token": "Mã OTP của bạn",
"Verify token": "Xác minh mã token",
"Sign in with Google": "Đăng nhập với Google",
"Sign in with Apple": "Đăng nhập với Apple",
"Sign in with GitHub": "Đăng nhập với GitHub"
}
@@ -165,5 +165,38 @@
"Download Book": "下载书籍",
"Deselect Group": "取消选择分组",
"Select Group": "选择分组",
"Keep Screen Awake": "保持屏幕常亮"
"Keep Screen Awake": "保持屏幕常亮",
"Email address": "电子邮件地址",
"Your Password": "您的密码",
"Your email address": "您的电子邮件地址",
"Your password": "您的密码",
"Sign in": "登录",
"Signing in...": "正在登录...",
"Sign in with {{provider}}": "使用 {{provider}} 登录",
"Already have an account? Sign in": "已经有账号?登录",
"Create a Password": "创建密码",
"Sign up": "注册",
"Signing up...": "正在注册...",
"Dont have an account? Sign up": "没有账号?注册",
"Check your email for the confirmation link": "检查您的电子邮件以获取确认链接",
"Signing in ...": "正在登录...",
"Send a magic link email": "发送魔法链接邮件",
"Check your email for the magic link": "检查您的电子邮件以获取魔法链接",
"Send reset password instructions": "发送重置密码说明",
"Sending reset instructions ...": "正在发送重置密码说明...",
"Forgot your password?": "忘记密码?",
"Check your email for the password reset link": "检查您的电子邮件以获取密码重置链接",
"New Password": "新密码",
"Your new password": "您的新密码",
"Update password": "更新密码",
"Updating password ...": "正在更新密码...",
"Your password has been updated": "您的密码已更新",
"Phone number": "电话号码",
"Your phone number": "您的电话号码",
"Token": "令牌",
"Your OTP token": "您的 OTP 令牌",
"Verify token": "验证令牌",
"Sign in with Google": "使用 Google 登录",
"Sign in with Apple": "使用 Apple 登录",
"Sign in with GitHub": "使用 GitHub 登录"
}
@@ -165,5 +165,38 @@
"Download Book": "下載書籍",
"Deselect Group": "取消選擇組",
"Select Group": "選擇組",
"Keep Screen Awake": "保持屏幕常亮"
"Keep Screen Awake": "保持屏幕常亮",
"Email address": "電子郵件地址",
"Your Password": "您的密碼",
"Your email address": "您的電子郵件地址",
"Your password": "您的密碼",
"Sign in": "登入",
"Signing in...": "正在登入...",
"Sign in with {{provider}}": "使用 {{provider}} 登入",
"Already have an account? Sign in": "已經有帳號?登入",
"Create a Password": "創建密碼",
"Sign up": "註冊",
"Signing up...": "正在註冊...",
"Dont have an account? Sign up": "沒有帳號?註冊",
"Check your email for the confirmation link": "檢查您的電子郵件以獲取確認連結",
"Signing in ...": "正在登入...",
"Send a magic link email": "發送魔法連結郵件",
"Check your email for the magic link": "檢查您的電子郵件以獲取魔法連結",
"Send reset password instructions": "發送重設密碼指示",
"Sending reset instructions ...": "正在發送重設密碼指示...",
"Forgot your password?": "忘記密碼?",
"Check your email for the password reset link": "檢查您的電子郵件以獲取密碼重設連結",
"New Password": "新密碼",
"Your new password": "您的新密碼",
"Update password": "更新密碼",
"Updating password ...": "正在更新密碼...",
"Your password has been updated": "您的密碼已更新",
"Phone number": "電話號碼",
"Your phone number": "您的電話號碼",
"Token": "令牌",
"Your OTP token": "您的 OTP 令牌",
"Verify token": "驗證令牌",
"Sign in with Google": "使用 Google 登入",
"Sign in with Apple": "使用 Apple 登入",
"Sign in with GitHub": "使用 GitHub 登入"
}
+8
View File
@@ -1,5 +1,13 @@
{
"releases": {
"0.9.15": {
"date": "2025-02-21",
"notes": [
"Fix hardcoded font color from calibre",
"Fix scroll to current TOC item when toggling sidebar",
"Various fixes and enhancements on iOS platform"
]
},
"0.9.13": {
"date": "2025-02-19",
"notes": [
+1
View File
@@ -44,6 +44,7 @@ tauri-plugin-process = "2"
tauri-plugin-oauth = "2"
tauri-plugin-opener = "2.2.2"
tauri-plugin-deep-link = "2"
tauri-plugin-sign-in-with-apple = "1.0.2"
[patch.crates-io]
tauri = { path = "../../../packages/tauri/crates/tauri" }
@@ -68,6 +68,7 @@
"process:allow-restart",
"oauth:allow-start",
"oauth:allow-cancel",
"sign-in-with-apple:default",
"opener:default",
"deep-link:default"
]
+3
View File
@@ -129,6 +129,9 @@ pub fn run() {
#[cfg(target_os = "macos")]
let builder = builder.plugin(traffic_light_plugin::init());
#[cfg(target_os = "ios")]
let builder = builder.plugin(tauri_plugin_sign_in_with_apple::init());
builder
.setup(|#[allow(unused_variables)] app| {
#[cfg(desktop)]
+109 -14
View File
@@ -15,12 +15,13 @@ import { supabase } from '@/utils/supabase';
import { useTheme } from '@/hooks/useTheme';
import { useEnv } from '@/context/EnvContext';
import { useSettingsStore } from '@/store/settingsStore';
import { useTranslation } from '@/hooks/useTranslation';
import { isTauriAppPlatform } from '@/services/environment';
import { onOpenUrl } from '@tauri-apps/plugin-deep-link';
import { start, cancel, onUrl, onInvalidUrl } from '@fabianlars/tauri-plugin-oauth';
import { openUrl } from '@tauri-apps/plugin-opener';
import { handleAuthCallback } from '@/helpers/auth';
import { getOSPlatform } from '@/utils/misc';
import { getAppleIdAuth, Scope } from './utils/appleIdAuth';
type OAuthProvider = 'google' | 'apple' | 'azure' | 'github';
@@ -55,22 +56,42 @@ const ProviderLogin: React.FC<ProviderLoginProp> = ({ provider, handleSignIn, Ic
};
export default function AuthPage() {
const _ = useTranslation();
const router = useRouter();
const { login } = useAuth();
const { envConfig } = useEnv();
const { envConfig, appService } = useEnv();
const { isDarkMode } = useTheme();
const { settings, setSettings, saveSettings } = useSettingsStore();
const [port, setPort] = useState<number | null>(null);
const isOAuthServerRunning = useRef(false);
const [isMounted, setIsMounted] = useState(false);
const osPlatform = getOSPlatform();
const getTauriRedirectTo = () => {
return process.env.NODE_ENV === 'production'
? ['android', 'ios'].includes(osPlatform)
return process.env.NODE_ENV === 'production' || appService?.isMobile
? appService?.isMobile
? WEB_AUTH_CALLBACK
: DEEPLINK_CALLBACK
: `http://localhost:${port}`;
: `http://localhost:${port}`; // only for development env on Desktop
};
const tauriSignInApple = async () => {
if (!supabase) {
throw new Error('No backend connected');
}
supabase.auth.signOut();
const request = {
scope: ['fullName', 'email'] as Scope[],
};
const appleAuthResponse = await getAppleIdAuth(request);
if (appleAuthResponse.identityToken) {
const { error } = await supabase.auth.signInWithIdToken({
provider: 'apple',
token: appleAuthResponse.identityToken,
});
if (error) {
console.error('Authentication error:', error);
}
}
};
const tauriSignIn = async (provider: OAuthProvider) => {
@@ -110,7 +131,7 @@ export default function AuthPage() {
const startTauriOAuth = async () => {
try {
if (process.env.NODE_ENV === 'production') {
if (process.env.NODE_ENV === 'production' || appService?.isMobile) {
const { getCurrentWindow } = await import('@tauri-apps/api/window');
const currentWindow = getCurrentWindow();
currentWindow.listen('single-instance', ({ event, payload }) => {
@@ -159,6 +180,68 @@ export default function AuthPage() {
router.back();
};
const getAuthLocalization = () => {
return {
variables: {
sign_in: {
email_label: _('Email address'),
password_label: _('Your Password'),
email_input_placeholder: _('Your email address'),
password_input_placeholder: _('Your password'),
button_label: _('Sign in'),
loading_button_label: _('Signing in...'),
social_provider_text: _('Sign in with {{provider}}'),
link_text: _('Already have an account? Sign in'),
},
sign_up: {
email_label: _('Email address'),
password_label: _('Create a Password'),
email_input_placeholder: _('Your email address'),
password_input_placeholder: _('Your password'),
button_label: _('Sign up'),
loading_button_label: _('Signing up...'),
social_provider_text: _('Sign in with {{provider}}'),
link_text: _('Dont have an account? Sign up'),
confirmation_text: _('Check your email for the confirmation link'),
},
magic_link: {
email_input_label: _('Email address'),
email_input_placeholder: _('Your email address'),
button_label: _('Sign in'),
loading_button_label: _('Signing in ...'),
link_text: _('Send a magic link email'),
confirmation_text: _('Check your email for the magic link'),
},
forgotten_password: {
email_label: _('Email address'),
password_label: _('Your Password'),
email_input_placeholder: _('Your email address'),
button_label: _('Send reset password instructions'),
loading_button_label: _('Sending reset instructions ...'),
link_text: _('Forgot your password?'),
confirmation_text: _('Check your email for the password reset link'),
},
update_password: {
password_label: _('New Password'),
password_input_placeholder: _('Your new password'),
button_label: _('Update password'),
loading_button_label: _('Updating password ...'),
confirmation_text: _('Your password has been updated'),
},
verify_otp: {
email_input_label: _('Email address'),
email_input_placeholder: _('Your email address'),
phone_input_label: _('Phone number'),
phone_input_placeholder: _('Your phone number'),
token_input_label: _('Token'),
token_input_placeholder: _('Your OTP token'),
button_label: _('Verify token'),
loading_button_label: _('Signing in ...'),
},
},
};
};
useEffect(() => {
if (!isTauriAppPlatform()) return;
if (isOAuthServerRunning.current) return;
@@ -199,10 +282,20 @@ export default function AuthPage() {
// For tauri app production, use deeplink to handle the OAuth callback
// For web app, use the built-in OAuth callback page /auth/callback
return isTauriAppPlatform() ? (
<div className='flex pt-11'>
<div
className={clsx(
'mt-6 flex',
appService?.hasSafeAreaInset && 'pt-[env(safe-area-inset-top)]',
appService?.hasTrafficLight && 'pt-11',
)}
>
<button
onClick={handleGoBack}
className='btn btn-ghost fixed left-3 top-11 h-8 min-h-8 w-8 p-0'
className={clsx(
'btn btn-ghost fixed left-4 h-8 min-h-8 w-8 p-0',
appService?.hasSafeAreaInset && 'top-[calc(env(safe-area-inset-top)+16px)]',
appService?.hasTrafficLight && 'top-11',
)}
>
<IoArrowBack className='text-base-content' />
</button>
@@ -211,19 +304,19 @@ export default function AuthPage() {
provider='google'
handleSignIn={tauriSignIn}
Icon={FcGoogle}
label='Sign in with Google'
label={_('Sign in with Google')}
/>
<ProviderLogin
provider='apple'
handleSignIn={tauriSignIn}
handleSignIn={appService?.isIOSApp ? tauriSignInApple : tauriSignIn}
Icon={FaApple}
label='Sign in with Apple'
label={_('Sign in with Apple')}
/>
<ProviderLogin
provider='github'
handleSignIn={tauriSignIn}
Icon={FaGithub}
label='Sign in with GitHub'
label={_('Sign in with GitHub')}
/>
<hr className='my-3 mt-6 w-64 border-t border-gray-200' />
<Auth
@@ -233,6 +326,7 @@ export default function AuthPage() {
magicLink={true}
providers={[]}
redirectTo={getTauriRedirectTo()}
localization={getAuthLocalization()}
/>
</div>
</div>
@@ -240,7 +334,7 @@ export default function AuthPage() {
<div style={{ maxWidth: '420px', margin: 'auto', padding: '2rem', paddingTop: '4rem' }}>
<button
onClick={handleGoBack}
className='btn btn-ghost fixed left-10 top-6 h-8 min-h-8 w-8 p-0'
className='btn btn-ghost fixed left-6 top-6 h-8 min-h-8 w-8 p-0'
>
<IoArrowBack className='text-base-content' />
</button>
@@ -251,6 +345,7 @@ export default function AuthPage() {
magicLink={true}
providers={['google', 'apple', 'github']}
redirectTo='/auth/callback'
localization={getAuthLocalization()}
/>
</div>
);
@@ -0,0 +1,34 @@
import { invoke } from '@tauri-apps/api/core';
export type Scope = 'fullName' | 'email';
export interface AppleIDAuthorizationRequest {
scope: Scope[];
nonce?: string;
state?: string;
}
export interface AppleIDAuthorizationResponse {
// usually not null
userIdentifier: string | null;
givenName: string | null;
familyName: string | null;
email: string | null;
authorizationCode: string;
identityToken: string | null;
state: string | null;
}
export async function getAppleIdAuth(
request: AppleIDAuthorizationRequest,
): Promise<AppleIDAuthorizationResponse> {
const result = await invoke<AppleIDAuthorizationResponse>(
'plugin:sign-in-with-apple|get_apple_id_credential',
{
payload: request,
},
);
return result;
}
@@ -114,14 +114,16 @@ const Bookshelf: React.FC<BookshelfProps> = ({
};
const confirmDelete = async () => {
for (const id of selectedBooks) {
const book = libraryBooks.find((b) => b.hash === id || b.groupId === id);
if (book) {
await handleBookDelete(book);
selectedBooks.forEach((id) => {
for (const book of libraryBooks.filter((book) => book.hash === id || book.groupId === id)) {
if (book && !book.deletedAt) {
handleBookDelete(book);
}
}
}
});
setSelectedBooks([]);
setShowDeleteAlert(false);
setShowSelectModeActions(true);
};
const deleteSelectedBooks = () => {
+3 -4
View File
@@ -253,7 +253,7 @@ const LibraryPage = () => {
});
} catch (err) {
if (err instanceof Error) {
if (err.message.includes('Not authenticated')) {
if (err.message.includes('Not authenticated') && settings.keepLogin) {
navigateToLogin(router);
return;
} else if (err.message.includes('Insufficient storage quota')) {
@@ -323,8 +323,7 @@ const LibraryPage = () => {
let files;
if (isTauriAppPlatform()) {
const { type } = await import('@tauri-apps/plugin-os');
if (['android', 'ios'].includes(type())) {
if (appService?.isMobile) {
files = (await selectFilesWeb()) as [File];
} else {
files = (await selectFilesTauri()) as [string];
@@ -382,7 +381,7 @@ const LibraryPage = () => {
</div>
)}
{libraryLoaded &&
(libraryBooks.length > 0 ? (
(libraryBooks.some((book) => !book.deletedAt) ? (
<div
ref={containerRef}
className={clsx(
@@ -31,7 +31,7 @@ import DeepLPopup from './DeepLPopup';
const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const _ = useTranslation();
const { envConfig } = useEnv();
const { envConfig, appService } = useEnv();
const { settings } = useSettingsStore();
const { getConfig, saveConfig, getBookData, updateBooknotes } = useBookDataStore();
const { getProgress, getView, getViewsById, getViewSettings } = useReaderStore();
@@ -143,7 +143,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => {
// Disable the default context menu on mobile devices,
// although it should but doesn't work on iOS
if (osPlatform === 'android' || osPlatform === 'ios') {
if (appService?.isMobile) {
detail.doc?.addEventListener('contextmenu', (event: Event) => {
event.preventDefault();
event.stopPropagation();
@@ -3,6 +3,7 @@ import React, { useEffect, useRef } from 'react';
import { useNotebookStore } from '@/store/notebookStore';
import { useTranslation } from '@/hooks/useTranslation';
import { TextSelection } from '@/utils/sel';
import { md5Fingerprint } from '@/utils/md5';
import { BookNote } from '@/types/book';
import useShortcuts from '@/hooks/useShortcuts';
@@ -13,7 +14,14 @@ interface NoteEditorProps {
const NoteEditor: React.FC<NoteEditorProps> = ({ onSave, onEdit }) => {
const _ = useTranslation();
const { notebookNewAnnotation, notebookEditAnnotation, setNotebookNewAnnotation, setNotebookEditAnnotation } = useNotebookStore();
const {
notebookNewAnnotation,
notebookEditAnnotation,
setNotebookNewAnnotation,
setNotebookEditAnnotation,
saveNotebookAnnotationDraft,
getNotebookAnnotationDraft,
} = useNotebookStore();
const editorRef = useRef<HTMLTextAreaElement>(null);
const [note, setNote] = React.useState('');
@@ -21,13 +29,30 @@ const NoteEditor: React.FC<NoteEditorProps> = ({ onSave, onEdit }) => {
if (editorRef.current) {
editorRef.current.focus();
}
}, []);
}, [editorRef]);
useEffect(() => {
if (notebookEditAnnotation) {
setNote(notebookEditAnnotation.note);
if (editorRef.current) {
editorRef.current.value = notebookEditAnnotation.note;
editorRef.current.focus();
adjustHeight();
}
} else if (notebookNewAnnotation) {
const noteText = getAnnotationText();
if (noteText) {
const draftNote = getNotebookAnnotationDraft(md5Fingerprint(noteText)) || '';
setNote(draftNote);
if (editorRef.current) {
editorRef.current.value = draftNote;
editorRef.current.focus();
adjustHeight();
}
}
}
}, [notebookEditAnnotation]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [notebookNewAnnotation, notebookEditAnnotation]);
const adjustHeight = () => {
if (editorRef.current) {
@@ -36,11 +61,24 @@ const NoteEditor: React.FC<NoteEditorProps> = ({ onSave, onEdit }) => {
}
};
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const getAnnotationText = () => {
return notebookEditAnnotation?.text || notebookNewAnnotation?.text || '';
};
const handleOnChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
adjustHeight();
setNote(e.currentTarget.value);
};
const handleOnBlur = () => {
if (editorRef.current && editorRef.current.value) {
const noteText = getAnnotationText();
if (noteText) {
saveNotebookAnnotationDraft(md5Fingerprint(noteText), editorRef.current.value);
}
}
};
const handleSaveNote = () => {
if (editorRef.current && notebookNewAnnotation) {
onSave(notebookNewAnnotation, editorRef.current.value);
@@ -58,48 +96,52 @@ const NoteEditor: React.FC<NoteEditorProps> = ({ onSave, onEdit }) => {
},
onCloseNote: () => {
if (notebookNewAnnotation) {
setNotebookNewAnnotation(null)
} else if (notebookEditAnnotation) {
setNotebookEditAnnotation(null)
setNotebookNewAnnotation(null);
}
}
})
if (notebookEditAnnotation) {
setNotebookEditAnnotation(null);
}
},
});
return (
<div className='note-editor-container bg-base-100 mt-2 rounded-md p-2'>
<div className='content note-editor-container bg-base-100 mt-2 rounded-md p-2'>
<div className='flex w-full justify-between space-x-2'>
<div className='settings-content relative w-full'>
<div className='relative w-full'>
<textarea
className={clsx(
'note-editor textarea textarea-ghost min-h-[1em] resize-none !outline-none',
'inset-0 w-full rounded-none border-0 bg-transparent p-0 leading-normal',
'text-sm',
'inset-0 w-full rounded-none border-0 bg-transparent p-0',
'content font-size-sm',
)}
ref={editorRef}
value={note}
rows={1}
spellCheck={false}
onChange={handleChange}
onChange={handleOnChange}
onBlur={handleOnBlur}
placeholder={_('Add your notes here...')}
></textarea>
</div>
</div>
<div className='flex items-start pt-2'>
<div className='mr-2 min-h-full self-stretch border-l-2 border-gray-300'></div>
<div className='content font-size-sm line-clamp-3 py-2'>
<span className='content font-size-xs inline text-gray-500'>{getAnnotationText()}</span>
</div>
</div>
<div className='flex justify-end p-2'>
<button
className={clsx(
'btn btn-ghost settings-content hover:bg-transparent',
'content btn btn-ghost font-size-sm hover:bg-transparent',
'flex h-[1.3em] min-h-[1.3em] items-end p-0',
editorRef.current && editorRef.current.value ? '' : 'btn-disabled !bg-opacity-0',
)}
onClick={handleSaveNote}
>
<div className='pr-1 align-bottom text-xs text-blue-400'>{_('Save')}</div>
<div className='font-size-sm pr-1 align-bottom text-blue-500'>{_('Save')}</div>
</button>
</div>
<div className='flex items-start pt-2'>
<div className='mr-2 min-h-full self-stretch border-l-2 border-gray-300'></div>
<div className='note-citation settings-content line-clamp-3 text-sm'>
{notebookNewAnnotation?.text || notebookEditAnnotation?.text}
</div>
</div>
</div>
);
};
@@ -77,6 +77,8 @@ const Notebook: React.FC = ({}) => {
event.preventDefault();
event.stopPropagation();
setNotebookVisible(false);
setNotebookNewAnnotation(null);
setNotebookEditAnnotation(null);
};
const handleDragMove = (e: MouseEvent) => {
@@ -151,6 +153,7 @@ const Notebook: React.FC = ({}) => {
<div
className={clsx(
'notebook-container bg-base-200 right-0 z-20 min-w-60 select-none',
'font-sans text-base font-normal sm:text-sm',
appService?.isIOSApp ? 'h-[100vh]' : 'h-full',
appService?.hasSafeAreaInset && 'pt-[env(safe-area-inset-top)]',
appService?.hasRoundedWindow && 'rounded-window-top-right rounded-window-bottom-right',
@@ -180,7 +183,11 @@ const Notebook: React.FC = ({}) => {
handleTogglePin={handleTogglePin}
/>
<div className='max-h-[calc(100vh-44px)] overflow-y-auto px-3'>
<div>{excerptNotes.length > 0 && <p className='pt-1 text-sm'>{_('Excerpts')}</p>}</div>
<div>
{excerptNotes.length > 0 && (
<p className='content font-size-base pt-1'>{_('Excerpts')}</p>
)}
</div>
<ul className=''>
{excerptNotes.map((item, index) => (
<li key={`${index}-${item.id}`} className='my-2'>
@@ -189,7 +196,7 @@ const Notebook: React.FC = ({}) => {
className='collapse-arrow border-base-300 bg-base-100 collapse border'
>
<div
className='collapse-title h-9 min-h-9 p-2 pr-8 text-sm font-medium'
className='collapse-title font-size-sm h-9 min-h-9 p-2 pr-8 font-medium'
style={
{
'--top-override': '1.2rem',
@@ -199,11 +206,11 @@ const Notebook: React.FC = ({}) => {
>
<p className='line-clamp-1'>{item.text || `Excerpt ${index + 1}`}</p>
</div>
<div className='collapse-content select-text px-3 pb-0 text-xs'>
<div className='collapse-content font-size-xs select-text px-3 pb-0'>
<p className='hyphens-auto text-justify'>{item.text}</p>
<div className='flex justify-end'>
<div
className='cursor-pointer align-bottom text-xs text-red-400'
className='font-size-xs cursor-pointer align-bottom text-red-500 hover:text-red-600'
onClick={handleEditNote.bind(null, item, true)}
>
{_('Delete')}
@@ -216,13 +223,13 @@ const Notebook: React.FC = ({}) => {
</ul>
<div>
{(notebookNewAnnotation || annotationNotes.length > 0) && (
<p className='pt-1 text-sm'>{_('Notes')}</p>
<p className='content font-size-base pt-1'>{_('Notes')}</p>
)}
</div>
{(notebookNewAnnotation || notebookEditAnnotation) && (
<NoteEditor onSave={handleSaveNote} onEdit={(item) => handleEditNote(item, false)} />
)}
<ul className=''>
<ul>
{annotationNotes.map((item, index) => (
<BooknoteItem key={`${index}-${item.cfi}`} bookKey={sideBarBookKey} item={item} />
))}
@@ -21,7 +21,7 @@ const DialogMenu: React.FC<DialogMenuProps> = ({ toggleDropdown }) => {
return (
<div
tabIndex={0}
className='dropdown-content dropdown-right no-triangle border-base-200 z-20 mt-1 w-44 border shadow-2xl'
className='dropdown-content dropdown-right no-triangle border-base-200 z-20 mt-1 border shadow-2xl'
>
<button
className='hover:bg-base-200 text-base-content flex w-full items-center justify-between rounded-md p-2'
@@ -37,7 +37,7 @@ const DialogMenu: React.FC<DialogMenuProps> = ({ toggleDropdown }) => {
isFontLayoutSettingsGlobal ? _('Apply to All Books') : _('Apply to This Book')
}
>
<span className='ml-2'>{_('Global Settings')}</span>
<span className='ml-2 whitespace-nowrap'>{_('Global Settings')}</span>
</div>
</div>
</button>
@@ -135,7 +135,10 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
>
<div className='relative p-1'>
<textarea
className='textarea textarea-ghost h-48 w-full border-0 p-3 text-base !outline-none sm:text-sm'
className={clsx(
'textarea textarea-ghost h-48 w-full border-0 p-3 text-base !outline-none sm:text-sm',
'placeholder:text-base-content/70',
)}
placeholder={_('Enter your custom CSS here...')}
spellCheck='false'
value={draftStylesheet}
@@ -64,7 +64,7 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
isOpen={true}
onClose={handleClose}
className='modal-open'
boxClassName='sm:w-1/2 sm:min-w-[480px]'
boxClassName='sm:w-1/2 sm:min-w-[480px] sm:h-[65%]'
header={
<div className='flex w-full items-center justify-between'>
<button
@@ -65,7 +65,7 @@ const BooknoteItem: React.FC<BooknoteItemProps> = ({ bookKey, item }) => {
<li
ref={viewRef}
className={clsx(
'border-base-300 group relative my-2 cursor-pointer rounded-lg p-2 text-sm',
'border-base-300 content group relative my-2 cursor-pointer rounded-lg p-2',
isCurrent ? 'bg-base-300/85 hover:bg-base-300' : 'hover:bg-base-300/55 bg-base-100',
'transition-all duration-300 ease-in-out',
)}
@@ -81,16 +81,16 @@ const BooknoteItem: React.FC<BooknoteItemProps> = ({ bookKey, item }) => {
} as React.CSSProperties
}
>
{item.note && <span className='line-clamp-3 font-normal'>{item.note}</span>}
{item.note && <span className='content font-size-sm font-normal'>{item.note}</span>}
<div className='flex items-start'>
{item.note && (
<div className='my-1 mr-2 min-h-full self-stretch border-l-2 border-gray-300'></div>
)}
<div className='line-clamp-3'>
<div className={clsx('content font-size-sm line-clamp-3', item.note && 'py-2')}>
<span
className={clsx(
'inline',
item.note && 'text-xs text-gray-500',
item.note && 'content font-size-xs text-gray-500',
(item.style === 'underline' || item.style === 'squiggly') &&
'underline decoration-2',
item.style === 'highlight' && `bg-${item.color}-500 bg-opacity-40`,
@@ -120,15 +120,16 @@ const BooknoteItem: React.FC<BooknoteItemProps> = ({ bookKey, item }) => {
{item.note && (
<button
className={clsx(
'btn btn-ghost settings-content hover:bg-transparent',
'btn btn-ghost content settings-content hover:bg-transparent',
'flex h-4 min-h-4 items-end p-0',
)}
onClick={editNote.bind(null, item)}
>
<div
className={clsx(
'align-bottom text-blue-400',
'align-bottom text-blue-500',
'transition duration-300 ease-in-out',
'content font-size-sm',
'opacity-0 group-hover:opacity-100',
'hover:text-blue-600',
)}
@@ -139,15 +140,16 @@ const BooknoteItem: React.FC<BooknoteItemProps> = ({ bookKey, item }) => {
)}
<button
className={clsx(
'btn btn-ghost settings-content hover:bg-transparent',
'btn btn-ghost content settings-content hover:bg-transparent',
'flex h-4 min-h-4 items-end p-0',
)}
onClick={deleteNote.bind(null, item)}
>
<div
className={clsx(
'align-bottom text-red-400',
'align-bottom text-red-500',
'transition duration-300 ease-in-out',
'content font-size-sm',
'opacity-0 group-hover:opacity-100',
'hover:text-red-600',
)}
@@ -51,7 +51,7 @@ const BooknoteView: React.FC<{
<ul role='tree' className='px-2'>
{sortedGroups.map((group) => (
<li key={group.href} className='p-2'>
<h3 className='line-clamp-1 font-normal'>{group.label}</h3>
<h3 className='content font-size-base line-clamp-1 font-normal'>{group.label}</h3>
<ul>
{group.booknotes.map((item, index) => (
<BooknoteItem key={`${index}-${item.cfi}`} bookKey={bookKey} item={item} />
@@ -7,6 +7,7 @@ import { useSidebarStore } from '@/store/sidebarStore';
import { findParentPath } from '@/utils/toc';
import { getContentMd5 } from '@/utils/misc';
import { eventDispatcher } from '@/utils/event';
import { BookProgress } from '@/types/book';
const createExpanderIcon = (isExpanded: boolean) => {
return (
@@ -119,8 +120,7 @@ const TOCView: React.FC<{
setExpandedItems(parentPath.filter(Boolean) as string[]);
};
useEffect(() => {
if (!progress || eventDispatcher.dispatchSync('tts-is-speaking')) return;
const scrollToProgress = (progress: BookProgress) => {
const { sectionHref: currentHref } = progress;
const hrefMd5 = currentHref ? getContentMd5(currentHref) : '';
const currentItem = viewRef.current?.querySelector(`[data-href="${hrefMd5}"]`);
@@ -135,6 +135,28 @@ const TOCView: React.FC<{
if (currentHref) {
expandParents(toc, currentHref);
}
};
useEffect(() => {
const observer = new MutationObserver(() => {
const progress = getProgress(bookKey);
if (progress && viewRef.current) {
scrollToProgress(progress);
observer.disconnect();
}
});
if (viewRef.current) {
observer.observe(viewRef.current, { childList: true, subtree: true });
}
return () => observer.disconnect();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [viewRef]);
useEffect(() => {
if (!progress || eventDispatcher.dispatchSync('tts-is-speaking')) return;
scrollToProgress(progress);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [toc, progress, sideBarBookKey]);
return (
@@ -94,7 +94,7 @@ const TTSControl = () => {
setShowIndicator(true);
try {
if (getOSPlatform() === 'ios') {
if (getOSPlatform() === 'ios' || appService?.isIOSApp) {
unblockAudio();
}
const ttsController = new TTSController(view);
@@ -1,16 +1,17 @@
import { useEffect } from 'react';
import { FoliateView } from '@/types/view';
import { useEnv } from '@/context/EnvContext';
import { useReaderStore } from '@/store/readerStore';
import { eventDispatcher } from '@/utils/event';
import { isTauriAppPlatform } from '@/services/environment';
import { tauriGetWindowLogicalPosition } from '@/utils/window';
import { getOSPlatform } from '@/utils/misc';
export const useClickEvent = (
bookKey: string,
viewRef: React.MutableRefObject<FoliateView | null>,
containerRef: React.RefObject<HTMLDivElement>,
) => {
const { appService } = useEnv();
const { getViewSettings } = useReaderStore();
const { hoveredBookKey, setHoveredBookKey } = useReaderStore();
const handleTurnPage = async (
@@ -27,7 +28,7 @@ export const useClickEvent = (
let windowStartX;
// Currently for tauri APP the window.screenX is always 0
if (isTauriAppPlatform()) {
if (['android', 'ios'].includes(getOSPlatform())) {
if (appService?.isMobile) {
windowStartX = 0;
} else {
const windowPosition = await tauriGetWindowLogicalPosition();
@@ -33,7 +33,7 @@ export const AboutWindow = () => {
isOpen={false}
title={_('About Readest')}
onClose={() => setAboutDialogVisible(false)}
boxClassName='sm:!w-96'
boxClassName='sm:!w-96 sm:h-auto'
>
<div className='about-content flex h-full flex-col items-center justify-center'>
<div className='flex flex-col items-center px-8'>
@@ -73,7 +73,7 @@ export const AboutWindow = () => {
. You are free to use, modify, and distribute this software under the terms of the
AGPL v3 license. Please see the license for more details.
</p>
<p className='text-neutral-content mt-2 text-xs'>
<p className='text-neutral-content my-2 text-xs'>
Source code is available at{' '}
<a
href='https://github.com/readest/readest'
@@ -83,7 +83,7 @@ const BookDetailModal = ({ book, isOpen, onClose }: BookDetailModalProps) => {
isOpen={isOpen}
onClose={handleClose}
className='!bg-[rgba(0,0,0,0.5)]'
boxClassName='sm:min-w-[480px]'
boxClassName='sm:min-w-[480px] sm:h-auto'
contentClassName='!px-6 !py-2'
>
<div className='flex w-full select-text items-center justify-center'>
+3 -3
View File
@@ -54,9 +54,9 @@ const Dialog: React.FC<DialogProps> = ({
>
<div
className={clsx(
'modal-box settings-content flex h-full max-h-full w-full max-w-full flex-col rounded-none p-0 sm:rounded-2xl',
'sm:h-[60%] sm:w-[65%] sm:max-w-[600px]',
appService?.hasSafeAreaInset && 'pt-[env(safe-area-inset-top)]',
'modal-box settings-content flex flex-col rounded-none p-0 sm:rounded-2xl',
'h-full max-h-full w-full max-w-full sm:w-[65%] sm:max-w-[600px]',
appService?.hasSafeAreaInset && 'pt-[env(safe-area-inset-top)] sm:pt-0',
boxClassName,
)}
>
+8 -1
View File
@@ -43,7 +43,14 @@ const MenuItem: React.FC<MenuItemProps> = ({
</span>
</div>
{shortcut && (
<span className='text-neutral-content hidden shrink-0 text-sm sm:flex'>{shortcut}</span>
<kbd
className={clsx(
'border-base-300/40 bg-base-300/75 text-neutral-content hidden rounded-md border shadow-sm sm:flex',
'shrink-0 px-1.5 py-0.5 text-xs font-medium',
)}
>
{shortcut}
</kbd>
)}
</button>
);
+1 -1
View File
@@ -125,7 +125,7 @@ export function useSync(bookKey?: string) {
} catch (err: unknown) {
console.error(err);
if (err instanceof Error) {
if (err.message.includes('Not authenticated')) {
if (err.message.includes('Not authenticated') && settings.keepLogin) {
navigateToLogin(router);
}
setSyncError(err.message || `Error pulling ${type}`);
+1 -1
View File
@@ -40,11 +40,11 @@ import { BOOK_FILE_NOT_FOUND_ERROR } from './errors';
export abstract class BaseAppService implements AppService {
osPlatform: string = getOSPlatform();
isMobile: boolean = ['android', 'ios'].includes(getOSPlatform());
localBooksDir: string = '';
abstract fs: FileSystem;
abstract appPlatform: AppPlatform;
abstract isAppDataSandbox: boolean;
abstract isMobile: boolean;
abstract isAndroidApp: boolean;
abstract isIOSApp: boolean;
abstract hasTrafficLight: boolean;
@@ -86,6 +86,7 @@ export const DEFAULT_BOOK_STYLE: BookStyle = {
export const DEFAULT_MOBILE_VIEW_SETTINGS: Partial<ViewSettings> = {
fullJustification: false,
animated: true,
};
export const DEFAULT_CJK_VIEW_SETTINGS: Partial<ViewSettings> = {
@@ -118,6 +118,7 @@ export class NativeAppService extends BaseAppService {
fs = nativeFileSystem;
appPlatform = 'tauri' as AppPlatform;
isAppDataSandbox = ['android', 'ios'].includes(OS_TYPE);
isMobile = ['android', 'ios'].includes(OS_TYPE);
isAndroidApp = OS_TYPE === 'android';
isIOSApp = OS_TYPE === 'ios';
hasTrafficLight = OS_TYPE === 'macos';
@@ -1,7 +1,7 @@
import { Book } from '@/types/book';
import { ToastType, FileSystem, BaseDir, AppPlatform } from '@/types/system';
import { getCoverFilename } from '@/utils/book';
import { isValidURL } from '@/utils/misc';
import { getOSPlatform, isValidURL } from '@/utils/misc';
import { isPWA } from './environment';
import { BaseAppService } from './appService';
@@ -180,6 +180,7 @@ export class WebAppService extends BaseAppService {
fs = indexedDBFileSystem;
appPlatform = 'web' as AppPlatform;
isAppDataSandbox = false;
isMobile = ['android', 'ios'].includes(getOSPlatform());
isAndroidApp = false;
isIOSApp = false;
hasTrafficLight = false;
+10 -1
View File
@@ -8,6 +8,7 @@ interface NotebookState {
isNotebookPinned: boolean;
notebookNewAnnotation: TextSelection | null;
notebookEditAnnotation: BookNote | null;
notebookAnnotationDrafts: { [key: string]: string };
toggleNotebook: () => void;
toggleNotebookPin: () => void;
setNotebookWidth: (width: string) => void;
@@ -15,14 +16,17 @@ interface NotebookState {
setNotebookPin: (pinned: boolean) => void;
setNotebookNewAnnotation: (selection: TextSelection | null) => void;
setNotebookEditAnnotation: (note: BookNote | null) => void;
saveNotebookAnnotationDraft: (key: string, note: string) => void;
getNotebookAnnotationDraft: (key: string) => string | undefined;
}
export const useNotebookStore = create<NotebookState>((set) => ({
export const useNotebookStore = create<NotebookState>((set, get) => ({
notebookWidth: '',
isNotebookVisible: false,
isNotebookPinned: false,
notebookNewAnnotation: null,
notebookEditAnnotation: null,
notebookAnnotationDrafts: {},
setNotebookWidth: (width: string) => set({ notebookWidth: width }),
toggleNotebook: () => set((state) => ({ isNotebookVisible: !state.isNotebookVisible })),
toggleNotebookPin: () => set((state) => ({ isNotebookPinned: !state.isNotebookPinned })),
@@ -31,4 +35,9 @@ export const useNotebookStore = create<NotebookState>((set) => ({
setNotebookNewAnnotation: (selection: TextSelection | null) =>
set({ notebookNewAnnotation: selection }),
setNotebookEditAnnotation: (note: BookNote | null) => set({ notebookEditAnnotation: note }),
saveNotebookAnnotationDraft: (key: string, note: string) =>
set((state) => ({
notebookAnnotationDrafts: { ...state.notebookAnnotationDrafts, [key]: note },
})),
getNotebookAnnotationDraft: (key: string) => get().notebookAnnotationDrafts[key],
}));
+31
View File
@@ -230,3 +230,34 @@ foliate-view {
.pull-indicator.flip svg {
transform: rotate(180deg);
}
.content {
font-size: 16px;
}
@media (max-width: 480px) {
.content {
font-size: 20px;
}
}
@media (min-width: 481px) and (max-width: 1024px) {
.content {
font-size: 18.4px;
}
}
.content.font-size-base {
font-size: 1em !important;
line-height: 1.5em !important;
}
.content.font-size-sm {
font-size: 0.875em !important;
line-height: 1.25em !important;
}
.content.font-size-xs {
font-size: 0.75em !important;
line-height: 1em !important;
}
+3
View File
@@ -44,6 +44,9 @@ export const getUserLocale = (lang: string): string | undefined => {
return filteredLocales.length > 0 ? filteredLocales[0] : undefined;
};
// Note that iPad may have a user agent string like a desktop browser
// when possible please use appService.isIOSApp || getOSPlatform() === 'ios'
// to check if the app is running on iOS
export const getOSPlatform = () => {
const userAgent = navigator.userAgent.toLowerCase();
+4
View File
@@ -201,6 +201,10 @@ const getLayoutStyles = (
.duokan-footnote-item {
display: none;
}
.calibre {
color: unset;
}
`;
export const getFootnoteStyles = () => `