forked from akai/readest
* feat(rsvp): add persistent context, display settings, and layout improvements, closes #3333 Add always-visible collapsible context panel, font size adjustment, ORP color selection, and stable context window that only rebuilds on scroll. Refactor speed controls into playback row with collapsible settings behind a gear icon. Translate new i18n keys across 29 locales. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(selfhost): update docker db schema to match FileRecords (#3527) In #2636, FileRecord was defined to have updated_at field which is used when it is accessed from the database. But the local dev setup is missing this field. This diff adds an updated_at column to match the expectation * chore(ci): cache system dependency and rustc outputs in ci * chore(ci): lint script changed from tsc to tsgo --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Azeem Bande-Ali <me@azeemba.com>
This commit is contained in:
@@ -12,16 +12,25 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTFLAGS: '-C target-cpu=skylake'
|
||||
SCCACHE_GHA_ENABLED: 'true'
|
||||
RUSTC_WRAPPER: sccache
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: 'true'
|
||||
- name: setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Install minimal stable with clippy and rustfmt
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
- name: Cache apt packages
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: /var/cache/apt/archives
|
||||
key: apt-rust-lint-${{ runner.os }}
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -87,6 +96,9 @@ jobs:
|
||||
|
||||
build_tauri_app:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SCCACHE_GHA_ENABLED: 'true'
|
||||
RUSTC_WRAPPER: sccache
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
@@ -115,6 +127,9 @@ jobs:
|
||||
run: |
|
||||
pnpm install && pnpm setup-vendors
|
||||
|
||||
- name: setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
|
||||
- name: install Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
@@ -125,6 +140,11 @@ jobs:
|
||||
key: tauri-cargo
|
||||
cache-all-crates: 'true'
|
||||
|
||||
- name: Cache apt packages
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: /var/cache/apt/archives
|
||||
key: apt-tauri-${{ runner.os }}
|
||||
- name: install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
|
||||
@@ -48,6 +48,9 @@ tauri.*.conf.json
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# eslint
|
||||
.eslintcache
|
||||
|
||||
#generated
|
||||
src-tauri/gen
|
||||
|
||||
|
||||
@@ -10,17 +10,17 @@
|
||||
"dev-web": "dotenv -e .env.web -- next dev",
|
||||
"build-web": "dotenv -e .env.web -- next build",
|
||||
"start-web": "dotenv -e .env.web -- next start",
|
||||
"dev-web:vinext": "dotenv -e .env.web -- vinext dev --port 3000",
|
||||
"dev-web:vinext": "dotenv -e .env.web -- vinext dev",
|
||||
"build-web:vinext": "dotenv -e .env.web -- vinext build",
|
||||
"start-web:vinext": "dotenv -e .env.web -- vinext start",
|
||||
"build-tauri": "dotenv -e .env.tauri -- next build",
|
||||
"i18n:extract": "i18next-scanner --config i18next-scanner.config.cjs",
|
||||
"lint": "tsc --noEmit && eslint .",
|
||||
"lint": "tsgo --noEmit && eslint --cache .",
|
||||
"test": "dotenv -e .env -e .env.test.local vitest",
|
||||
"test:browser": "vitest --config vitest.browser.config.mts --watch=false",
|
||||
"test:tauri": "bash scripts/test-tauri.sh",
|
||||
"test:pr:web": "pnpm test -- --watch=false && pnpm test:browser",
|
||||
"test:pr:tauri": "pnpm test -- --watch=false && bash scripts/test-tauri.sh",
|
||||
"test:pr:tauri": "bash scripts/test-tauri.sh",
|
||||
"test:all": "pnpm test -- --watch=false && pnpm test:browser && bash scripts/test-tauri.sh",
|
||||
"test:e2e": "wdio run wdio.conf.ts",
|
||||
"tauri:dev:test": "dotenv -e .env.tauri -- tauri dev --features webdriver",
|
||||
@@ -194,6 +194,7 @@
|
||||
"@types/ws": "^8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.48.0",
|
||||
"@typescript-eslint/parser": "^8.48.0",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260312.1",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"@vitejs/plugin-rsc": "^0.5.21",
|
||||
"@vitest/browser-playwright": "^4.0.18",
|
||||
|
||||
@@ -1038,10 +1038,8 @@
|
||||
"Play (Space)": "تشغيل (المسافة)",
|
||||
"Skip forward 15 words": "تخطي للأمام 15 كلمة",
|
||||
"Forward 15 words (Shift+Right)": "للأمام 15 كلمة (Shift+اليمين)",
|
||||
"Pause:": "إيقاف مؤقت:",
|
||||
"Decrease speed": "تقليل السرعة",
|
||||
"Slower (Left/Down)": "أبطأ (اليسار/الأسفل)",
|
||||
"Current speed": "السرعة الحالية",
|
||||
"Increase speed": "زيادة السرعة",
|
||||
"Faster (Right/Up)": "أسرع (اليمين/الأعلى)",
|
||||
"Start RSVP Reading": "بدء قراءة RSVP",
|
||||
@@ -1113,5 +1111,11 @@
|
||||
"Translating...": "جارٍ الترجمة...",
|
||||
"Show Battery Percentage": "عرض نسبة البطارية",
|
||||
"Hide Scrollbar": "إخفاء شريط التمرير",
|
||||
"Skip to last reading position": "انتقل إلى آخر موضع قراءة"
|
||||
"Skip to last reading position": "انتقل إلى آخر موضع قراءة",
|
||||
"Show context": "إظهار السياق",
|
||||
"Hide context": "إخفاء السياق",
|
||||
"Decrease font size": "تصغير حجم الخط",
|
||||
"Increase font size": "تكبير حجم الخط",
|
||||
"Focus": "تركيز",
|
||||
"Theme color": "لون السمة"
|
||||
}
|
||||
|
||||
@@ -990,10 +990,8 @@
|
||||
"Play (Space)": "চালান (Space)",
|
||||
"Skip forward 15 words": "১৫ শব্দ এগিয়ে যান",
|
||||
"Forward 15 words (Shift+Right)": "১৫ শব্দ এগিয়ে যান (Shift+Right)",
|
||||
"Pause:": "বিরতি:",
|
||||
"Decrease speed": "গতি কমান",
|
||||
"Slower (Left/Down)": "ধীর (Left/Down)",
|
||||
"Current speed": "বর্তমান গতি",
|
||||
"Increase speed": "গতি বাড়ান",
|
||||
"Faster (Right/Up)": "দ্রুত (Right/Up)",
|
||||
"Start RSVP Reading": "RSVP পড়া শুরু করুন",
|
||||
@@ -1065,5 +1063,11 @@
|
||||
"Translating...": "অনুবাদ করা হচ্ছে...",
|
||||
"Show Battery Percentage": "ব্যাটারি শতাংশ দেখান",
|
||||
"Hide Scrollbar": "স্ক্রলবার লুকান",
|
||||
"Skip to last reading position": "শেষ পড়ার অবস্থানে যান"
|
||||
"Skip to last reading position": "শেষ পড়ার অবস্থানে যান",
|
||||
"Show context": "প্রসঙ্গ দেখান",
|
||||
"Hide context": "প্রসঙ্গ লুকান",
|
||||
"Decrease font size": "ফন্টের আকার কমান",
|
||||
"Increase font size": "ফন্টের আকার বাড়ান",
|
||||
"Focus": "ফোকাস",
|
||||
"Theme color": "থিম রঙ"
|
||||
}
|
||||
|
||||
@@ -978,10 +978,8 @@
|
||||
"Play (Space)": "གཏོང་བ། (Space)",
|
||||
"Skip forward 15 words": "ཚིག་ ༡༥ མདུན་ལ་བཤུད་པ།",
|
||||
"Forward 15 words (Shift+Right)": "ཚིག་ ༡༥ མདུན་ལ་བཤུད་པ། (Shift+Right)",
|
||||
"Pause:": "མཚམས་འཇོག་པ།",
|
||||
"Decrease speed": "མགྱོགས་ཚད་འགོར་དུ་གཏོང་བ།",
|
||||
"Slower (Left/Down)": "འགོར་བ། (Left/Down)",
|
||||
"Current speed": "ད་ལྟའི་མགྱོགས་ཚད།",
|
||||
"Increase speed": "མགྱོགས་ཚད་མྱུར་དུ་གཏོང་བ།",
|
||||
"Faster (Right/Up)": "མྱུར་བ། (Right/Up)",
|
||||
"Start RSVP Reading": "RSVP ཀློག་འགོ་འཛུགས་པ།",
|
||||
@@ -1053,5 +1051,11 @@
|
||||
"Translating...": "ཡིག་བསྒྱུར་བྱེད་བཞིན་པ།...",
|
||||
"Show Battery Percentage": "གློག་རྫས་བརྒྱ་ཆ་སྟོན།",
|
||||
"Hide Scrollbar": "འགྲིལ་ཤིང་སྦེད་པ།",
|
||||
"Skip to last reading position": "མཐའ་མའི་ཀློག་གནས་སུ་མཆོང་"
|
||||
"Skip to last reading position": "མཐའ་མའི་ཀློག་གནས་སུ་མཆོང་",
|
||||
"Show context": "སྐབས་དོན་མངོན་པ",
|
||||
"Hide context": "སྐབས་དོན་སྦས་པ",
|
||||
"Decrease font size": "ཡིག་གཟུགས་ཆུང་དུ་གཏོང་བ",
|
||||
"Increase font size": "ཡིག་གཟུགས་ཆེ་རུ་གཏོང་བ",
|
||||
"Focus": "དམིགས་གཏད",
|
||||
"Theme color": "བརྗོད་དོན་ཚོན་མདོག"
|
||||
}
|
||||
|
||||
@@ -990,10 +990,8 @@
|
||||
"Play (Space)": "Abspielen (Leertaste)",
|
||||
"Skip forward 15 words": "15 Wörter vor",
|
||||
"Forward 15 words (Shift+Right)": "15 Wörter vor (Shift+Rechts)",
|
||||
"Pause:": "Pause:",
|
||||
"Decrease speed": "Geschwindigkeit verringern",
|
||||
"Slower (Left/Down)": "Langsamer (Links/Unten)",
|
||||
"Current speed": "Aktuelle Geschwindigkeit",
|
||||
"Increase speed": "Geschwindigkeit erhöhen",
|
||||
"Faster (Right/Up)": "Schneller (Rechts/Oben)",
|
||||
"Start RSVP Reading": "RSVP-Lesen starten",
|
||||
@@ -1065,5 +1063,11 @@
|
||||
"Translating...": "Übersetzen...",
|
||||
"Show Battery Percentage": "Akkustand in Prozent anzeigen",
|
||||
"Hide Scrollbar": "Scrollleiste ausblenden",
|
||||
"Skip to last reading position": "Zur letzten Leseposition springen"
|
||||
"Skip to last reading position": "Zur letzten Leseposition springen",
|
||||
"Show context": "Kontext anzeigen",
|
||||
"Hide context": "Kontext ausblenden",
|
||||
"Decrease font size": "Schriftgröße verkleinern",
|
||||
"Increase font size": "Schriftgröße vergrößern",
|
||||
"Focus": "Fokus",
|
||||
"Theme color": "Themenfarbe"
|
||||
}
|
||||
|
||||
@@ -990,10 +990,8 @@
|
||||
"Play (Space)": "Αναπαραγωγή (Διάστημα)",
|
||||
"Skip forward 15 words": "Προώθηση 15 λέξεων",
|
||||
"Forward 15 words (Shift+Right)": "Εμπρός 15 λέξεις (Shift+Δεξιά)",
|
||||
"Pause:": "Παύση:",
|
||||
"Decrease speed": "Μείωση ταχύτητας",
|
||||
"Slower (Left/Down)": "Πιο αργά (Αριστερά/Κάτω)",
|
||||
"Current speed": "Τρέχουσα ταχύτητα",
|
||||
"Increase speed": "Αύξηση ταχύτητας",
|
||||
"Faster (Right/Up)": "Πιο γρήγορα (Δεξιά/Πάνω)",
|
||||
"Start RSVP Reading": "Έναρξη ανάγνωσης RSVP",
|
||||
@@ -1065,5 +1063,11 @@
|
||||
"Translating...": "Μετάφραση...",
|
||||
"Show Battery Percentage": "Εμφάνιση ποσοστού μπαταρίας",
|
||||
"Hide Scrollbar": "Απόκρυψη γραμμής κύλισης",
|
||||
"Skip to last reading position": "Μετάβαση στην τελευταία θέση ανάγνωσης"
|
||||
"Skip to last reading position": "Μετάβαση στην τελευταία θέση ανάγνωσης",
|
||||
"Show context": "Εμφάνιση πλαισίου",
|
||||
"Hide context": "Απόκρυψη πλαισίου",
|
||||
"Decrease font size": "Μείωση μεγέθους γραμματοσειράς",
|
||||
"Increase font size": "Αύξηση μεγέθους γραμματοσειράς",
|
||||
"Focus": "Εστίαση",
|
||||
"Theme color": "Χρώμα θέματος"
|
||||
}
|
||||
|
||||
@@ -1002,10 +1002,8 @@
|
||||
"Play (Space)": "Reproducir (Espacio)",
|
||||
"Skip forward 15 words": "Adelantar 15 palabras",
|
||||
"Forward 15 words (Shift+Right)": "Adelantar 15 palabras (Shift+Derecha)",
|
||||
"Pause:": "Pausa:",
|
||||
"Decrease speed": "Disminuir velocidad",
|
||||
"Slower (Left/Down)": "Más lento (Izquierda/Abajo)",
|
||||
"Current speed": "Velocidad actual",
|
||||
"Increase speed": "Aumentar velocidad",
|
||||
"Faster (Right/Up)": "Más rápido (Derecha/Arriba)",
|
||||
"Start RSVP Reading": "Iniciar lectura RSVP",
|
||||
@@ -1077,5 +1075,11 @@
|
||||
"Translating...": "Traduciendo...",
|
||||
"Show Battery Percentage": "Mostrar porcentaje de batería",
|
||||
"Hide Scrollbar": "Ocultar barra de desplazamiento",
|
||||
"Skip to last reading position": "Ir a la última posición de lectura"
|
||||
"Skip to last reading position": "Ir a la última posición de lectura",
|
||||
"Show context": "Mostrar contexto",
|
||||
"Hide context": "Ocultar contexto",
|
||||
"Decrease font size": "Reducir tamaño de fuente",
|
||||
"Increase font size": "Aumentar tamaño de fuente",
|
||||
"Focus": "Enfoque",
|
||||
"Theme color": "Color del tema"
|
||||
}
|
||||
|
||||
@@ -990,10 +990,8 @@
|
||||
"Play (Space)": "پخش (Space)",
|
||||
"Skip forward 15 words": "۱۵ کلمه به جلو",
|
||||
"Forward 15 words (Shift+Right)": "۱۵ کلمه به جلو (Shift+Right)",
|
||||
"Pause:": "توقف:",
|
||||
"Decrease speed": "کاهش سرعت",
|
||||
"Slower (Left/Down)": "کندتر (Left/Down)",
|
||||
"Current speed": "سرعت فعلی",
|
||||
"Increase speed": "افزایش سرعت",
|
||||
"Faster (Right/Up)": "سریعتر (Right/Up)",
|
||||
"Start RSVP Reading": "شروع مطالعه RSVP",
|
||||
@@ -1065,5 +1063,11 @@
|
||||
"Translating...": "در حال ترجمه...",
|
||||
"Show Battery Percentage": "نمایش درصد باتری",
|
||||
"Hide Scrollbar": "مخفی کردن نوار پیمایش",
|
||||
"Skip to last reading position": "رفتن به آخرین موقعیت مطالعه"
|
||||
"Skip to last reading position": "رفتن به آخرین موقعیت مطالعه",
|
||||
"Show context": "نمایش زمینه",
|
||||
"Hide context": "پنهان کردن زمینه",
|
||||
"Decrease font size": "کاهش اندازه قلم",
|
||||
"Increase font size": "افزایش اندازه قلم",
|
||||
"Focus": "تمرکز",
|
||||
"Theme color": "رنگ پوسته"
|
||||
}
|
||||
|
||||
@@ -1002,10 +1002,8 @@
|
||||
"Play (Space)": "Lecture (Espace)",
|
||||
"Skip forward 15 words": "Avancer de 15 mots",
|
||||
"Forward 15 words (Shift+Right)": "Avancer de 15 mots (Shift+Right)",
|
||||
"Pause:": "Pause :",
|
||||
"Decrease speed": "Diminuer la vitesse",
|
||||
"Slower (Left/Down)": "Plus lent (Gauche/Bas)",
|
||||
"Current speed": "Vitesse actuelle",
|
||||
"Increase speed": "Augmenter la vitesse",
|
||||
"Faster (Right/Up)": "Plus rapide (Droite/Haut)",
|
||||
"Start RSVP Reading": "Démarrer la lecture RSVP",
|
||||
@@ -1077,5 +1075,11 @@
|
||||
"Translating...": "Traduction en cours...",
|
||||
"Show Battery Percentage": "Afficher le pourcentage de batterie",
|
||||
"Hide Scrollbar": "Masquer la barre de défilement",
|
||||
"Skip to last reading position": "Aller à la dernière position de lecture"
|
||||
"Skip to last reading position": "Aller à la dernière position de lecture",
|
||||
"Show context": "Afficher le contexte",
|
||||
"Hide context": "Masquer le contexte",
|
||||
"Decrease font size": "Réduire la taille de police",
|
||||
"Increase font size": "Augmenter la taille de police",
|
||||
"Focus": "Focus",
|
||||
"Theme color": "Couleur du thème"
|
||||
}
|
||||
|
||||
@@ -502,10 +502,8 @@
|
||||
"Play (Space)": "נגן (רווח)",
|
||||
"Skip forward 15 words": "קפוץ 15 מילים קדימה",
|
||||
"Forward 15 words (Shift+Right)": "15 מילים קדימה (Shift+Right)",
|
||||
"Pause:": "השהה:",
|
||||
"Decrease speed": "הפחת מהירות",
|
||||
"Slower (Left/Down)": "איטי יותר (חץ שמאלה/למטה)",
|
||||
"Current speed": "מהירות נוכחית",
|
||||
"Increase speed": "הגבר מהירות",
|
||||
"Faster (Right/Up)": "מהיר יותר (חץ ימינה/למעלה)",
|
||||
"Start RSVP Reading": "התחל קריאת RSVP",
|
||||
@@ -1077,5 +1075,11 @@
|
||||
"Translating...": "מתרגם...",
|
||||
"Show Battery Percentage": "הצג אחוזי סוללה",
|
||||
"Hide Scrollbar": "הסתר סרגל גלילה",
|
||||
"Skip to last reading position": "דלג למיקום הקריאה האחרון"
|
||||
"Skip to last reading position": "דלג למיקום הקריאה האחרון",
|
||||
"Show context": "הצג הקשר",
|
||||
"Hide context": "הסתר הקשר",
|
||||
"Decrease font size": "הקטן גודל גופן",
|
||||
"Increase font size": "הגדל גודל גופן",
|
||||
"Focus": "מיקוד",
|
||||
"Theme color": "צבע ערכת נושא"
|
||||
}
|
||||
|
||||
@@ -990,10 +990,8 @@
|
||||
"Play (Space)": "चलाएं (Space)",
|
||||
"Skip forward 15 words": "15 शब्द आगे",
|
||||
"Forward 15 words (Shift+Right)": "15 शब्द आगे (Shift+Right)",
|
||||
"Pause:": "विराम:",
|
||||
"Decrease speed": "गति कम करें",
|
||||
"Slower (Left/Down)": "धीमी (Left/Down)",
|
||||
"Current speed": "वर्तमान गति",
|
||||
"Increase speed": "गति बढ़ाएं",
|
||||
"Faster (Right/Up)": "तेज़ (Right/Up)",
|
||||
"Start RSVP Reading": "RSVP पठन शुरू करें",
|
||||
@@ -1065,5 +1063,11 @@
|
||||
"Translating...": "अनुवाद हो रहा है...",
|
||||
"Show Battery Percentage": "बैटरी प्रतिशत दिखाएं",
|
||||
"Hide Scrollbar": "स्क्रॉलबार छुपाएँ",
|
||||
"Skip to last reading position": "अंतिम पढ़ने की स्थिति पर जाएँ"
|
||||
"Skip to last reading position": "अंतिम पढ़ने की स्थिति पर जाएँ",
|
||||
"Show context": "संदर्भ दिखाएँ",
|
||||
"Hide context": "संदर्भ छुपाएँ",
|
||||
"Decrease font size": "फ़ॉन्ट आकार घटाएँ",
|
||||
"Increase font size": "फ़ॉन्ट आकार बढ़ाएँ",
|
||||
"Focus": "फ़ोकस",
|
||||
"Theme color": "थीम रंग"
|
||||
}
|
||||
|
||||
@@ -978,10 +978,8 @@
|
||||
"Play (Space)": "Putar (Spasi)",
|
||||
"Skip forward 15 words": "Lompat maju 15 kata",
|
||||
"Forward 15 words (Shift+Right)": "Maju 15 kata (Shift+Kanan)",
|
||||
"Pause:": "Jeda:",
|
||||
"Decrease speed": "Kurangi kecepatan",
|
||||
"Slower (Left/Down)": "Lebih lambat (Kiri/Bawah)",
|
||||
"Current speed": "Kecepatan saat ini",
|
||||
"Increase speed": "Tambah kecepatan",
|
||||
"Faster (Right/Up)": "Lebih cepat (Kanan/Atas)",
|
||||
"Start RSVP Reading": "Mulai Membaca RSVP",
|
||||
@@ -1053,5 +1051,11 @@
|
||||
"Translating...": "Menerjemahkan...",
|
||||
"Show Battery Percentage": "Tampilkan Persentase Baterai",
|
||||
"Hide Scrollbar": "Sembunyikan Bilah Gulir",
|
||||
"Skip to last reading position": "Lompat ke posisi baca terakhir"
|
||||
"Skip to last reading position": "Lompat ke posisi baca terakhir",
|
||||
"Show context": "Tampilkan konteks",
|
||||
"Hide context": "Sembunyikan konteks",
|
||||
"Decrease font size": "Perkecil ukuran font",
|
||||
"Increase font size": "Perbesar ukuran font",
|
||||
"Focus": "Fokus",
|
||||
"Theme color": "Warna tema"
|
||||
}
|
||||
|
||||
@@ -1002,10 +1002,8 @@
|
||||
"Play (Space)": "Riproduci (Spazio)",
|
||||
"Skip forward 15 words": "Avanti di 15 parole",
|
||||
"Forward 15 words (Shift+Right)": "Avanti di 15 parole (Shift+Destra)",
|
||||
"Pause:": "Pausa:",
|
||||
"Decrease speed": "Diminuisci velocità",
|
||||
"Slower (Left/Down)": "Più lento (Sinistra/Giù)",
|
||||
"Current speed": "Velocità attuale",
|
||||
"Increase speed": "Aumenta velocità",
|
||||
"Faster (Right/Up)": "Più veloce (Destra/Su)",
|
||||
"Start RSVP Reading": "Avvia lettura RSVP",
|
||||
@@ -1077,5 +1075,11 @@
|
||||
"Translating...": "Traduzione...",
|
||||
"Show Battery Percentage": "Mostra percentuale batteria",
|
||||
"Hide Scrollbar": "Nascondi barra di scorrimento",
|
||||
"Skip to last reading position": "Vai all'ultima posizione di lettura"
|
||||
"Skip to last reading position": "Vai all'ultima posizione di lettura",
|
||||
"Show context": "Mostra contesto",
|
||||
"Hide context": "Nascondi contesto",
|
||||
"Decrease font size": "Riduci dimensione carattere",
|
||||
"Increase font size": "Aumenta dimensione carattere",
|
||||
"Focus": "Messa a fuoco",
|
||||
"Theme color": "Colore del tema"
|
||||
}
|
||||
|
||||
@@ -978,10 +978,8 @@
|
||||
"Play (Space)": "再生 (Space)",
|
||||
"Skip forward 15 words": "15単語進む",
|
||||
"Forward 15 words (Shift+Right)": "15単語進む (Shift+右)",
|
||||
"Pause:": "一時停止:",
|
||||
"Decrease speed": "速度を下げる",
|
||||
"Slower (Left/Down)": "低速 (左/下)",
|
||||
"Current speed": "現在の速度",
|
||||
"Increase speed": "速度を上げる",
|
||||
"Faster (Right/Up)": "高速 (右/上)",
|
||||
"Start RSVP Reading": "RSVP読書を開始",
|
||||
@@ -1053,5 +1051,11 @@
|
||||
"Translating...": "翻訳中...",
|
||||
"Show Battery Percentage": "バッテリー残量を表示",
|
||||
"Hide Scrollbar": "スクロールバーを非表示",
|
||||
"Skip to last reading position": "最後の読書位置へ移動"
|
||||
"Skip to last reading position": "最後の読書位置へ移動",
|
||||
"Show context": "コンテキストを表示",
|
||||
"Hide context": "コンテキストを非表示",
|
||||
"Decrease font size": "文字サイズを縮小",
|
||||
"Increase font size": "文字サイズを拡大",
|
||||
"Focus": "フォーカス",
|
||||
"Theme color": "テーマカラー"
|
||||
}
|
||||
|
||||
@@ -978,10 +978,8 @@
|
||||
"Play (Space)": "재생 (스페이스바)",
|
||||
"Skip forward 15 words": "15단어 앞으로",
|
||||
"Forward 15 words (Shift+Right)": "15단어 앞으로 (Shift+오른쪽 화살표)",
|
||||
"Pause:": "일시정지:",
|
||||
"Decrease speed": "속도 줄이기",
|
||||
"Slower (Left/Down)": "느리게 (왼쪽/아래 화살표)",
|
||||
"Current speed": "현재 속도",
|
||||
"Increase speed": "속도 높이기",
|
||||
"Faster (Right/Up)": "빠르게 (오른쪽/위 화살표)",
|
||||
"Start RSVP Reading": "RSVP 독서 시작",
|
||||
@@ -1053,5 +1051,11 @@
|
||||
"Translating...": "번역 중...",
|
||||
"Show Battery Percentage": "배터리 백분율 표시",
|
||||
"Hide Scrollbar": "스크롤바 숨기기",
|
||||
"Skip to last reading position": "마지막 읽기 위치로 이동"
|
||||
"Skip to last reading position": "마지막 읽기 위치로 이동",
|
||||
"Show context": "맥락 표시",
|
||||
"Hide context": "맥락 숨기기",
|
||||
"Decrease font size": "글꼴 크기 줄이기",
|
||||
"Increase font size": "글꼴 크기 늘리기",
|
||||
"Focus": "초점",
|
||||
"Theme color": "테마 색상"
|
||||
}
|
||||
|
||||
@@ -978,10 +978,8 @@
|
||||
"Play (Space)": "Main (Ruang)",
|
||||
"Skip forward 15 words": "Langkau depan 15 perkataan",
|
||||
"Forward 15 words (Shift+Right)": "Depan 15 perkataan (Shift+Kanan)",
|
||||
"Pause:": "Jeda:",
|
||||
"Decrease speed": "Kurangkan kelajuan",
|
||||
"Slower (Left/Down)": "Lebih perlahan (Kiri/Bawah)",
|
||||
"Current speed": "Kelajuan semasa",
|
||||
"Increase speed": "Tingkatkan kelajuan",
|
||||
"Faster (Right/Up)": "Lebih cepat (Kanan/Atas)",
|
||||
"Start RSVP Reading": "Mulakan Pembacaan RSVP",
|
||||
@@ -1053,5 +1051,11 @@
|
||||
"Translating...": "Menterjemah...",
|
||||
"Show Battery Percentage": "Tunjukkan Peratusan Bateri",
|
||||
"Hide Scrollbar": "Sembunyikan Bar Tatal",
|
||||
"Skip to last reading position": "Lompat ke posisi bacaan terakhir"
|
||||
"Skip to last reading position": "Lompat ke posisi bacaan terakhir",
|
||||
"Show context": "Tunjukkan konteks",
|
||||
"Hide context": "Sembunyikan konteks",
|
||||
"Decrease font size": "Kecilkan saiz fon",
|
||||
"Increase font size": "Besarkan saiz fon",
|
||||
"Focus": "Fokus",
|
||||
"Theme color": "Warna tema"
|
||||
}
|
||||
|
||||
@@ -990,10 +990,8 @@
|
||||
"Play (Space)": "Afspelen (Spatie)",
|
||||
"Skip forward 15 words": "15 woorden vooruit",
|
||||
"Forward 15 words (Shift+Right)": "15 woorden vooruit (Shift+Rechts)",
|
||||
"Pause:": "Pauze:",
|
||||
"Decrease speed": "Snelheid verlagen",
|
||||
"Slower (Left/Down)": "Langzamer (Links/Omlaag)",
|
||||
"Current speed": "Huidige snelheid",
|
||||
"Increase speed": "Snelheid verhogen",
|
||||
"Faster (Right/Up)": "Sneller (Rechts/Omhoog)",
|
||||
"Start RSVP Reading": "Start RSVP-lezen",
|
||||
@@ -1065,5 +1063,11 @@
|
||||
"Translating...": "Vertalen...",
|
||||
"Show Battery Percentage": "Batterijpercentage weergeven",
|
||||
"Hide Scrollbar": "Schuifbalk verbergen",
|
||||
"Skip to last reading position": "Ga naar laatste leespositie"
|
||||
"Skip to last reading position": "Ga naar laatste leespositie",
|
||||
"Show context": "Context tonen",
|
||||
"Hide context": "Context verbergen",
|
||||
"Decrease font size": "Lettergrootte verkleinen",
|
||||
"Increase font size": "Lettergrootte vergroten",
|
||||
"Focus": "Focus",
|
||||
"Theme color": "Themakleur"
|
||||
}
|
||||
|
||||
@@ -1014,10 +1014,8 @@
|
||||
"Play (Space)": "Odtwarzaj (Spacja)",
|
||||
"Skip forward 15 words": "Naprzód o 15 słów",
|
||||
"Forward 15 words (Shift+Right)": "Naprzód o 15 słów (Shift+Prawo)",
|
||||
"Pause:": "Pauza:",
|
||||
"Decrease speed": "Zmniejsz prędkość",
|
||||
"Slower (Left/Down)": "Wolniej (Lewo/Dół)",
|
||||
"Current speed": "Aktualna prędkość",
|
||||
"Increase speed": "Zwiększ prędkość",
|
||||
"Faster (Right/Up)": "Szybciej (Prawo/Góra)",
|
||||
"Start RSVP Reading": "Uruchom czytanie RSVP",
|
||||
@@ -1089,5 +1087,11 @@
|
||||
"Translating...": "Tłumaczenie...",
|
||||
"Show Battery Percentage": "Pokaż procent baterii",
|
||||
"Hide Scrollbar": "Ukryj pasek przewijania",
|
||||
"Skip to last reading position": "Przejdź do ostatniej pozycji czytania"
|
||||
"Skip to last reading position": "Przejdź do ostatniej pozycji czytania",
|
||||
"Show context": "Pokaż kontekst",
|
||||
"Hide context": "Ukryj kontekst",
|
||||
"Decrease font size": "Zmniejsz rozmiar czcionki",
|
||||
"Increase font size": "Zwiększ rozmiar czcionki",
|
||||
"Focus": "Fokus",
|
||||
"Theme color": "Kolor motywu"
|
||||
}
|
||||
|
||||
@@ -1002,10 +1002,8 @@
|
||||
"Play (Space)": "Reproduzir (Espaço)",
|
||||
"Skip forward 15 words": "Avançar 15 palavras",
|
||||
"Forward 15 words (Shift+Right)": "Avançar 15 palavras (Shift+Direita)",
|
||||
"Pause:": "Pausa:",
|
||||
"Decrease speed": "Diminuir velocidade",
|
||||
"Slower (Left/Down)": "Mais lento (Esquerda/Baixo)",
|
||||
"Current speed": "Velocidade atual",
|
||||
"Increase speed": "Aumentar velocidade",
|
||||
"Faster (Right/Up)": "Mais rápido (Direita/Cima)",
|
||||
"Start RSVP Reading": "Iniciar leitura RSVP",
|
||||
@@ -1077,5 +1075,11 @@
|
||||
"Translating...": "Traduzindo...",
|
||||
"Show Battery Percentage": "Mostrar porcentagem da bateria",
|
||||
"Hide Scrollbar": "Ocultar barra de rolagem",
|
||||
"Skip to last reading position": "Ir para a última posição de leitura"
|
||||
"Skip to last reading position": "Ir para a última posição de leitura",
|
||||
"Show context": "Mostrar contexto",
|
||||
"Hide context": "Ocultar contexto",
|
||||
"Decrease font size": "Diminuir tamanho da fonte",
|
||||
"Increase font size": "Aumentar tamanho da fonte",
|
||||
"Focus": "Foco",
|
||||
"Theme color": "Cor do tema"
|
||||
}
|
||||
|
||||
@@ -1014,10 +1014,8 @@
|
||||
"Play (Space)": "Воспроизведение (Пробел)",
|
||||
"Skip forward 15 words": "Вперед на 15 слов",
|
||||
"Forward 15 words (Shift+Right)": "Вперед на 15 слов (Shift+Вправо)",
|
||||
"Pause:": "Пауза:",
|
||||
"Decrease speed": "Уменьшить скорость",
|
||||
"Slower (Left/Down)": "Медленнее (Влево/Вниз)",
|
||||
"Current speed": "Текущая скорость",
|
||||
"Increase speed": "Увеличить скорость",
|
||||
"Faster (Right/Up)": "Быстрее (Вправо/Вверх)",
|
||||
"Start RSVP Reading": "Начать чтение RSVP",
|
||||
@@ -1089,5 +1087,11 @@
|
||||
"Translating...": "Перевод...",
|
||||
"Show Battery Percentage": "Показывать процент заряда батареи",
|
||||
"Hide Scrollbar": "Скрыть полосу прокрутки",
|
||||
"Skip to last reading position": "Перейти к последней позиции чтения"
|
||||
"Skip to last reading position": "Перейти к последней позиции чтения",
|
||||
"Show context": "Показать контекст",
|
||||
"Hide context": "Скрыть контекст",
|
||||
"Decrease font size": "Уменьшить размер шрифта",
|
||||
"Increase font size": "Увеличить размер шрифта",
|
||||
"Focus": "Фокус",
|
||||
"Theme color": "Цвет темы"
|
||||
}
|
||||
|
||||
@@ -990,10 +990,8 @@
|
||||
"Play (Space)": "ධාවනය (Space)",
|
||||
"Skip forward 15 words": "වචන 15ක් ඉදිරියට",
|
||||
"Forward 15 words (Shift+Right)": "වචන 15ක් ඉදිරියට (Shift+Right)",
|
||||
"Pause:": "විරාමය:",
|
||||
"Decrease speed": "වේගය අඩු කරන්න",
|
||||
"Slower (Left/Down)": "මන්දගාමී (Left/Down)",
|
||||
"Current speed": "වත්මන් වේගය",
|
||||
"Increase speed": "වේගය වැඩි කරන්න",
|
||||
"Faster (Right/Up)": "වේගවත් (Right/Up)",
|
||||
"Start RSVP Reading": "RSVP කියවීම ආරම්භ කරන්න",
|
||||
@@ -1065,5 +1063,11 @@
|
||||
"Translating...": "පරිවර්තනය වෙමින් පවතී...",
|
||||
"Show Battery Percentage": "බැටරි ප්රතිශතය පෙන්වන්න",
|
||||
"Hide Scrollbar": "අනුචලන තීරුව සඟවන්න",
|
||||
"Skip to last reading position": "අවසන් කියවීමේ ස්ථානයට යන්න"
|
||||
"Skip to last reading position": "අවසන් කියවීමේ ස්ථානයට යන්න",
|
||||
"Show context": "සන්දර්භය පෙන්වන්න",
|
||||
"Hide context": "සන්දර්භය සඟවන්න",
|
||||
"Decrease font size": "අකුරු ප්රමාණය අඩු කරන්න",
|
||||
"Increase font size": "අකුරු ප්රමාණය වැඩි කරන්න",
|
||||
"Focus": "අවධානය",
|
||||
"Theme color": "තේමා වර්ණය"
|
||||
}
|
||||
|
||||
@@ -532,10 +532,8 @@
|
||||
"Play (Space)": "Predvajaj (Preslednica)",
|
||||
"Skip forward 15 words": "Skoči naprej za 15 besed",
|
||||
"Forward 15 words (Shift+Right)": "Naprej za 15 besed (Shift+Desno)",
|
||||
"Pause:": "Premor:",
|
||||
"Decrease speed": "Zmanjšaj hitrost",
|
||||
"Slower (Left/Down)": "Počasneje (Levo/Dol)",
|
||||
"Current speed": "Trenutna hitrost",
|
||||
"Increase speed": "Povečaj hitrost",
|
||||
"Faster (Right/Up)": "Hitreje (Desno/Gor)",
|
||||
"Start RSVP Reading": "Začni RSVP branje",
|
||||
@@ -1089,5 +1087,11 @@
|
||||
"Show Current Battery Status": "Prikaži trenutno stanje baterije",
|
||||
"Show Battery Percentage": "Prikaži odstotek baterije",
|
||||
"Hide Scrollbar": "Skrij drsni trak",
|
||||
"Skip to last reading position": "Skoči na zadnji položaj branja"
|
||||
"Skip to last reading position": "Skoči na zadnji položaj branja",
|
||||
"Show context": "Prikaži kontekst",
|
||||
"Hide context": "Skrij kontekst",
|
||||
"Decrease font size": "Pomanjšaj pisavo",
|
||||
"Increase font size": "Povečaj pisavo",
|
||||
"Focus": "Fokus",
|
||||
"Theme color": "Barva teme"
|
||||
}
|
||||
|
||||
@@ -990,10 +990,8 @@
|
||||
"Play (Space)": "Spela (Mellanslag)",
|
||||
"Skip forward 15 words": "Hoppa framåt 15 ord",
|
||||
"Forward 15 words (Shift+Right)": "Framåt 15 ord (Shift+Höger)",
|
||||
"Pause:": "Paus:",
|
||||
"Decrease speed": "Sänk hastigheten",
|
||||
"Slower (Left/Down)": "Långsammare (Vänster/Nedåt)",
|
||||
"Current speed": "Nuvarande hastighet",
|
||||
"Increase speed": "Höj hastigheten",
|
||||
"Faster (Right/Up)": "Snabbare (Höger/Uppåt)",
|
||||
"Start RSVP Reading": "Starta RSVP-läsning",
|
||||
@@ -1065,5 +1063,11 @@
|
||||
"Translating...": "Översätter...",
|
||||
"Show Battery Percentage": "Visa batteriprocent",
|
||||
"Hide Scrollbar": "Dölj rullningslist",
|
||||
"Skip to last reading position": "Hoppa till senaste läsposition"
|
||||
"Skip to last reading position": "Hoppa till senaste läsposition",
|
||||
"Show context": "Visa kontext",
|
||||
"Hide context": "Dölj kontext",
|
||||
"Decrease font size": "Minska teckenstorlek",
|
||||
"Increase font size": "Öka teckenstorlek",
|
||||
"Focus": "Fokus",
|
||||
"Theme color": "Temafärg"
|
||||
}
|
||||
|
||||
@@ -990,10 +990,8 @@
|
||||
"Play (Space)": "இயக்கு (Space)",
|
||||
"Skip forward 15 words": "15 வார்த்தைகள் முன்னால் செல்",
|
||||
"Forward 15 words (Shift+Right)": "15 வார்த்தைகள் முன்னால் செல் (Shift+Right)",
|
||||
"Pause:": "நிறுத்து:",
|
||||
"Decrease speed": "வேகத்தைக் குறை",
|
||||
"Slower (Left/Down)": "மெதுவாக (Left/Down)",
|
||||
"Current speed": "தற்போதைய வேகம்",
|
||||
"Increase speed": "வேகத்தை அதிகரி",
|
||||
"Faster (Right/Up)": "வேகமாக (Right/Up)",
|
||||
"Start RSVP Reading": "RSVP வாசிப்பைத் தொடங்கு",
|
||||
@@ -1065,5 +1063,11 @@
|
||||
"Translating...": "மொழிபெயர்க்கிறது...",
|
||||
"Show Battery Percentage": "பேட்டரி சதவீதத்தைக் காண்பி",
|
||||
"Hide Scrollbar": "உருள்பட்டையை மறை",
|
||||
"Skip to last reading position": "கடைசி வாசிப்பு நிலைக்குச் செல்"
|
||||
"Skip to last reading position": "கடைசி வாசிப்பு நிலைக்குச் செல்",
|
||||
"Show context": "சூழலைக் காட்டு",
|
||||
"Hide context": "சூழலை மறை",
|
||||
"Decrease font size": "எழுத்துரு அளவைக் குறை",
|
||||
"Increase font size": "எழுத்துரு அளவை அதிகரி",
|
||||
"Focus": "குவியம்",
|
||||
"Theme color": "தீம் நிறம்"
|
||||
}
|
||||
|
||||
@@ -978,10 +978,8 @@
|
||||
"Play (Space)": "เล่น (Space)",
|
||||
"Skip forward 15 words": "ไปข้างหน้า 15 คำ",
|
||||
"Forward 15 words (Shift+Right)": "ไปข้างหน้า 15 คำ (Shift+ขวา)",
|
||||
"Pause:": "หยุดชั่วคราว:",
|
||||
"Decrease speed": "ลดความเร็ว",
|
||||
"Slower (Left/Down)": "ช้าลง (ซ้าย/ลง)",
|
||||
"Current speed": "ความเร็วปัจจุบัน",
|
||||
"Increase speed": "เพิ่มความเร็ว",
|
||||
"Faster (Right/Up)": "เร็วขึ้น (ขวา/ขึ้น)",
|
||||
"Start RSVP Reading": "เริ่มการอ่านแบบ RSVP",
|
||||
@@ -1053,5 +1051,11 @@
|
||||
"Translating...": "กำลังแปล...",
|
||||
"Show Battery Percentage": "แสดงเปอร์เซ็นต์แบตเตอรี่",
|
||||
"Hide Scrollbar": "ซ่อนแถบเลื่อน",
|
||||
"Skip to last reading position": "ข้ามไปตำแหน่งอ่านล่าสุด"
|
||||
"Skip to last reading position": "ข้ามไปตำแหน่งอ่านล่าสุด",
|
||||
"Show context": "แสดงบริบท",
|
||||
"Hide context": "ซ่อนบริบท",
|
||||
"Decrease font size": "ลดขนาดตัวอักษร",
|
||||
"Increase font size": "เพิ่มขนาดตัวอักษร",
|
||||
"Focus": "โฟกัส",
|
||||
"Theme color": "สีธีม"
|
||||
}
|
||||
|
||||
@@ -990,10 +990,8 @@
|
||||
"Play (Space)": "Oynat (Boşluk)",
|
||||
"Skip forward 15 words": "15 kelime ileri atla",
|
||||
"Forward 15 words (Shift+Right)": "15 kelime ileri (Shift+Sağ)",
|
||||
"Pause:": "Duraklat:",
|
||||
"Decrease speed": "Hızı azalt",
|
||||
"Slower (Left/Down)": "Daha yavaş (Sol/Aşağı)",
|
||||
"Current speed": "Mevcut hız",
|
||||
"Increase speed": "Hızı artır",
|
||||
"Faster (Right/Up)": "Daha hızlı (Sağ/Yukarı)",
|
||||
"Start RSVP Reading": "RSVP Okumasını Başlat",
|
||||
@@ -1065,5 +1063,11 @@
|
||||
"Translating...": "Çevriliyor...",
|
||||
"Show Battery Percentage": "Pil Yüzdesini Göster",
|
||||
"Hide Scrollbar": "Kaydırma çubuğunu gizle",
|
||||
"Skip to last reading position": "Son okuma konumuna atla"
|
||||
"Skip to last reading position": "Son okuma konumuna atla",
|
||||
"Show context": "Bağlamı göster",
|
||||
"Hide context": "Bağlamı gizle",
|
||||
"Decrease font size": "Yazı tipi boyutunu küçült",
|
||||
"Increase font size": "Yazı tipi boyutunu büyüt",
|
||||
"Focus": "Odak",
|
||||
"Theme color": "Tema rengi"
|
||||
}
|
||||
|
||||
@@ -1014,10 +1014,8 @@
|
||||
"Play (Space)": "Відтворити (Пробіл)",
|
||||
"Skip forward 15 words": "Вперед на 15 слів",
|
||||
"Forward 15 words (Shift+Right)": "Вперед на 15 слів (Shift+Стрілка вправо)",
|
||||
"Pause:": "Пауза:",
|
||||
"Decrease speed": "Зменшити швидкість",
|
||||
"Slower (Left/Down)": "Повільніше (стрілки Вліво/Вниз)",
|
||||
"Current speed": "Поточна швидкість",
|
||||
"Increase speed": "Збільшити швидкість",
|
||||
"Faster (Right/Up)": "Швидше (стрілки Вправо/Вгору)",
|
||||
"Start RSVP Reading": "Почати читання RSVP",
|
||||
@@ -1089,5 +1087,11 @@
|
||||
"Translating...": "Переклад...",
|
||||
"Show Battery Percentage": "Показувати відсоток заряду батареї",
|
||||
"Hide Scrollbar": "Сховати смугу прокрутки",
|
||||
"Skip to last reading position": "Перейти до останньої позиції читання"
|
||||
"Skip to last reading position": "Перейти до останньої позиції читання",
|
||||
"Show context": "Показати контекст",
|
||||
"Hide context": "Сховати контекст",
|
||||
"Decrease font size": "Зменшити розмір шрифту",
|
||||
"Increase font size": "Збільшити розмір шрифту",
|
||||
"Focus": "Фокус",
|
||||
"Theme color": "Колір теми"
|
||||
}
|
||||
|
||||
@@ -978,10 +978,8 @@
|
||||
"Play (Space)": "Phát (Khoảng trắng)",
|
||||
"Skip forward 15 words": "Tiến tới 15 từ",
|
||||
"Forward 15 words (Shift+Right)": "Tiến tới 15 từ (Shift+Phải)",
|
||||
"Pause:": "Tạm dừng:",
|
||||
"Decrease speed": "Giảm tốc độ",
|
||||
"Slower (Left/Down)": "Chậm hơn (Trái/Xuống)",
|
||||
"Current speed": "Tốc độ hiện tại",
|
||||
"Increase speed": "Tăng tốc độ",
|
||||
"Faster (Right/Up)": "Nhanh hơn (Phải/Lên)",
|
||||
"Start RSVP Reading": "Bắt đầu đọc RSVP",
|
||||
@@ -1053,5 +1051,11 @@
|
||||
"Translating...": "Đang dịch...",
|
||||
"Show Battery Percentage": "Hiển thị phần trăm pin",
|
||||
"Hide Scrollbar": "Ẩn thanh cuộn",
|
||||
"Skip to last reading position": "Chuyển đến vị trí đọc cuối"
|
||||
"Skip to last reading position": "Chuyển đến vị trí đọc cuối",
|
||||
"Show context": "Hiển thị ngữ cảnh",
|
||||
"Hide context": "Ẩn ngữ cảnh",
|
||||
"Decrease font size": "Giảm cỡ chữ",
|
||||
"Increase font size": "Tăng cỡ chữ",
|
||||
"Focus": "Tiêu điểm",
|
||||
"Theme color": "Màu chủ đề"
|
||||
}
|
||||
|
||||
@@ -979,10 +979,8 @@
|
||||
"Play (Space)": "播放 (空格)",
|
||||
"Skip forward 15 words": "前进 15 词",
|
||||
"Forward 15 words (Shift+Right)": "前进 15 词 (Shift+Right)",
|
||||
"Pause:": "暂停:",
|
||||
"Decrease speed": "减速",
|
||||
"Slower (Left/Down)": "较慢 (左/下)",
|
||||
"Current speed": "当前速度",
|
||||
"Increase speed": "加速",
|
||||
"Faster (Right/Up)": "较快 (右/上)",
|
||||
"Start RSVP Reading": "开始 RSVP 阅读",
|
||||
@@ -1053,5 +1051,11 @@
|
||||
"Annotation page number": "批注页码",
|
||||
"Translating...": "翻译中...",
|
||||
"Show Battery Percentage": "显示电量百分比",
|
||||
"Skip to last reading position": "跳转到上次阅读位置"
|
||||
"Skip to last reading position": "跳转到上次阅读位置",
|
||||
"Show context": "显示上下文",
|
||||
"Hide context": "隐藏上下文",
|
||||
"Decrease font size": "缩小字号",
|
||||
"Increase font size": "放大字号",
|
||||
"Focus": "焦点",
|
||||
"Theme color": "主题颜色"
|
||||
}
|
||||
|
||||
@@ -978,10 +978,8 @@
|
||||
"Play (Space)": "播放 (空格)",
|
||||
"Skip forward 15 words": "前進 15 詞",
|
||||
"Forward 15 words (Shift+Right)": "前進 15 詞 (Shift+Right)",
|
||||
"Pause:": "暫停:",
|
||||
"Decrease speed": "減速",
|
||||
"Slower (Left/Down)": "較慢 (左/下)",
|
||||
"Current speed": "當前速度",
|
||||
"Increase speed": "加速",
|
||||
"Faster (Right/Up)": "較快 (右/上)",
|
||||
"Start RSVP Reading": "開始 RSVP 閱讀",
|
||||
@@ -1053,5 +1051,11 @@
|
||||
"Translating...": "翻譯中...",
|
||||
"Show Battery Percentage": "顯示電池百分比",
|
||||
"Hide Scrollbar": "隱藏滾動條",
|
||||
"Skip to last reading position": "跳至上次閱讀位置"
|
||||
"Skip to last reading position": "跳至上次閱讀位置",
|
||||
"Show context": "顯示上下文",
|
||||
"Hide context": "隱藏上下文",
|
||||
"Decrease font size": "縮小字型",
|
||||
"Increase font size": "放大字型",
|
||||
"Focus": "焦點",
|
||||
"Theme color": "主題顏色"
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ DEV_PID=$!
|
||||
|
||||
echo "Waiting for dev server on port $DEV_PORT..."
|
||||
elapsed=0
|
||||
while ! curl -sf "http://127.0.0.1:${DEV_PORT}" >/dev/null 2>&1; do
|
||||
while ! curl -sf "http://localhost:${DEV_PORT}" >/dev/null 2>&1; do
|
||||
if ! kill -0 "$DEV_PID" 2>/dev/null; then
|
||||
echo "ERROR: Dev server exited unexpectedly."
|
||||
exit 1
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
IoPlaySkipForward,
|
||||
IoRemove,
|
||||
IoAdd,
|
||||
IoChevronDown,
|
||||
IoSettingsSharp,
|
||||
} from 'react-icons/io5';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { Overlay } from '@/components/Overlay';
|
||||
@@ -24,6 +26,14 @@ interface FlatChapter {
|
||||
level: number;
|
||||
}
|
||||
|
||||
// Display settings
|
||||
const FONT_SIZE_OPTIONS = [1.25, 1.5, 1.875, 2.25, 3, 3.75, 4.25, 5, 6, 8];
|
||||
const DEFAULT_FONT_SIZE_INDEX = 4;
|
||||
const ORP_COLOR_OPTIONS = ['', '#EF4444', '#3B82F6', '#22C55E', '#F97316', '#A855F7'];
|
||||
const STORAGE_KEY_FONT_SIZE = 'readest_rsvp_fontsize';
|
||||
const STORAGE_KEY_ORP_COLOR = 'readest_rsvp_orp_color';
|
||||
const STORAGE_KEY_CONTEXT = 'readest_rsvp_context';
|
||||
|
||||
interface RSVPOverlayProps {
|
||||
gridInsets: Insets;
|
||||
controller: RSVPController;
|
||||
@@ -49,6 +59,49 @@ const RSVPOverlay: React.FC<RSVPOverlayProps> = ({
|
||||
const [currentWord, setCurrentWord] = useState<RsvpWord | null>(controller.currentWord);
|
||||
const [countdown, setCountdown] = useState<number | null>(controller.currentCountdown);
|
||||
const [showChapterDropdown, setShowChapterDropdown] = useState(false);
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const [contextCollapsed, setContextCollapsed] = useState(() => {
|
||||
try {
|
||||
return localStorage.getItem(STORAGE_KEY_CONTEXT) === '1';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
const [fontSizeIndex, setFontSizeIndex] = useState(() => {
|
||||
try {
|
||||
const saved = localStorage.getItem(STORAGE_KEY_FONT_SIZE);
|
||||
if (saved !== null) {
|
||||
const idx = parseInt(saved, 10);
|
||||
if (idx >= 0 && idx < FONT_SIZE_OPTIONS.length) return idx;
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
return DEFAULT_FONT_SIZE_INDEX;
|
||||
});
|
||||
const [orpColorIndex, setOrpColorIndex] = useState(() => {
|
||||
try {
|
||||
const saved = localStorage.getItem(STORAGE_KEY_ORP_COLOR);
|
||||
if (saved !== null) {
|
||||
const idx = parseInt(saved, 10);
|
||||
if (idx >= 0 && idx < ORP_COLOR_OPTIONS.length) return idx;
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
// Context window: only rebuild the rendered word list when the current word
|
||||
// falls outside the window or nears its edge, keeping the DOM stable.
|
||||
const [contextWindow, setContextWindow] = useState(() => {
|
||||
if (!state || state.words.length === 0) return { start: 0, end: 0 };
|
||||
return {
|
||||
start: Math.max(0, state.currentIndex - 50),
|
||||
end: Math.min(state.words.length, state.currentIndex + 151),
|
||||
};
|
||||
});
|
||||
const contextWordRef = useRef<HTMLSpanElement>(null);
|
||||
const contextPanelRef = useRef<HTMLDivElement>(null);
|
||||
const touchStartX = useRef(0);
|
||||
const touchStartY = useRef(0);
|
||||
const touchStartTime = useRef(0);
|
||||
@@ -76,6 +129,27 @@ const RSVPOverlay: React.FC<RSVPOverlayProps> = ({
|
||||
const newState = (e as CustomEvent<RsvpState>).detail;
|
||||
setState(newState);
|
||||
setCurrentWord(controller.currentWord);
|
||||
|
||||
// Update context window only when current word falls outside or nears edge
|
||||
const idx = newState.currentIndex;
|
||||
const total = newState.words.length;
|
||||
setContextWindow((prev) => {
|
||||
if (total === 0) return { start: 0, end: 0 };
|
||||
// Outside window — reset
|
||||
if (idx < prev.start || idx >= prev.end) {
|
||||
return {
|
||||
start: Math.max(0, idx - 50),
|
||||
end: Math.min(total, idx + 151),
|
||||
};
|
||||
}
|
||||
// Near end of window — extend forward
|
||||
const windowSize = prev.end - prev.start;
|
||||
if (idx - prev.start > windowSize * 0.8) {
|
||||
return { start: prev.start, end: Math.min(total, prev.end + 100) };
|
||||
}
|
||||
// No change — return same reference to avoid re-render
|
||||
return prev;
|
||||
});
|
||||
};
|
||||
|
||||
const handleCountdownChange = (e: Event) => {
|
||||
@@ -174,24 +248,60 @@ const RSVPOverlay: React.FC<RSVPOverlayProps> = ({
|
||||
}
|
||||
}, [state]);
|
||||
|
||||
// Context text helpers - show 100 words before and after
|
||||
const getContextBefore = useCallback((): string => {
|
||||
if (!state || state.words.length === 0) return '';
|
||||
const startIndex = Math.max(0, state.currentIndex - 100);
|
||||
return state.words
|
||||
.slice(startIndex, state.currentIndex)
|
||||
.map((w) => w.text)
|
||||
.join(' ');
|
||||
}, [state]);
|
||||
// Stable word list that only changes when contextWindow changes
|
||||
const contextWords = useMemo(
|
||||
() => state.words.slice(contextWindow.start, contextWindow.end),
|
||||
[state.words, contextWindow],
|
||||
);
|
||||
|
||||
const getContextAfter = useCallback((): string => {
|
||||
if (!state || state.words.length === 0) return '';
|
||||
const endIndex = Math.min(state.words.length, state.currentIndex + 101);
|
||||
return state.words
|
||||
.slice(state.currentIndex + 1, endIndex)
|
||||
.map((w) => w.text)
|
||||
.join(' ');
|
||||
}, [state]);
|
||||
// Auto-scroll: keep highlighted word away from top/bottom edges
|
||||
useEffect(() => {
|
||||
const panel = contextPanelRef.current;
|
||||
const word = contextWordRef.current;
|
||||
if (contextCollapsed || !panel || !word) return;
|
||||
|
||||
const panelRect = panel.getBoundingClientRect();
|
||||
const wordRect = word.getBoundingClientRect();
|
||||
const margin = panelRect.height * 0.15;
|
||||
const topLine = panelRect.top + margin;
|
||||
|
||||
if (wordRect.top < topLine) {
|
||||
panel.scrollTop -= topLine - wordRect.top;
|
||||
} else if (wordRect.bottom > panelRect.bottom - margin) {
|
||||
panel.scrollTop += wordRect.top - topLine;
|
||||
}
|
||||
}, [state.currentIndex, contextCollapsed]);
|
||||
|
||||
const toggleContext = useCallback(() => {
|
||||
setContextCollapsed((prev) => {
|
||||
const next = !prev;
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY_CONTEXT, next ? '1' : '0');
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const updateFontSize = useCallback((idx: number) => {
|
||||
const clamped = Math.max(0, Math.min(FONT_SIZE_OPTIONS.length - 1, idx));
|
||||
setFontSizeIndex(clamped);
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY_FONT_SIZE, String(clamped));
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}, []);
|
||||
|
||||
const updateOrpColor = useCallback((idx: number) => {
|
||||
setOrpColorIndex(idx);
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY_ORP_COLOR, String(idx));
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Chapter helpers
|
||||
const getCurrentChapterLabel = useCallback((): string => {
|
||||
@@ -290,6 +400,9 @@ const RSVPOverlay: React.FC<RSVPOverlayProps> = ({
|
||||
const bgColor = themeCode.bg;
|
||||
const fgColor = themeCode.fg;
|
||||
const accentColor = themeCode.primary;
|
||||
const effectiveOrpColor = ORP_COLOR_OPTIONS[orpColorIndex] || accentColor;
|
||||
const currentFontSize =
|
||||
FONT_SIZE_OPTIONS[fontSizeIndex] ?? FONT_SIZE_OPTIONS[DEFAULT_FONT_SIZE_INDEX]!;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -373,35 +486,60 @@ const RSVPOverlay: React.FC<RSVPOverlayProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Context panel (shown when paused) */}
|
||||
{!state.playing && countdown === null && (
|
||||
<div className='mx-3 max-h-[25vh] overflow-y-auto rounded-lg border border-gray-500/20 bg-gray-500/10 p-3 md:mx-4 md:max-h-[30vh] md:rounded-xl md:p-4'>
|
||||
<div className='mb-2 flex items-center gap-2 text-xs font-semibold uppercase tracking-wide opacity-60 md:mb-3'>
|
||||
<svg
|
||||
width='14'
|
||||
height='14'
|
||||
viewBox='0 0 24 24'
|
||||
fill='none'
|
||||
stroke='currentColor'
|
||||
strokeWidth='2'
|
||||
className='md:h-4 md:w-4'
|
||||
>
|
||||
<path d='M4 6h16M4 12h16M4 18h10' />
|
||||
</svg>
|
||||
<span>{_('Context')}</span>
|
||||
{/* Context panel (always visible, collapsible) */}
|
||||
<div className='mx-3 overflow-hidden rounded-lg border border-gray-500/20 bg-gray-500/10 md:mx-4 md:rounded-xl'>
|
||||
<button
|
||||
className='flex w-full items-center gap-2 px-3 py-2 text-xs font-semibold uppercase tracking-wide opacity-60 transition-opacity hover:opacity-80 md:px-4 md:py-3'
|
||||
onClick={toggleContext}
|
||||
aria-expanded={!contextCollapsed}
|
||||
aria-label={contextCollapsed ? _('Show context') : _('Hide context')}
|
||||
>
|
||||
<svg
|
||||
width='14'
|
||||
height='14'
|
||||
viewBox='0 0 24 24'
|
||||
fill='none'
|
||||
stroke='currentColor'
|
||||
strokeWidth='2'
|
||||
className='md:h-4 md:w-4'
|
||||
>
|
||||
<path d='M4 6h16M4 12h16M4 18h10' />
|
||||
</svg>
|
||||
<span className='flex-1 text-left'>{_('Context')}</span>
|
||||
<IoChevronDown
|
||||
className={clsx(
|
||||
'h-3.5 w-3.5 transition-transform duration-200',
|
||||
!contextCollapsed && 'rotate-180',
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
{!contextCollapsed && (
|
||||
<div
|
||||
ref={contextPanelRef}
|
||||
className='max-h-[20vh] overflow-y-auto px-3 pb-3 md:px-4 md:pb-4'
|
||||
>
|
||||
<div className='text-left text-base leading-relaxed md:text-lg'>
|
||||
{contextWords.map((w, i) => {
|
||||
const wordIndex = contextWindow.start + i;
|
||||
const isCurrent = wordIndex === state.currentIndex;
|
||||
return (
|
||||
<span
|
||||
key={wordIndex}
|
||||
ref={isCurrent ? contextWordRef : undefined}
|
||||
className={isCurrent ? undefined : 'opacity-70'}
|
||||
style={isCurrent ? { color: effectiveOrpColor } : undefined}
|
||||
>
|
||||
{w.text}{' '}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className='text-left text-base leading-relaxed md:text-lg'>
|
||||
<span className='opacity-70'>{getContextBefore()} </span>
|
||||
<span className='font-semibold' style={{ color: accentColor }}>
|
||||
{currentWord?.text || ''}
|
||||
</span>
|
||||
<span className='opacity-70'> {getContextAfter()}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Main content area */}
|
||||
<div className='flex flex-1 flex-col items-center justify-center p-4 md:p-8'>
|
||||
<div className='flex flex-1 flex-col items-center justify-center p-4 md:p-6'>
|
||||
<div className='flex h-full w-full flex-col items-center justify-center'>
|
||||
<div className='flex h-full w-full flex-col items-center'>
|
||||
{/* Top guide line */}
|
||||
@@ -422,16 +560,22 @@ const RSVPOverlay: React.FC<RSVPOverlayProps> = ({
|
||||
)}
|
||||
|
||||
{/* Word display */}
|
||||
<div className='relative flex min-h-16 w-full items-center justify-center whitespace-nowrap px-2 py-4 font-mono text-2xl font-medium tracking-wide sm:min-h-20 sm:px-4 sm:py-6 sm:text-3xl md:text-4xl lg:text-5xl'>
|
||||
<div
|
||||
className='rsvp-word relative flex min-h-16 w-full items-center justify-center whitespace-nowrap px-2 py-2 font-mono font-medium leading-none tracking-wide sm:min-h-20 sm:px-4 sm:py-4'
|
||||
style={{ fontSize: `${currentFontSize}rem` }}
|
||||
>
|
||||
{currentWord ? (
|
||||
<>
|
||||
<span className='absolute right-[calc(50%+0.3em)] text-right opacity-60'>
|
||||
<span className='rsvp-word-before absolute right-[calc(50%+0.3em)] text-right opacity-60'>
|
||||
{wordBefore}
|
||||
</span>
|
||||
<span className='relative z-10 font-bold' style={{ color: accentColor }}>
|
||||
<span
|
||||
className='rsvp-word-orp relative z-10 font-bold'
|
||||
style={{ color: effectiveOrpColor }}
|
||||
>
|
||||
{orpChar}
|
||||
</span>
|
||||
<span className='absolute left-[calc(50%+0.3em)] text-left opacity-60'>
|
||||
<span className='rsvp-word-after absolute left-[calc(50%+0.3em)] text-left opacity-60'>
|
||||
{wordAfter}
|
||||
</span>
|
||||
</>
|
||||
@@ -494,96 +638,140 @@ const RSVPOverlay: React.FC<RSVPOverlayProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Controls */}
|
||||
<div className='flex flex-col gap-3 md:flex-row md:items-center md:justify-between md:gap-4'>
|
||||
{/* Playback controls - centered on mobile, middle on desktop */}
|
||||
<div className='flex items-center justify-center gap-2 md:order-2 md:gap-4'>
|
||||
<button
|
||||
aria-label={_('Skip back 15 words')}
|
||||
className='flex cursor-pointer items-center gap-1 rounded-full border-none bg-transparent px-2 py-1.5 transition-colors hover:bg-gray-500/20 active:scale-95 md:px-3 md:py-2'
|
||||
onClick={() => controller.skipBackward(15)}
|
||||
title={_('Back 15 words (Shift+Left)')}
|
||||
>
|
||||
<span className='text-xs font-semibold opacity-80'>15</span>
|
||||
<IoPlaySkipBack className='h-5 w-5 md:h-6 md:w-6' />
|
||||
</button>
|
||||
{/* Playback controls */}
|
||||
<div className='relative flex items-center justify-center gap-1 md:gap-2'>
|
||||
<button
|
||||
aria-label={_('Skip back 15 words')}
|
||||
className='flex cursor-pointer items-center gap-0.5 rounded-full border-none bg-transparent px-2 py-1.5 transition-colors hover:bg-gray-500/20 active:scale-95'
|
||||
onClick={() => controller.skipBackward(15)}
|
||||
title={_('Back 15 words (Shift+Left)')}
|
||||
>
|
||||
<span className='text-xs font-semibold opacity-80'>15</span>
|
||||
<IoPlaySkipBack className='h-5 w-5 md:h-6 md:w-6' />
|
||||
</button>
|
||||
|
||||
<button
|
||||
aria-label={state.playing ? _('Pause') : _('Play')}
|
||||
className={clsx(
|
||||
'flex h-14 w-14 cursor-pointer items-center justify-center rounded-full border-none bg-gray-500/15 transition-colors hover:bg-gray-500/25 active:scale-95 md:h-16 md:w-16',
|
||||
state.playing ? '' : 'ps-1',
|
||||
)}
|
||||
onClick={() => controller.togglePlayPause()}
|
||||
title={state.playing ? _('Pause (Space)') : _('Play (Space)')}
|
||||
>
|
||||
{state.playing ? (
|
||||
<IoPause className='h-7 w-7 md:h-8 md:w-8' />
|
||||
) : (
|
||||
<IoPlay className='h-7 w-7 md:h-8 md:w-8' />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
aria-label={_('Decrease speed')}
|
||||
className='flex h-9 w-9 cursor-pointer items-center justify-center rounded-full border-none bg-transparent transition-colors hover:bg-gray-500/20 active:scale-95'
|
||||
onClick={() => controller.decreaseSpeed()}
|
||||
title={_('Slower (Left/Down)')}
|
||||
>
|
||||
<IoRemove className='h-4 w-4 md:h-5 md:w-5' />
|
||||
</button>
|
||||
|
||||
<button
|
||||
aria-label={_('Skip forward 15 words')}
|
||||
className='flex cursor-pointer items-center gap-1 rounded-full border-none bg-transparent px-2 py-1.5 transition-colors hover:bg-gray-500/20 active:scale-95 md:px-3 md:py-2'
|
||||
onClick={() => controller.skipForward(15)}
|
||||
title={_('Forward 15 words (Shift+Right)')}
|
||||
>
|
||||
<IoPlaySkipForward className='h-5 w-5 md:h-6 md:w-6' />
|
||||
<span className='text-xs font-semibold opacity-80'>15</span>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
aria-label={state.playing ? _('Pause') : _('Play')}
|
||||
className={clsx(
|
||||
'flex h-14 w-14 cursor-pointer items-center justify-center rounded-full border-none bg-gray-500/15 transition-colors hover:bg-gray-500/25 active:scale-95 md:h-16 md:w-16',
|
||||
state.playing ? '' : 'ps-1',
|
||||
)}
|
||||
onClick={() => controller.togglePlayPause()}
|
||||
title={state.playing ? _('Pause (Space)') : _('Play (Space)')}
|
||||
>
|
||||
{state.playing ? (
|
||||
<IoPause className='h-7 w-7 md:h-8 md:w-8' />
|
||||
) : (
|
||||
<IoPlay className='h-7 w-7 md:h-8 md:w-8' />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Secondary controls row on mobile, split on desktop */}
|
||||
<div className='flex items-center justify-between gap-4 md:contents'>
|
||||
{/* Punctuation pause - left on desktop */}
|
||||
<div className='flex items-center md:order-1 md:min-w-[140px] md:flex-1'>
|
||||
<label className='flex cursor-pointer items-center gap-1.5 text-xs font-medium opacity-80 md:gap-2'>
|
||||
<span className='hidden sm:inline'>{_('Pause:')}</span>
|
||||
<span className='sm:hidden'>{_('Pause:')}</span>
|
||||
<select
|
||||
className='cursor-pointer rounded border border-gray-500/30 bg-gray-500/20 px-1.5 py-1 text-xs font-medium transition-colors hover:border-gray-500/40 hover:bg-gray-500/30 md:px-2'
|
||||
style={{ color: 'inherit' }}
|
||||
value={state.punctuationPauseMs}
|
||||
onChange={(e) => controller.setPunctuationPause(parseInt(e.target.value, 10))}
|
||||
>
|
||||
{controller.getPunctuationPauseOptions().map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}ms
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
aria-label={_('Increase speed')}
|
||||
className='flex h-9 w-9 cursor-pointer items-center justify-center rounded-full border-none bg-transparent transition-colors hover:bg-gray-500/20 active:scale-95'
|
||||
onClick={() => controller.increaseSpeed()}
|
||||
title={_('Faster (Right/Up)')}
|
||||
>
|
||||
<IoAdd className='h-4 w-4 md:h-5 md:w-5' />
|
||||
</button>
|
||||
|
||||
{/* Speed controls - right on desktop */}
|
||||
<div className='flex items-center justify-end gap-1.5 md:order-3 md:min-w-[140px] md:flex-1 md:gap-2'>
|
||||
<button
|
||||
aria-label={_('Skip forward 15 words')}
|
||||
className='flex cursor-pointer items-center gap-0.5 rounded-full border-none bg-transparent px-2 py-1.5 transition-colors hover:bg-gray-500/20 active:scale-95'
|
||||
onClick={() => controller.skipForward(15)}
|
||||
title={_('Forward 15 words (Shift+Right)')}
|
||||
>
|
||||
<IoPlaySkipForward className='h-5 w-5 md:h-6 md:w-6' />
|
||||
<span className='text-xs font-semibold opacity-80'>15</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
aria-label={_('Settings')}
|
||||
className={clsx(
|
||||
'absolute right-0 flex h-9 w-9 cursor-pointer items-center justify-center rounded-full border-none bg-transparent transition-colors hover:bg-gray-500/20 active:scale-95',
|
||||
showSettings && 'bg-gray-500/15',
|
||||
)}
|
||||
onClick={() => setShowSettings((prev) => !prev)}
|
||||
title={_('Settings')}
|
||||
>
|
||||
<IoSettingsSharp className='h-4 w-4 md:h-5 md:w-5' />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Settings row (collapsible) */}
|
||||
{showSettings && (
|
||||
<div className='mt-3 flex flex-wrap items-center justify-evenly gap-x-8 gap-y-4 text-xs md:justify-center'>
|
||||
{/* Punctuation pause */}
|
||||
<label className='flex cursor-pointer items-center gap-1.5 font-medium opacity-80'>
|
||||
<span className='mr-0.5 font-medium opacity-50'>{_('Pause')}</span>
|
||||
<select
|
||||
className='cursor-pointer rounded border border-gray-500/30 bg-gray-500/20 px-1.5 py-1 text-xs font-medium transition-colors hover:border-gray-500/40 hover:bg-gray-500/30'
|
||||
style={{ color: 'inherit' }}
|
||||
value={state.punctuationPauseMs}
|
||||
onChange={(e) => controller.setPunctuationPause(parseInt(e.target.value, 10))}
|
||||
>
|
||||
{controller.getPunctuationPauseOptions().map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}ms
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
{/* Font size */}
|
||||
<div className='flex items-center gap-0.5'>
|
||||
<span className='mr-0.5 font-medium opacity-50'>{_('Font')}</span>
|
||||
<button
|
||||
aria-label={_('Decrease speed')}
|
||||
className='flex h-9 w-9 cursor-pointer items-center justify-center rounded-full border-none bg-transparent transition-colors hover:bg-gray-500/20 active:scale-95 md:h-10 md:w-10'
|
||||
onClick={() => controller.decreaseSpeed()}
|
||||
title={_('Slower (Left/Down)')}
|
||||
aria-label={_('Decrease font size')}
|
||||
className='flex h-6 w-6 cursor-pointer items-center justify-center rounded-full border-none bg-transparent transition-colors hover:bg-gray-500/20 active:scale-95'
|
||||
onClick={() => updateFontSize(fontSizeIndex - 1)}
|
||||
disabled={fontSizeIndex <= 0}
|
||||
>
|
||||
<IoRemove className='h-4 w-4 md:h-5 md:w-5' />
|
||||
<IoRemove className='h-3 w-3' />
|
||||
</button>
|
||||
<span
|
||||
aria-label={_('Current speed')}
|
||||
className='min-w-10 text-center text-sm font-medium md:min-w-12'
|
||||
>
|
||||
{state.wpm}
|
||||
<span className='min-w-4 text-center font-medium tabular-nums'>
|
||||
{fontSizeIndex + 1}
|
||||
</span>
|
||||
<button
|
||||
aria-label={_('Increase speed')}
|
||||
className='flex h-9 w-9 cursor-pointer items-center justify-center rounded-full border-none bg-transparent transition-colors hover:bg-gray-500/20 active:scale-95 md:h-10 md:w-10'
|
||||
onClick={() => controller.increaseSpeed()}
|
||||
title={_('Faster (Right/Up)')}
|
||||
aria-label={_('Increase font size')}
|
||||
className='flex h-6 w-6 cursor-pointer items-center justify-center rounded-full border-none bg-transparent transition-colors hover:bg-gray-500/20 active:scale-95'
|
||||
onClick={() => updateFontSize(fontSizeIndex + 1)}
|
||||
disabled={fontSizeIndex >= FONT_SIZE_OPTIONS.length - 1}
|
||||
>
|
||||
<IoAdd className='h-4 w-4 md:h-5 md:w-5' />
|
||||
<IoAdd className='h-3 w-3' />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* ORP color */}
|
||||
<div className='flex items-center gap-1.5'>
|
||||
<span className='mr-0.5 font-medium opacity-50'>{_('Focus')}</span>
|
||||
{ORP_COLOR_OPTIONS.map((color, idx) => (
|
||||
<button
|
||||
key={idx}
|
||||
onClick={() => updateOrpColor(idx)}
|
||||
className={clsx(
|
||||
'h-6 min-h-6 w-6 min-w-6 rounded-full border-2 transition-transform',
|
||||
orpColorIndex === idx
|
||||
? 'scale-110 border-current'
|
||||
: 'border-transparent hover:scale-105',
|
||||
)}
|
||||
style={{ backgroundColor: color || accentColor }}
|
||||
aria-label={idx === 0 ? _('Theme color') : `Color ${idx}`}
|
||||
title={idx === 0 ? _('Theme color') : undefined}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ export type Insets = {
|
||||
right: number;
|
||||
};
|
||||
|
||||
export interface LocaleWithTextInfo extends Intl.Locale {
|
||||
export type LocaleWithTextInfo = Intl.Locale & {
|
||||
getTextInfo?: () => { direction: string };
|
||||
textInfo?: { direction: string };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -72,9 +72,9 @@ export const handleA11yNavigation = (
|
||||
const skipLink = document.createElement('div');
|
||||
skipLink.id = skipLinkId;
|
||||
skipLink.setAttribute('cfi-inert', '');
|
||||
skipLink.setAttribute('aria-hidden', 'false');
|
||||
skipLink.setAttribute('tabindex', '0');
|
||||
skipLink.textContent = options?.skipToLastPosLabel ?? '';
|
||||
skipLink.setAttribute('aria-hidden', 'false');
|
||||
skipLink.setAttribute('aria-label', options?.skipToLastPosLabel ?? '');
|
||||
Object.assign(skipLink.style, {
|
||||
position: 'absolute',
|
||||
left: '0px',
|
||||
|
||||
Generated
+73
@@ -438,6 +438,9 @@ importers:
|
||||
'@typescript-eslint/parser':
|
||||
specifier: ^8.48.0
|
||||
version: 8.53.1(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
|
||||
'@typescript/native-preview':
|
||||
specifier: 7.0.0-dev.20260312.1
|
||||
version: 7.0.0-dev.20260312.1
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^5.1.1
|
||||
version: 5.1.2(vite@7.3.1(@types/node@22.19.7)(jiti@1.21.7)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
|
||||
@@ -4314,6 +4317,45 @@ packages:
|
||||
resolution: {integrity: sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@typescript/native-preview-darwin-arm64@7.0.0-dev.20260312.1':
|
||||
resolution: {integrity: sha512-AhPdPuVe4osxWoeImS21jVhc0VJ2QnzLUZtEFMakY0Rf70C0b6il/m7hwRf9wkr9xXZLVOVJ1kYrpvQRuHFE0Q==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@typescript/native-preview-darwin-x64@7.0.0-dev.20260312.1':
|
||||
resolution: {integrity: sha512-9I0P1/c/mQ6UVcQq7SYY/FJD23IN5T2y4GbSFOKQvzNVASV0tMnX4YV8YNf6b5jcwCzrVcrGNKKgWCj8xEFf8Q==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@typescript/native-preview-linux-arm64@7.0.0-dev.20260312.1':
|
||||
resolution: {integrity: sha512-xwoMywagcvx9F2ocM+ybeg7eH9PHDpx1FBGOrloL1/xkGC4BCrn/RcaAe0AhzXzoJfHHmg7Sz9VzYmTR4N1Kqw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@typescript/native-preview-linux-arm@7.0.0-dev.20260312.1':
|
||||
resolution: {integrity: sha512-/nAOhSLTxMJfHY+2cKdUxi2wYadf3g1GtC3VzgPfZMNxA28dJ8x75T26aSLaFYluh7cCSAwuGesCImijQDS2Lw==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@typescript/native-preview-linux-x64@7.0.0-dev.20260312.1':
|
||||
resolution: {integrity: sha512-vZs0LLpZw50Ac0TCmF9ND7KphJBhOfp9fxLhC+hFWaUU1iCQRjv1MtvroitF5OJKb21qFPJxkU+kfhlCRxLfqg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@typescript/native-preview-win32-arm64@7.0.0-dev.20260312.1':
|
||||
resolution: {integrity: sha512-4LY/gd9cj1xDY2nEthB7WDW4j/fIYJ9wp9H71nOLd0wNNtkfqRXWSkQEeb+RByhV+dIb/n6kWbQQMeNfk7q4VQ==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@typescript/native-preview-win32-x64@7.0.0-dev.20260312.1':
|
||||
resolution: {integrity: sha512-EP2JPo9s9EPUwXSX83qTImlDHhgkLeBbJ2MMdj+XrfBltHAvHKktzeSS73UhP77s/TnTkJR6BTWHENKKvLRbGQ==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@typescript/native-preview@7.0.0-dev.20260312.1':
|
||||
resolution: {integrity: sha512-FwhlXG/yG0d7b2UmooBYyszLMpICRYdYGE6v65ZlMnH7cWKQyyFpMFgH9suRf3Np4QCbN+7qisj+F23kQOidVw==}
|
||||
hasBin: true
|
||||
|
||||
'@ungap/structured-clone@1.3.0':
|
||||
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
||||
|
||||
@@ -14494,6 +14536,37 @@ snapshots:
|
||||
'@typescript-eslint/types': 8.53.1
|
||||
eslint-visitor-keys: 4.2.1
|
||||
|
||||
'@typescript/native-preview-darwin-arm64@7.0.0-dev.20260312.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview-darwin-x64@7.0.0-dev.20260312.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview-linux-arm64@7.0.0-dev.20260312.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview-linux-arm@7.0.0-dev.20260312.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview-linux-x64@7.0.0-dev.20260312.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview-win32-arm64@7.0.0-dev.20260312.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview-win32-x64@7.0.0-dev.20260312.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview@7.0.0-dev.20260312.1':
|
||||
optionalDependencies:
|
||||
'@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260312.1
|
||||
'@typescript/native-preview-darwin-x64': 7.0.0-dev.20260312.1
|
||||
'@typescript/native-preview-linux-arm': 7.0.0-dev.20260312.1
|
||||
'@typescript/native-preview-linux-arm64': 7.0.0-dev.20260312.1
|
||||
'@typescript/native-preview-linux-x64': 7.0.0-dev.20260312.1
|
||||
'@typescript/native-preview-win32-arm64': 7.0.0-dev.20260312.1
|
||||
'@typescript/native-preview-win32-x64': 7.0.0-dev.20260312.1
|
||||
|
||||
'@ungap/structured-clone@1.3.0': {}
|
||||
|
||||
'@unpic/core@1.0.3':
|
||||
|
||||
Reference in New Issue
Block a user