forked from akai/readest
fix(sync): cloud-sync settings polish + temporary premium ungate (#4828)
* fix(settings): clamp option-row description to a single line SettingsRow descriptions wrapped to multiple lines on narrow (mobile) widths, giving boxed-list rows uneven heights (e.g. "Uploads book files to your other devices." in the Cloud Sync panel). Clamp the description to one line with ellipsis in the shared primitive so every option row stays uniform; the description is a hint, not a paragraph (longer copy belongs in a Tips block). Codified in DESIGN.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * i18n(settings): shorten sync strategy labels to "Send only" / "Receive only" Rename the Sync Strategy options (shared by the Cloud Sync and KOReader Sync forms). Keys renamed in every locale, preserving existing translations. * feat(sync): temporarily ungate third-party cloud sync from premium Cloud sync (WebDAV / Google Drive) ships available to every plan, incl. free, while the feature stabilises. Gated behind a single CLOUD_SYNC_REQUIRES_PREMIUM flag (off) via isCloudSyncAllowed; the paywall code (CLOUD_SYNC_PLANS / isCloudSyncInPlan) is intact, so re-gating in an upcoming release is a one-line flip. Applies to the Settings provider rows and the reader auto-sync gate. * fix(settings): polish cloud-sync connect buttons Use btn-contrast for the WebDAV and Google Drive Connect CTAs (theme-neutral, e-ink correct); rename "Connect Google Drive" to "Connect"; move the Google Drive sign-in tips below the Connect button. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -448,7 +448,11 @@ the mobile bump. Instead:
|
||||
system fonts.
|
||||
- **Secondary text** (SettingsRow description, NavigationRow status, Tips
|
||||
body, BoxedList description): use `text-[0.85em]` so it stays
|
||||
proportional (≈12px desktop, ≈13.6px mobile).
|
||||
proportional (≈12px desktop, ≈13.6px mobile). A **`SettingsRow`
|
||||
description is clamped to a single line** (`line-clamp-1`, ellipsis on
|
||||
overflow) by the primitive — keep it short enough to read on one line at
|
||||
mobile width; it is a hint, not a paragraph. Move anything longer into a
|
||||
`<Tips>` block below the list.
|
||||
- **Form controls** (`<input>`, `<select>`): browsers don't inherit
|
||||
font-size onto form elements, so add the `settings-content` class
|
||||
_directly on the element_ to re-apply the 14/16 cascade. The legacy
|
||||
|
||||
@@ -480,8 +480,8 @@
|
||||
"Ask on conflict": "اسأل عند حدوث تعارض",
|
||||
"Always use latest": "استخدام الأحدث دائمًا",
|
||||
"Send and receive": "إرسال واستلام",
|
||||
"Send changes only": "إرسال التغييرات فقط",
|
||||
"Receive changes only": "استلام التغييرات فقط",
|
||||
"Send only": "إرسال التغييرات فقط",
|
||||
"Receive only": "استلام التغييرات فقط",
|
||||
"Checksum Method": "طريقة التحقق من الصحة",
|
||||
"File Name": "اسم الملف",
|
||||
"Device Name": "اسم الجهاز",
|
||||
|
||||
@@ -165,8 +165,8 @@
|
||||
"Ask on conflict": "দ্বন্দ্বে জিজ্ঞাসা করুন",
|
||||
"Always use latest": "সবসময় সর্বশেষ ব্যবহার করুন",
|
||||
"Send and receive": "পাঠানো এবং গ্রহণ করা",
|
||||
"Send changes only": "শুধু পরিবর্তন পাঠান",
|
||||
"Receive changes only": "শুধু পরিবর্তন গ্রহণ করুন",
|
||||
"Send only": "শুধু পরিবর্তন পাঠান",
|
||||
"Receive only": "শুধু পরিবর্তন গ্রহণ করুন",
|
||||
"Checksum Method": "চেকসাম পদ্ধতি",
|
||||
"File Name": "ফাইলের নাম",
|
||||
"Device Name": "ডিভাইসের নাম",
|
||||
|
||||
@@ -462,8 +462,8 @@
|
||||
"Ask on conflict": "འབྱུང་ཁུངས་འདེམས་པ་བྱས་ནས་བརྗེ་བ།",
|
||||
"Always use latest": "དེ་ལས་འབྱུང་ཁུངས་འདེམས་པ་བྱས་ནས་བརྗེ་བ།",
|
||||
"Send and receive": "གཏོང་བ་དང་ལེན་པ།",
|
||||
"Send changes only": "འབྱུང་ཁུངས་འདེམས་པ་བྱས་ནས་བརྗེ་བ།",
|
||||
"Receive changes only": "འབྱུང་ཁུངས་འདེམས་པ་བྱས་ནས་བརྗེ་བ།",
|
||||
"Send only": "འབྱུང་ཁུངས་འདེམས་པ་བྱས་ནས་བརྗེ་བ།",
|
||||
"Receive only": "འབྱུང་ཁུངས་འདེམས་པ་བྱས་ནས་བརྗེ་བ།",
|
||||
"Checksum Method": "ཡིག་སྣེ་འབྱུང་ཁུངས།",
|
||||
"File Name": "ཡིག་སྣེ་འབྱུང་ཁུངས།",
|
||||
"Device Name": "ཡིག་སྣེ་འབྱུང་ཁུངས།",
|
||||
|
||||
@@ -464,8 +464,8 @@
|
||||
"Ask on conflict": "Bei Konflikten fragen",
|
||||
"Always use latest": "Immer die neueste Version verwenden",
|
||||
"Send and receive": "Senden und empfangen",
|
||||
"Send changes only": "Nur Änderungen senden",
|
||||
"Receive changes only": "Nur Änderungen empfangen",
|
||||
"Send only": "Nur Änderungen senden",
|
||||
"Receive only": "Nur Änderungen empfangen",
|
||||
"Checksum Method": "Prüfziffernverfahren",
|
||||
"File Name": "Dateiname",
|
||||
"Device Name": "Gerätename",
|
||||
|
||||
@@ -464,8 +464,8 @@
|
||||
"Ask on conflict": "Ρώτησε σε περίπτωση σύγκρουσης",
|
||||
"Always use latest": "Χρησιμοποίησε πάντα την τελευταία έκδοση",
|
||||
"Send and receive": "Αποστολή και λήψη",
|
||||
"Send changes only": "Αποστολή μόνο αλλαγών",
|
||||
"Receive changes only": "Λήψη μόνο αλλαγών",
|
||||
"Send only": "Αποστολή μόνο αλλαγών",
|
||||
"Receive only": "Λήψη μόνο αλλαγών",
|
||||
"Checksum Method": "Μέθοδος ελέγχου",
|
||||
"File Name": "Όνομα αρχείου",
|
||||
"Device Name": "Όνομα συσκευής",
|
||||
|
||||
@@ -82,8 +82,8 @@
|
||||
"Ask on conflict": "Preguntar en caso de conflicto",
|
||||
"Always use latest": "Usar siempre el más reciente",
|
||||
"Send and receive": "Enviar y recibir",
|
||||
"Send changes only": "Solo enviar cambios",
|
||||
"Receive changes only": "Solo recibir cambios",
|
||||
"Send only": "Solo enviar cambios",
|
||||
"Receive only": "Solo recibir cambios",
|
||||
"Checksum Method": "Método de identificación",
|
||||
"File Name": "Nombre del archivo",
|
||||
"Device Name": "Nombre del dispositivo",
|
||||
|
||||
@@ -464,8 +464,8 @@
|
||||
"Ask on conflict": "پرسش هنگام تداخل",
|
||||
"Always use latest": "همیشه از آخرین استفاده شود",
|
||||
"Send and receive": "ارسال و دریافت",
|
||||
"Send changes only": "فقط ارسال تغییرات",
|
||||
"Receive changes only": "فقط دریافت تغییرات",
|
||||
"Send only": "فقط ارسال تغییرات",
|
||||
"Receive only": "فقط دریافت تغییرات",
|
||||
"Checksum Method": "روش Checksum",
|
||||
"File Name": "نام فایل",
|
||||
"Device Name": "نام دستگاه",
|
||||
|
||||
@@ -468,8 +468,8 @@
|
||||
"Ask on conflict": "Demander en cas de conflit",
|
||||
"Always use latest": "Toujours utiliser la dernière version",
|
||||
"Send and receive": "Envoyer et recevoir",
|
||||
"Send changes only": "Envoyer uniquement les modifications",
|
||||
"Receive changes only": "Recevoir uniquement les modifications",
|
||||
"Send only": "Envoyer uniquement les modifications",
|
||||
"Receive only": "Recevoir uniquement les modifications",
|
||||
"Checksum Method": "Méthode de somme de contrôle",
|
||||
"File Name": "Nom du fichier",
|
||||
"Device Name": "Nom de l'appareil",
|
||||
|
||||
@@ -424,8 +424,8 @@
|
||||
"Ask on conflict": "שאל בעת קונפליקט",
|
||||
"Always use latest": "תמיד השתמש בהכי חדש",
|
||||
"Send and receive": "שליחה וקבלה",
|
||||
"Send changes only": "שלח שינויים בלבד",
|
||||
"Receive changes only": "קבל שינויים בלבד",
|
||||
"Send only": "שלח שינויים בלבד",
|
||||
"Receive only": "קבל שינויים בלבד",
|
||||
"Checksum Method": "שיטת סיכום ביקורת (Checksum)",
|
||||
"File Name": "שם קובץ",
|
||||
"Device Name": "שם מכשיר",
|
||||
|
||||
@@ -464,8 +464,8 @@
|
||||
"Ask on conflict": "संघर्ष पर पूछें",
|
||||
"Always use latest": "हमेशा नवीनतम का उपयोग करें",
|
||||
"Send and receive": "भेजें और प्राप्त करें",
|
||||
"Send changes only": "केवल परिवर्तन भेजें",
|
||||
"Receive changes only": "केवल परिवर्तन प्राप्त करें",
|
||||
"Send only": "केवल परिवर्तन भेजें",
|
||||
"Receive only": "केवल परिवर्तन प्राप्त करें",
|
||||
"Checksum Method": "चेकसम विधि",
|
||||
"File Name": "फाइल नाम",
|
||||
"Device Name": "डिवाइस नाम",
|
||||
|
||||
@@ -470,8 +470,8 @@
|
||||
"Ask on conflict": "Kérdezzen ütközéskor",
|
||||
"Always use latest": "Mindig a legújabbat használja",
|
||||
"Send and receive": "Küldés és fogadás",
|
||||
"Send changes only": "Csak módosítások küldése",
|
||||
"Receive changes only": "Csak módosítások fogadása",
|
||||
"Send only": "Csak módosítások küldése",
|
||||
"Receive only": "Csak módosítások fogadása",
|
||||
"Checksum Method": "Ellenőrzőösszeg-módszer",
|
||||
"File Name": "Fájlnév",
|
||||
"Device Name": "Eszköznév",
|
||||
|
||||
@@ -460,8 +460,8 @@
|
||||
"Ask on conflict": "Tanya saat terjadi konflik",
|
||||
"Always use latest": "Selalu gunakan yang terbaru",
|
||||
"Send and receive": "Kirim dan terima",
|
||||
"Send changes only": "Kirim perubahan saja",
|
||||
"Receive changes only": "Terima perubahan saja",
|
||||
"Send only": "Kirim perubahan saja",
|
||||
"Receive only": "Terima perubahan saja",
|
||||
"Checksum Method": "Metode Checksum",
|
||||
"File Name": "Nama File",
|
||||
"Device Name": "Nama Perangkat",
|
||||
|
||||
@@ -468,8 +468,8 @@
|
||||
"Ask on conflict": "Chiedi in caso di conflitto",
|
||||
"Always use latest": "Usa sempre l'ultima versione",
|
||||
"Send and receive": "Invia e ricevi",
|
||||
"Send changes only": "Invia solo le modifiche",
|
||||
"Receive changes only": "Ricevi solo le modifiche",
|
||||
"Send only": "Invia solo le modifiche",
|
||||
"Receive only": "Ricevi solo le modifiche",
|
||||
"Checksum Method": "Metodo di Controllo",
|
||||
"File Name": "Nome del File",
|
||||
"Device Name": "Nome del Dispositivo",
|
||||
|
||||
@@ -460,8 +460,8 @@
|
||||
"Ask on conflict": "競合時に確認",
|
||||
"Always use latest": "最新を常に使用",
|
||||
"Send and receive": "送信と受信",
|
||||
"Send changes only": "変更のみを送信",
|
||||
"Receive changes only": "変更のみを受信",
|
||||
"Send only": "変更のみを送信",
|
||||
"Receive only": "変更のみを受信",
|
||||
"Checksum Method": "チェックサム方式",
|
||||
"File Name": "ファイル名",
|
||||
"Device Name": "デバイス名",
|
||||
|
||||
@@ -460,8 +460,8 @@
|
||||
"Ask on conflict": "충돌 시 묻기",
|
||||
"Always use latest": "항상 최신 버전 사용",
|
||||
"Send and receive": "전송 및 수신",
|
||||
"Send changes only": "변경 사항만 전송",
|
||||
"Receive changes only": "변경 사항만 수신",
|
||||
"Send only": "변경 사항만 전송",
|
||||
"Receive only": "변경 사항만 수신",
|
||||
"Checksum Method": "체크섬 방법",
|
||||
"File Name": "파일 이름",
|
||||
"Device Name": "장치 이름",
|
||||
|
||||
@@ -222,8 +222,8 @@
|
||||
"Ask on conflict": "Tanya jika konflik",
|
||||
"Always use latest": "Sentiasa guna terkini",
|
||||
"Send and receive": "Hantar dan terima",
|
||||
"Send changes only": "Hantar perubahan sahaja",
|
||||
"Receive changes only": "Terima perubahan sahaja",
|
||||
"Send only": "Hantar perubahan sahaja",
|
||||
"Receive only": "Terima perubahan sahaja",
|
||||
"Checksum Method": "Kaedah Semak Jumlah",
|
||||
"File Name": "Nama Fail",
|
||||
"Device Name": "Nama Peranti",
|
||||
|
||||
@@ -464,8 +464,8 @@
|
||||
"Ask on conflict": "Vraag bij conflict",
|
||||
"Always use latest": "Altijd de nieuwste gebruiken",
|
||||
"Send and receive": "Verzenden en ontvangen",
|
||||
"Send changes only": "Verzend alleen wijzigingen",
|
||||
"Receive changes only": "Ontvang alleen wijzigingen",
|
||||
"Send only": "Verzend alleen wijzigingen",
|
||||
"Receive only": "Ontvang alleen wijzigingen",
|
||||
"Checksum Method": "Checksum-methode",
|
||||
"File Name": "Bestandsnaam",
|
||||
"Device Name": "Apparaatnaam",
|
||||
|
||||
@@ -472,8 +472,8 @@
|
||||
"Ask on conflict": "Pytaj w przypadku konfliktu",
|
||||
"Always use latest": "Zawsze używaj najnowszej",
|
||||
"Send and receive": "Wysyłanie i odbieranie",
|
||||
"Send changes only": "Wyślij tylko zmiany",
|
||||
"Receive changes only": "Odbierz tylko zmiany",
|
||||
"Send only": "Wyślij tylko zmiany",
|
||||
"Receive only": "Odbierz tylko zmiany",
|
||||
"Checksum Method": "Metoda sumy kontrolnej",
|
||||
"File Name": "Nazwa pliku",
|
||||
"Device Name": "Nazwa urządzenia",
|
||||
|
||||
@@ -534,8 +534,8 @@
|
||||
"Ask on conflict": "Perguntar em caso de conflito",
|
||||
"Always use latest": "Sempre usar o mais recente",
|
||||
"Send and receive": "Enviar e receber",
|
||||
"Send changes only": "Enviar apenas alterações",
|
||||
"Receive changes only": "Receber apenas alterações",
|
||||
"Send only": "Enviar apenas alterações",
|
||||
"Receive only": "Receber apenas alterações",
|
||||
"Checksum Method": "Método de verificação",
|
||||
"File Name": "Nome do arquivo",
|
||||
"Device Name": "Nome do dispositivo",
|
||||
|
||||
@@ -468,8 +468,8 @@
|
||||
"Ask on conflict": "Perguntar em caso de conflito",
|
||||
"Always use latest": "Sempre usar o mais recente",
|
||||
"Send and receive": "Enviar e receber",
|
||||
"Send changes only": "Enviar apenas alterações",
|
||||
"Receive changes only": "Receber apenas alterações",
|
||||
"Send only": "Enviar apenas alterações",
|
||||
"Receive only": "Receber apenas alterações",
|
||||
"Checksum Method": "Método de Verificação",
|
||||
"File Name": "Nome do Arquivo",
|
||||
"Device Name": "Nome do Dispositivo",
|
||||
|
||||
@@ -462,8 +462,8 @@
|
||||
"Ask on conflict": "Întreabă în caz de conflict",
|
||||
"Always use latest": "Folosește întotdeauna cel mai recent",
|
||||
"Send and receive": "Trimite și primește",
|
||||
"Send changes only": "Trimite doar modificările",
|
||||
"Receive changes only": "Primește doar modificările",
|
||||
"Send only": "Trimite doar modificările",
|
||||
"Receive only": "Primește doar modificările",
|
||||
"Checksum Method": "Metoda sumei de control",
|
||||
"File Name": "Numele fișierului",
|
||||
"Device Name": "Numele dispozitivului",
|
||||
|
||||
@@ -472,8 +472,8 @@
|
||||
"Ask on conflict": "Спрашивать при конфликте",
|
||||
"Always use latest": "Всегда использовать последнюю",
|
||||
"Send and receive": "Отправлять и получать",
|
||||
"Send changes only": "Отправлять только изменения",
|
||||
"Receive changes only": "Получать только изменения",
|
||||
"Send only": "Отправлять только изменения",
|
||||
"Receive only": "Получать только изменения",
|
||||
"Checksum Method": "Метод контрольной суммы",
|
||||
"File Name": "Имя файла",
|
||||
"Device Name": "Имя устройства",
|
||||
|
||||
@@ -165,8 +165,8 @@
|
||||
"Ask on conflict": "ගැටුමේදී විමසන්න",
|
||||
"Always use latest": "සෑම විටම නවතම භාවිතා කරන්න",
|
||||
"Send and receive": "යැවීම සහ ලැබීම",
|
||||
"Send changes only": "වෙනස්කම් පමණක් යවන්න",
|
||||
"Receive changes only": "වෙනස්කම් පමණක් ලබන්න",
|
||||
"Send only": "වෙනස්කම් පමණක් යවන්න",
|
||||
"Receive only": "වෙනස්කම් පමණක් ලබන්න",
|
||||
"Checksum Method": "පරීක්ෂණ ක්රමය",
|
||||
"File Name": "ගොනුවේ නම",
|
||||
"Device Name": "උපකරණයේ නම",
|
||||
|
||||
@@ -440,8 +440,8 @@
|
||||
"Ask on conflict": "Vprašaj ob sporu",
|
||||
"Always use latest": "Vedno uporabi najnovejše",
|
||||
"Send and receive": "Pošiljanje in prejemanje",
|
||||
"Send changes only": "Pošlji samo spremembe",
|
||||
"Receive changes only": "Prejmi samo spremembe",
|
||||
"Send only": "Pošlji samo spremembe",
|
||||
"Receive only": "Prejmi samo spremembe",
|
||||
"Checksum Method": "Metoda kontrolne vsote",
|
||||
"File Name": "Ime datoteke",
|
||||
"Device Name": "Ime naprave",
|
||||
|
||||
@@ -209,8 +209,8 @@
|
||||
"Ask on conflict": "Fråga vid konflikt",
|
||||
"Always use latest": "Använd alltid senaste",
|
||||
"Send and receive": "Skicka och ta emot",
|
||||
"Send changes only": "Skicka endast ändringar",
|
||||
"Receive changes only": "Ta endast emot ändringar",
|
||||
"Send only": "Skicka endast ändringar",
|
||||
"Receive only": "Ta endast emot ändringar",
|
||||
"Checksum Method": "Kontrollsummemetod",
|
||||
"File Name": "Filnamn",
|
||||
"Device Name": "Enhetsnamn",
|
||||
|
||||
@@ -165,8 +165,8 @@
|
||||
"Ask on conflict": "முரண்பாட்டில் கேட்கவும்",
|
||||
"Always use latest": "எப்போதும் சமீபத்தியதைப் பயன்படுத்தவும்",
|
||||
"Send and receive": "அனுப்புதல் மற்றும் பெறுதல்",
|
||||
"Send changes only": "மாற்றங்களை மட்டும் அனுப்பவும்",
|
||||
"Receive changes only": "மாற்றங்களை மட்டும் பெறவும்",
|
||||
"Send only": "மாற்றங்களை மட்டும் அனுப்பவும்",
|
||||
"Receive only": "மாற்றங்களை மட்டும் பெறவும்",
|
||||
"Checksum Method": "சரிபார்ப்பு முறை",
|
||||
"File Name": "கோப்பு பெயர்",
|
||||
"Device Name": "சாதன பெயர்",
|
||||
|
||||
@@ -460,8 +460,8 @@
|
||||
"Ask on conflict": "ถามเมื่อเกิดความขัดแย้ง",
|
||||
"Always use latest": "ใช้เวอร์ชันล่าสุดเสมอ",
|
||||
"Send and receive": "ส่งและรับ",
|
||||
"Send changes only": "ส่งการเปลี่ยนแปลงเท่านั้น",
|
||||
"Receive changes only": "รับการเปลี่ยนแปลงเท่านั้น",
|
||||
"Send only": "ส่งการเปลี่ยนแปลงเท่านั้น",
|
||||
"Receive only": "รับการเปลี่ยนแปลงเท่านั้น",
|
||||
"Checksum Method": "วิธีการตรวจสอบความถูกต้อง",
|
||||
"File Name": "ชื่อไฟล์",
|
||||
"Device Name": "ชื่ออุปกรณ์",
|
||||
|
||||
@@ -464,8 +464,8 @@
|
||||
"Ask on conflict": "Çatışmada Sor",
|
||||
"Always use latest": "Her Zaman En Sonunu Kullan",
|
||||
"Send and receive": "Gönder ve al",
|
||||
"Send changes only": "Sadece Değişiklikleri Gönder",
|
||||
"Receive changes only": "Sadece Değişiklikleri Al",
|
||||
"Send only": "Sadece Değişiklikleri Gönder",
|
||||
"Receive only": "Sadece Değişiklikleri Al",
|
||||
"Checksum Method": "Kontrol Toplamı Yöntemi",
|
||||
"File Name": "Dosya Adı",
|
||||
"Device Name": "Cihaz Adı",
|
||||
|
||||
@@ -472,8 +472,8 @@
|
||||
"Ask on conflict": "Запитувати при конфлікті",
|
||||
"Always use latest": "Завжди використовувати найновішу",
|
||||
"Send and receive": "Надсилати й отримувати",
|
||||
"Send changes only": "Відправити тільки зміни",
|
||||
"Receive changes only": "Отримати тільки зміни",
|
||||
"Send only": "Відправити тільки зміни",
|
||||
"Receive only": "Отримати тільки зміни",
|
||||
"Checksum Method": "Метод контролю суми",
|
||||
"File Name": "Ім'я файлу",
|
||||
"Device Name": "Ім'я пристрою",
|
||||
|
||||
@@ -524,8 +524,8 @@
|
||||
"Ask on conflict": "Ziddiyatda soʻrash",
|
||||
"Always use latest": "Doimo eng songgisidan foydalanish",
|
||||
"Send and receive": "Yuborish va qabul qilish",
|
||||
"Send changes only": "Faqat oʻzgarishlarni yuborish",
|
||||
"Receive changes only": "Faqat oʻzgarishlarni qabul qilish",
|
||||
"Send only": "Faqat oʻzgarishlarni yuborish",
|
||||
"Receive only": "Faqat oʻzgarishlarni qabul qilish",
|
||||
"Checksum Method": "Nazorat yigʻindisi usuli",
|
||||
"File Name": "Fayl nomi",
|
||||
"Device Name": "Qurilma nomi",
|
||||
|
||||
@@ -460,8 +460,8 @@
|
||||
"Ask on conflict": "Hỏi khi có xung đột",
|
||||
"Always use latest": "Luôn sử dụng phiên bản mới nhất",
|
||||
"Send and receive": "Gửi và nhận",
|
||||
"Send changes only": "Chỉ gửi thay đổi",
|
||||
"Receive changes only": "Chỉ nhận thay đổi",
|
||||
"Send only": "Chỉ gửi thay đổi",
|
||||
"Receive only": "Chỉ nhận thay đổi",
|
||||
"Checksum Method": "Phương pháp kiểm tra",
|
||||
"File Name": "Tên tệp",
|
||||
"Device Name": "Tên thiết bị",
|
||||
|
||||
@@ -461,8 +461,8 @@
|
||||
"Ask on conflict": "发生冲突时询问",
|
||||
"Always use latest": "始终使用最新",
|
||||
"Send and receive": "发送和接收",
|
||||
"Send changes only": "仅发送更改",
|
||||
"Receive changes only": "仅接收更改",
|
||||
"Send only": "仅发送更改",
|
||||
"Receive only": "仅接收更改",
|
||||
"Checksum Method": "校验和方法",
|
||||
"File Name": "文件名",
|
||||
"Device Name": "设备名称",
|
||||
|
||||
@@ -460,8 +460,8 @@
|
||||
"Ask on conflict": "發生衝突時詢問",
|
||||
"Always use latest": "始終使用最新",
|
||||
"Send and receive": "發送和接收",
|
||||
"Send changes only": "僅發送更改",
|
||||
"Receive changes only": "僅接收更改",
|
||||
"Send only": "僅發送更改",
|
||||
"Receive only": "僅接收更改",
|
||||
"Checksum Method": "校驗和方法",
|
||||
"File Name": "文件名",
|
||||
"Device Name": "設備名稱",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { withActiveCloudProvider } from '@/components/settings/integrations/cloudSync';
|
||||
import { isCloudSyncInPlan } from '@/utils/access';
|
||||
import { CLOUD_SYNC_REQUIRES_PREMIUM, isCloudSyncAllowed, isCloudSyncInPlan } from '@/utils/access';
|
||||
import type { SystemSettings } from '@/types/settings';
|
||||
|
||||
const base = {
|
||||
@@ -45,3 +45,13 @@ describe('isCloudSyncInPlan', () => {
|
||||
expect(isCloudSyncInPlan('free')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isCloudSyncAllowed (temporary ungate)', () => {
|
||||
test('cloud sync is currently ungated for every plan, including free', () => {
|
||||
// The feature ships ungated while it stabilises. When the paywall is
|
||||
// restored (flip CLOUD_SYNC_REQUIRES_PREMIUM back to true), update this.
|
||||
expect(CLOUD_SYNC_REQUIRES_PREMIUM).toBe(false);
|
||||
expect(isCloudSyncAllowed('free')).toBe(true);
|
||||
expect(isCloudSyncAllowed('plus')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useBookProgress } from '@/store/readerProgressStore';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useQuotaStats } from '@/hooks/useQuotaStats';
|
||||
import { isCloudSyncInPlan } from '@/utils/access';
|
||||
import { isCloudSyncAllowed } from '@/utils/access';
|
||||
import { debounce } from '@/utils/debounce';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
import { FileSyncEngine } from '@/services/sync/file/engine';
|
||||
@@ -133,11 +133,12 @@ export const useFileSync = (bookKey: string) => {
|
||||
[activeKind, envConfig, setSettings, saveSettings],
|
||||
);
|
||||
|
||||
// Third-party cloud sync is a premium feature; the reader's auto-sync stays
|
||||
// off for free plans (and stops if a user downgrades) even if a provider's
|
||||
// `enabled` flag lingers in settings.
|
||||
// Third-party cloud sync will be a premium feature (the reader's auto-sync
|
||||
// would stay off for free plans), but it is temporarily UNGATED while the
|
||||
// feature stabilises — `isCloudSyncAllowed` returns true for every plan until
|
||||
// `CLOUD_SYNC_REQUIRES_PREMIUM` is flipped back on.
|
||||
const { userProfilePlan } = useQuotaStats();
|
||||
const isPremium = !!userProfilePlan && isCloudSyncInPlan(userProfilePlan);
|
||||
const isPremium = isCloudSyncAllowed(userProfilePlan ?? 'free');
|
||||
|
||||
const isReady = useMemo(() => {
|
||||
if (!isPremium) return false;
|
||||
|
||||
@@ -22,7 +22,7 @@ import { useCustomOPDSStore } from '@/store/customOPDSStore';
|
||||
import { useFileSyncStore } from '@/store/fileSyncStore';
|
||||
import { CatalogManager } from '@/app/opds/components/CatalogManager';
|
||||
import { saveSysSettings } from '@/helpers/settings';
|
||||
import { isCloudSyncInPlan } from '@/utils/access';
|
||||
import { isCloudSyncAllowed } from '@/utils/access';
|
||||
import { navigateToLogin, navigateToProfile } from '@/utils/nav';
|
||||
import KOSyncForm from './integrations/KOSyncForm';
|
||||
import ReadwiseForm from './integrations/ReadwiseForm';
|
||||
@@ -63,10 +63,12 @@ const IntegrationsPanel: React.FC = () => {
|
||||
// when they back out of the WebDAV sub-page or close the dialog.
|
||||
const isWebDAVSyncing = useFileSyncStore((s) => s.byKind.webdav?.isSyncing ?? false);
|
||||
const isGDriveSyncing = useFileSyncStore((s) => s.byKind.gdrive?.isSyncing ?? false);
|
||||
// Third-party cloud sync is a premium feature (any paid plan). `undefined`
|
||||
// while the plan is still loading — treated as not-yet-premium.
|
||||
// Third-party cloud sync will be a premium feature (any paid plan), but it is
|
||||
// temporarily UNGATED while the feature stabilises — `isCloudSyncAllowed`
|
||||
// returns true for every plan until `CLOUD_SYNC_REQUIRES_PREMIUM` is flipped
|
||||
// back on. The `?? 'free'` keeps the (re-gated) loading state non-premium.
|
||||
const { userProfilePlan } = useQuotaStats();
|
||||
const isCloudSyncPremium = !!userProfilePlan && isCloudSyncInPlan(userProfilePlan);
|
||||
const isCloudSyncPremium = isCloudSyncAllowed(userProfilePlan ?? 'free');
|
||||
|
||||
const [subPage, setSubPage] = useState<SubPage>(null);
|
||||
|
||||
|
||||
@@ -187,8 +187,8 @@ const FileSyncForm: React.FC<FileSyncFormProps> = ({ kind, stored, persist }) =>
|
||||
ariaLabel={_('Sync Strategy')}
|
||||
options={[
|
||||
{ value: 'silent', label: _('Send and receive') },
|
||||
{ value: 'send', label: _('Send changes only') },
|
||||
{ value: 'receive', label: _('Receive changes only') },
|
||||
{ value: 'send', label: _('Send only') },
|
||||
{ value: 'receive', label: _('Receive only') },
|
||||
]}
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
@@ -21,9 +21,9 @@ const disconnectButtonClass = clsx(
|
||||
);
|
||||
|
||||
const primaryButtonClass = clsx(
|
||||
'btn btn-primary',
|
||||
'btn btn-contrast',
|
||||
'h-10 min-h-10 rounded-lg border-0 px-5 text-sm font-medium',
|
||||
'focus-visible:ring-primary/40 focus-visible:outline-none focus-visible:ring-2',
|
||||
'focus-visible:ring-base-content/40 focus-visible:outline-none focus-visible:ring-2',
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -137,10 +137,6 @@ const GoogleDriveForm: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className='space-y-5'>
|
||||
<Tips>
|
||||
<li>{_('Sign-in opens in your browser.')}</li>
|
||||
<li>{_('Readest only accesses the files it creates in your Drive.')}</li>
|
||||
</Tips>
|
||||
<div className='flex justify-end pt-1'>
|
||||
<button
|
||||
type='button'
|
||||
@@ -154,10 +150,14 @@ const GoogleDriveForm: React.FC = () => {
|
||||
{_('Waiting for sign-in…')}
|
||||
</>
|
||||
) : (
|
||||
_('Connect Google Drive')
|
||||
_('Connect')
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<Tips>
|
||||
<li>{_('Sign-in opens in your browser.')}</li>
|
||||
<li>{_('Readest only accesses the files it creates in your Drive.')}</li>
|
||||
</Tips>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -182,8 +182,8 @@ const KOSyncForm: React.FC<KOSyncFormProps> = ({ onBack }) => {
|
||||
options={[
|
||||
{ value: 'prompt', label: _('Ask on conflict') },
|
||||
{ value: 'silent', label: _('Always use latest') },
|
||||
{ value: 'send', label: _('Send changes only') },
|
||||
{ value: 'receive', label: _('Receive changes only') },
|
||||
{ value: 'send', label: _('Send only') },
|
||||
{ value: 'receive', label: _('Receive only') },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -233,9 +233,9 @@ const WebDAVForm: React.FC = () => {
|
||||
type='submit'
|
||||
disabled={isConnecting || !url || !username}
|
||||
className={clsx(
|
||||
'btn btn-primary',
|
||||
'btn btn-contrast',
|
||||
'h-10 min-h-10 rounded-lg border-0 px-5 text-sm font-medium',
|
||||
'focus-visible:ring-primary/40 focus-visible:outline-none focus-visible:ring-2',
|
||||
'focus-visible:ring-base-content/40 focus-visible:outline-none focus-visible:ring-2',
|
||||
isConnecting && 'opacity-60',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -7,7 +7,9 @@ interface SettingsRowProps {
|
||||
label: React.ReactNode;
|
||||
/**
|
||||
* Optional secondary line under the label (description / status hint).
|
||||
* Keep it short — settings rows aren't meant to host paragraphs.
|
||||
* Keep it short — settings rows aren't meant to host paragraphs; it is
|
||||
* clamped to a single line (ellipsis on overflow) to keep row heights
|
||||
* uniform across narrow (mobile) widths.
|
||||
*/
|
||||
description?: React.ReactNode;
|
||||
/** Trailing slot — typically the control (toggle, select, input, button). */
|
||||
@@ -66,7 +68,9 @@ const SettingsRow: React.FC<SettingsRowProps> = ({
|
||||
<div className='flex min-w-0 flex-col'>
|
||||
<SettingLabel>{label}</SettingLabel>
|
||||
{description && (
|
||||
<span className='text-base-content/65 text-[0.8em] leading-snug'>{description}</span>
|
||||
<span className='text-base-content/65 line-clamp-1 text-[0.8em] leading-snug'>
|
||||
{description}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{children}
|
||||
|
||||
@@ -56,6 +56,23 @@ export const CLOUD_SYNC_PLANS: readonly UserPlan[] = ['plus', 'pro', 'purchase']
|
||||
export const isCloudSyncInPlan = (plan: UserPlan): boolean =>
|
||||
(CLOUD_SYNC_PLANS as readonly UserPlan[]).includes(plan);
|
||||
|
||||
/**
|
||||
* Master switch for the third-party cloud-sync premium paywall. Temporarily
|
||||
* OFF: cloud sync (WebDAV / Google Drive) ships ungated to every plan — including
|
||||
* free — while the feature stabilises, and will be re-gated to
|
||||
* {@link CLOUD_SYNC_PLANS} in an upcoming release. Every gate goes through
|
||||
* {@link isCloudSyncAllowed}, so flipping this back to `true` is the whole toggle.
|
||||
*/
|
||||
export const CLOUD_SYNC_REQUIRES_PREMIUM = false;
|
||||
|
||||
/**
|
||||
* Whether third-party cloud sync is available for a plan. While
|
||||
* {@link CLOUD_SYNC_REQUIRES_PREMIUM} is off this is always true; once it is
|
||||
* re-enabled it falls back to the {@link isCloudSyncInPlan} paywall.
|
||||
*/
|
||||
export const isCloudSyncAllowed = (plan: UserPlan): boolean =>
|
||||
!CLOUD_SYNC_REQUIRES_PREMIUM || isCloudSyncInPlan(plan);
|
||||
|
||||
export const STORAGE_QUOTA_GRACE_BYTES = 10 * 1024 * 1024; // 10 MB grace
|
||||
|
||||
export const getStoragePlanData = (token: string) => {
|
||||
|
||||
Reference in New Issue
Block a user