From e9d71b29362f515cdc5cec23ae24e6511bbe53e9 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Mon, 13 Apr 2026 15:42:53 +0800 Subject: [PATCH] feat(settings): add an option to avoid overriding paragraph layout, closes #3824 (#3858) --- apps/readest-app/docs/view-settings.md | 25 ++ .../public/locales/ar/translation.json | 3 +- .../public/locales/bn/translation.json | 3 +- .../public/locales/bo/translation.json | 3 +- .../public/locales/de/translation.json | 3 +- .../public/locales/el/translation.json | 3 +- .../public/locales/es/translation.json | 3 +- .../public/locales/fa/translation.json | 3 +- .../public/locales/fr/translation.json | 3 +- .../public/locales/he/translation.json | 3 +- .../public/locales/hi/translation.json | 3 +- .../public/locales/hu/translation.json | 3 +- .../public/locales/id/translation.json | 3 +- .../public/locales/it/translation.json | 3 +- .../public/locales/ja/translation.json | 3 +- .../public/locales/ko/translation.json | 3 +- .../public/locales/ms/translation.json | 3 +- .../public/locales/nl/translation.json | 3 +- .../public/locales/pl/translation.json | 3 +- .../public/locales/pt/translation.json | 3 +- .../public/locales/ro/translation.json | 3 +- .../public/locales/ru/translation.json | 3 +- .../public/locales/si/translation.json | 3 +- .../public/locales/sl/translation.json | 3 +- .../public/locales/sv/translation.json | 3 +- .../public/locales/ta/translation.json | 3 +- .../public/locales/th/translation.json | 3 +- .../public/locales/tr/translation.json | 3 +- .../public/locales/uk/translation.json | 3 +- .../public/locales/vi/translation.json | 3 +- .../public/locales/zh-CN/translation.json | 3 +- .../public/locales/zh-TW/translation.json | 3 +- .../__tests__/utils/style-get-styles.test.ts | 52 ++++ .../src/components/settings/LayoutPanel.tsx | 24 ++ .../src/services/commandRegistry.ts | 6 + apps/readest-app/src/services/constants.ts | 8 +- apps/readest-app/src/types/book.ts | 8 +- apps/readest-app/src/utils/style.ts | 251 +++++++++--------- 38 files changed, 311 insertions(+), 156 deletions(-) diff --git a/apps/readest-app/docs/view-settings.md b/apps/readest-app/docs/view-settings.md index e1cbe24d..eb46b22d 100644 --- a/apps/readest-app/docs/view-settings.md +++ b/apps/readest-app/docs/view-settings.md @@ -167,6 +167,30 @@ const handleReset = () => { The hook resolves the default by reading from `getDefaultViewSettings(ctx)` and calls each provided setter with that value, which then fires your `useEffect` and persists the change. +### Step 5 — Register in the Command Palette + +If your setting has a visible row in a panel, register it in the matching `*PanelItems` array in `src/services/commandRegistry.ts`. This wires it into the command-palette fuzzy search so users can jump straight to it. + +```ts +// src/services/commandRegistry.ts +const layoutPanelItems = [ + // ...existing entries + { + id: 'settings.layout.myNewToggle', + labelKey: _('My New Toggle'), + keywords: ['search', 'terms', 'for', 'discoverability'], + section: 'Paragraph', + }, +]; +``` + +- `id` must match the `data-setting-id` attribute on the panel row. The palette uses it to scroll/highlight the target control. +- `labelKey` uses `stubTranslation` (imported as `_`) so the extractor picks it up — the same string that appears in the panel. +- `keywords` broadens fuzzy-search hits beyond the label; include synonyms, related jargon, and the panel section name. +- `section` groups the entry in the palette results (matches the panel's sub-header: `Layout`, `Paragraph`, `Page`, `Header & Footer`, etc.). + +Skip this step only for settings that don't surface as a user-visible row (hidden toggles, flags used by other settings). + ### Don'ts - **Don't make the field optional** just to skip providing a default. Add a default in Step 2 instead. @@ -182,3 +206,4 @@ The hook resolves the default by reading from `getDefaultViewSettings(ctx)` and - [ ] Read via `getViewSettings(bookKey) || settings.globalViewSettings` - [ ] Write via `saveViewSettings(envConfig, bookKey, 'key', value)` - [ ] Reset setter wired into `useResetViewSettings` if the panel has a reset menu +- [ ] Command-palette entry added to the matching `*PanelItems` array in `src/services/commandRegistry.ts`, with an `id` that matches the panel row's `data-setting-id` diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index 715198b3..4ed008b7 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -1242,5 +1242,6 @@ "Unavailable": "غير متاح", "Remove": "إزالة", "Edit OPDS Catalog": "تعديل كتالوج OPDS", - "Save Changes": "حفظ التغييرات" + "Save Changes": "حفظ التغييرات", + "Use Book Layout": "استخدام تخطيط الكتاب" } diff --git a/apps/readest-app/public/locales/bn/translation.json b/apps/readest-app/public/locales/bn/translation.json index f00e801d..6d02a895 100644 --- a/apps/readest-app/public/locales/bn/translation.json +++ b/apps/readest-app/public/locales/bn/translation.json @@ -1190,5 +1190,6 @@ "Unavailable": "অনুপলব্ধ", "Remove": "সরান", "Edit OPDS Catalog": "OPDS ক্যাটালগ সম্পাদনা", - "Save Changes": "পরিবর্তন সংরক্ষণ" + "Save Changes": "পরিবর্তন সংরক্ষণ", + "Use Book Layout": "বইয়ের লেআউট ব্যবহার করুন" } diff --git a/apps/readest-app/public/locales/bo/translation.json b/apps/readest-app/public/locales/bo/translation.json index 8187c029..a06cb59d 100644 --- a/apps/readest-app/public/locales/bo/translation.json +++ b/apps/readest-app/public/locales/bo/translation.json @@ -1177,5 +1177,6 @@ "Unavailable": "རག་མི་ཐུབ", "Remove": "བསུབ།", "Edit OPDS Catalog": "OPDS དཀར་ཆག་རྩོམ་སྒྲིག", - "Save Changes": "བསྒྱུར་བཅོས་ཉར་ཚགས།" + "Save Changes": "བསྒྱུར་བཅོས་ཉར་ཚགས།", + "Use Book Layout": "དཔེ་ཆའི་བཀོད་སྒྲིག་སྤྱོད་པ" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index 43b29fde..3fe803f7 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -1190,5 +1190,6 @@ "Unavailable": "Nicht verfügbar", "Remove": "Entfernen", "Edit OPDS Catalog": "OPDS-Katalog bearbeiten", - "Save Changes": "Änderungen speichern" + "Save Changes": "Änderungen speichern", + "Use Book Layout": "Buchlayout verwenden" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index a2752ee3..b24757f6 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -1190,5 +1190,6 @@ "Unavailable": "Μη διαθέσιμο", "Remove": "Αφαίρεση", "Edit OPDS Catalog": "Επεξεργασία καταλόγου OPDS", - "Save Changes": "Αποθήκευση αλλαγών" + "Save Changes": "Αποθήκευση αλλαγών", + "Use Book Layout": "Χρήση διάταξης βιβλίου" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index 0a57bc7d..ce9597fb 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -1203,5 +1203,6 @@ "Unavailable": "No disponible", "Remove": "Eliminar", "Edit OPDS Catalog": "Editar catálogo OPDS", - "Save Changes": "Guardar cambios" + "Save Changes": "Guardar cambios", + "Use Book Layout": "Usar el diseño del libro" } diff --git a/apps/readest-app/public/locales/fa/translation.json b/apps/readest-app/public/locales/fa/translation.json index c33f0d4a..57a09d5e 100644 --- a/apps/readest-app/public/locales/fa/translation.json +++ b/apps/readest-app/public/locales/fa/translation.json @@ -1190,5 +1190,6 @@ "Unavailable": "ناموجود", "Remove": "حذف", "Edit OPDS Catalog": "ویرایش کاتالوگ OPDS", - "Save Changes": "ذخیره تغییرات" + "Save Changes": "ذخیره تغییرات", + "Use Book Layout": "استفاده از چیدمان کتاب" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index d9838161..185aaf77 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -1203,5 +1203,6 @@ "Unavailable": "Indisponible", "Remove": "Supprimer", "Edit OPDS Catalog": "Modifier le catalogue OPDS", - "Save Changes": "Enregistrer les modifications" + "Save Changes": "Enregistrer les modifications", + "Use Book Layout": "Utiliser la mise en page du livre" } diff --git a/apps/readest-app/public/locales/he/translation.json b/apps/readest-app/public/locales/he/translation.json index 3b1315fa..7c771f85 100644 --- a/apps/readest-app/public/locales/he/translation.json +++ b/apps/readest-app/public/locales/he/translation.json @@ -1203,5 +1203,6 @@ "Unavailable": "לא זמין", "Remove": "הסר", "Edit OPDS Catalog": "עריכת קטלוג OPDS", - "Save Changes": "שמור שינויים" + "Save Changes": "שמור שינויים", + "Use Book Layout": "השתמש בפריסת הספר" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index 5d6c7419..137d3db9 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -1190,5 +1190,6 @@ "Unavailable": "अनुपलब्ध", "Remove": "हटाएँ", "Edit OPDS Catalog": "OPDS कैटलॉग संपादित करें", - "Save Changes": "परिवर्तन सहेजें" + "Save Changes": "परिवर्तन सहेजें", + "Use Book Layout": "पुस्तक का लेआउट उपयोग करें" } diff --git a/apps/readest-app/public/locales/hu/translation.json b/apps/readest-app/public/locales/hu/translation.json index 7c766859..c4614b6e 100644 --- a/apps/readest-app/public/locales/hu/translation.json +++ b/apps/readest-app/public/locales/hu/translation.json @@ -1190,5 +1190,6 @@ "Unavailable": "Nem elérhető", "Remove": "Eltávolítás", "Edit OPDS Catalog": "OPDS katalógus szerkesztése", - "Save Changes": "Módosítások mentése" + "Save Changes": "Módosítások mentése", + "Use Book Layout": "Könyv elrendezésének használata" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index 9fd540a0..aff1a571 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -1177,5 +1177,6 @@ "Unavailable": "Tidak Tersedia", "Remove": "Hapus", "Edit OPDS Catalog": "Edit Katalog OPDS", - "Save Changes": "Simpan Perubahan" + "Save Changes": "Simpan Perubahan", + "Use Book Layout": "Gunakan tata letak buku" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index 839a6263..151c6338 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -1203,5 +1203,6 @@ "Unavailable": "Non disponibile", "Remove": "Rimuovi", "Edit OPDS Catalog": "Modifica catalogo OPDS", - "Save Changes": "Salva modifiche" + "Save Changes": "Salva modifiche", + "Use Book Layout": "Usa layout del libro" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index 5612ef7b..55cbf0f7 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -1177,5 +1177,6 @@ "Unavailable": "利用不可", "Remove": "削除", "Edit OPDS Catalog": "OPDSカタログを編集", - "Save Changes": "変更を保存" + "Save Changes": "変更を保存", + "Use Book Layout": "書籍のレイアウトを使用" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index 274812c9..1ab0ddfe 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -1177,5 +1177,6 @@ "Unavailable": "사용 불가", "Remove": "삭제", "Edit OPDS Catalog": "OPDS 카탈로그 편집", - "Save Changes": "변경 사항 저장" + "Save Changes": "변경 사항 저장", + "Use Book Layout": "책 레이아웃 사용" } diff --git a/apps/readest-app/public/locales/ms/translation.json b/apps/readest-app/public/locales/ms/translation.json index 5e863dfb..a0fecf94 100644 --- a/apps/readest-app/public/locales/ms/translation.json +++ b/apps/readest-app/public/locales/ms/translation.json @@ -1177,5 +1177,6 @@ "Unavailable": "Tidak Tersedia", "Remove": "Alih keluar", "Edit OPDS Catalog": "Edit Katalog OPDS", - "Save Changes": "Simpan Perubahan" + "Save Changes": "Simpan Perubahan", + "Use Book Layout": "Guna susun atur buku" } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index 2b781b18..28a8ec7b 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -1190,5 +1190,6 @@ "Unavailable": "Niet beschikbaar", "Remove": "Verwijderen", "Edit OPDS Catalog": "OPDS-catalogus bewerken", - "Save Changes": "Wijzigingen opslaan" + "Save Changes": "Wijzigingen opslaan", + "Use Book Layout": "Boekindeling gebruiken" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index 1e80946f..ac59fe7d 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -1216,5 +1216,6 @@ "Unavailable": "Niedostępne", "Remove": "Usuń", "Edit OPDS Catalog": "Edytuj katalog OPDS", - "Save Changes": "Zapisz zmiany" + "Save Changes": "Zapisz zmiany", + "Use Book Layout": "Użyj układu książki" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index 21703b9c..fcea0c18 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -1203,5 +1203,6 @@ "Unavailable": "Indisponível", "Remove": "Remover", "Edit OPDS Catalog": "Editar catálogo OPDS", - "Save Changes": "Salvar alterações" + "Save Changes": "Salvar alterações", + "Use Book Layout": "Usar layout do livro" } diff --git a/apps/readest-app/public/locales/ro/translation.json b/apps/readest-app/public/locales/ro/translation.json index d68e25e7..5b7a8a54 100644 --- a/apps/readest-app/public/locales/ro/translation.json +++ b/apps/readest-app/public/locales/ro/translation.json @@ -1203,5 +1203,6 @@ "Unavailable": "Indisponibil", "Remove": "Elimină", "Edit OPDS Catalog": "Editare catalog OPDS", - "Save Changes": "Salvare modificări" + "Save Changes": "Salvare modificări", + "Use Book Layout": "Folosește aspectul cărții" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index c78e1c70..02b74389 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -1216,5 +1216,6 @@ "Unavailable": "Недоступно", "Remove": "Удалить", "Edit OPDS Catalog": "Редактировать каталог OPDS", - "Save Changes": "Сохранить изменения" + "Save Changes": "Сохранить изменения", + "Use Book Layout": "Использовать макет книги" } diff --git a/apps/readest-app/public/locales/si/translation.json b/apps/readest-app/public/locales/si/translation.json index f695e2ba..1628dc43 100644 --- a/apps/readest-app/public/locales/si/translation.json +++ b/apps/readest-app/public/locales/si/translation.json @@ -1190,5 +1190,6 @@ "Unavailable": "ලබාගත නොහැක", "Remove": "ඉවත් කරන්න", "Edit OPDS Catalog": "OPDS නාමාවලිය සංස්කරණය", - "Save Changes": "වෙනස්කම් සුරකින්න" + "Save Changes": "වෙනස්කම් සුරකින්න", + "Use Book Layout": "පොතේ සැකැස්ම භාවිත කරන්න" } diff --git a/apps/readest-app/public/locales/sl/translation.json b/apps/readest-app/public/locales/sl/translation.json index 42ad0f30..6d763405 100644 --- a/apps/readest-app/public/locales/sl/translation.json +++ b/apps/readest-app/public/locales/sl/translation.json @@ -1216,5 +1216,6 @@ "Unavailable": "Nedosegljivo", "Remove": "Odstrani", "Edit OPDS Catalog": "Uredi katalog OPDS", - "Save Changes": "Shrani spremembe" + "Save Changes": "Shrani spremembe", + "Use Book Layout": "Uporabi postavitev knjige" } diff --git a/apps/readest-app/public/locales/sv/translation.json b/apps/readest-app/public/locales/sv/translation.json index 6f32e605..40d4dc2a 100644 --- a/apps/readest-app/public/locales/sv/translation.json +++ b/apps/readest-app/public/locales/sv/translation.json @@ -1190,5 +1190,6 @@ "Unavailable": "Inte tillgängligt", "Remove": "Ta bort", "Edit OPDS Catalog": "Redigera OPDS-katalog", - "Save Changes": "Spara ändringar" + "Save Changes": "Spara ändringar", + "Use Book Layout": "Använd bokens layout" } diff --git a/apps/readest-app/public/locales/ta/translation.json b/apps/readest-app/public/locales/ta/translation.json index 7ea80359..ca4bdde2 100644 --- a/apps/readest-app/public/locales/ta/translation.json +++ b/apps/readest-app/public/locales/ta/translation.json @@ -1190,5 +1190,6 @@ "Unavailable": "கிடைக்கவில்லை", "Remove": "நீக்கு", "Edit OPDS Catalog": "OPDS பட்டியலைத் திருத்து", - "Save Changes": "மாற்றங்களைச் சேமி" + "Save Changes": "மாற்றங்களைச் சேமி", + "Use Book Layout": "புத்தகத்தின் அமைப்பைப் பயன்படுத்து" } diff --git a/apps/readest-app/public/locales/th/translation.json b/apps/readest-app/public/locales/th/translation.json index 3f13ddd9..cfa9851d 100644 --- a/apps/readest-app/public/locales/th/translation.json +++ b/apps/readest-app/public/locales/th/translation.json @@ -1177,5 +1177,6 @@ "Unavailable": "ไม่พร้อมใช้งาน", "Remove": "ลบ", "Edit OPDS Catalog": "แก้ไขแคตตาล็อก OPDS", - "Save Changes": "บันทึกการเปลี่ยนแปลง" + "Save Changes": "บันทึกการเปลี่ยนแปลง", + "Use Book Layout": "ใช้เลย์เอาต์ของหนังสือ" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index aec4ecb4..9ec557b1 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -1190,5 +1190,6 @@ "Unavailable": "Kullanılamaz", "Remove": "Kaldır", "Edit OPDS Catalog": "OPDS Kataloğunu Düzenle", - "Save Changes": "Değişiklikleri Kaydet" + "Save Changes": "Değişiklikleri Kaydet", + "Use Book Layout": "Kitap düzenini kullan" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index 2c813af4..c09f1ca8 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -1216,5 +1216,6 @@ "Unavailable": "Недоступно", "Remove": "Видалити", "Edit OPDS Catalog": "Редагувати каталог OPDS", - "Save Changes": "Зберегти зміни" + "Save Changes": "Зберегти зміни", + "Use Book Layout": "Використовувати макет книги" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index d7fb4bee..0ebf2533 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -1177,5 +1177,6 @@ "Unavailable": "Không khả dụng", "Remove": "Xóa", "Edit OPDS Catalog": "Chỉnh sửa danh mục OPDS", - "Save Changes": "Lưu thay đổi" + "Save Changes": "Lưu thay đổi", + "Use Book Layout": "Sử dụng bố cục sách" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index 6ccd76e1..c85f7d58 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -1177,5 +1177,6 @@ "Unavailable": "不可用", "Remove": "移除", "Edit OPDS Catalog": "编辑 OPDS 目录", - "Save Changes": "保存更改" + "Save Changes": "保存更改", + "Use Book Layout": "使用书籍排版" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index d9bc1c78..b3e4d5dd 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -1177,5 +1177,6 @@ "Unavailable": "無法使用", "Remove": "移除", "Edit OPDS Catalog": "編輯 OPDS 目錄", - "Save Changes": "儲存變更" + "Save Changes": "儲存變更", + "Use Book Layout": "使用書籍排版" } diff --git a/apps/readest-app/src/__tests__/utils/style-get-styles.test.ts b/apps/readest-app/src/__tests__/utils/style-get-styles.test.ts index e544d641..f0faef09 100644 --- a/apps/readest-app/src/__tests__/utils/style-get-styles.test.ts +++ b/apps/readest-app/src/__tests__/utils/style-get-styles.test.ts @@ -368,6 +368,58 @@ describe('getLayoutStyles branches (via getStyles)', () => { // getStyles passes 1.0 as zoomLevel to getLayoutStyles expect(css).toContain('zoom: 1'); }); + + it('omits only paragraph-related layout rules when useBookLayout is true', () => { + const vs = makeViewSettings({ + useBookLayout: true, + lineHeight: 1.7, + wordSpacing: 3, + letterSpacing: 2, + textIndent: 2, + paragraphMargin: 1.25, + fullJustification: true, + hyphenation: true, + marginTopPx: 50, + }); + const css = getStyles(vs, theme); + // paragraph-specific tokens driven by the Paragraph section must be absent + expect(css).not.toContain('--default-text-align:'); + expect(css).not.toContain('line-height: 1.7'); + expect(css).not.toContain('word-spacing: 3px'); + expect(css).not.toContain('letter-spacing: 2px'); + expect(css).not.toContain('text-indent: 2em'); + expect(css).not.toContain('hyphens: auto'); + expect(css).not.toContain('-webkit-hyphens: auto'); + // non-paragraph layout rules must still be emitted + expect(css).toContain('@namespace epub'); + expect(css).toContain('--margin-top: 50px'); + expect(css).toContain('--margin-right:'); + expect(css).toContain('--margin-bottom:'); + expect(css).toContain('--margin-left:'); + // font/color/translation sections must still be present + expect(css).toContain('--serif:'); + expect(css).toContain('--theme-bg-color'); + expect(css).toContain('.translation-source'); + }); + + it('includes paragraph layout rules when useBookLayout is false', () => { + const vs = makeViewSettings({ + useBookLayout: false, + lineHeight: 1.7, + wordSpacing: 3, + letterSpacing: 2, + textIndent: 2, + fullJustification: true, + hyphenation: true, + }); + const css = getStyles(vs, theme); + expect(css).toContain('--default-text-align: justify'); + expect(css).toContain('line-height: 1.7'); + expect(css).toContain('word-spacing: 3px'); + expect(css).toContain('letter-spacing: 2px'); + expect(css).toContain('text-indent: 2em'); + expect(css).toContain('hyphens: auto'); + }); }); // --------------------------------------------------------------------------- diff --git a/apps/readest-app/src/components/settings/LayoutPanel.tsx b/apps/readest-app/src/components/settings/LayoutPanel.tsx index 7a135e47..8d215fea 100644 --- a/apps/readest-app/src/components/settings/LayoutPanel.tsx +++ b/apps/readest-app/src/components/settings/LayoutPanel.tsx @@ -61,6 +61,7 @@ const LayoutPanel: React.FC = ({ bookKey, onRegisterRese const [maxBlockSize, setMaxBlockSize] = useState(viewSettings.maxBlockSize); const [writingMode, setWritingMode] = useState(viewSettings.writingMode); const [overrideLayout, setOverrideLayout] = useState(viewSettings.overrideLayout); + const [useBookLayout, setUseBookLayout] = useState(viewSettings.useBookLayout); const [doubleBorder, setDoubleBorder] = useState(viewSettings.doubleBorder); const [borderColor, setBorderColor] = useState(viewSettings.borderColor); const [showHeader, setShowHeader] = useState(viewSettings.showHeader); @@ -106,6 +107,7 @@ const LayoutPanel: React.FC = ({ bookKey, onRegisterRese maxInlineSize: setMaxInlineSize, maxBlockSize: setMaxBlockSize, overrideLayout: setOverrideLayout, + useBookLayout: setUseBookLayout, doubleBorder: setDoubleBorder, borderColor: setBorderColor, showHeader: setShowHeader, @@ -315,6 +317,12 @@ const LayoutPanel: React.FC = ({ bookKey, onRegisterRese // eslint-disable-next-line react-hooks/exhaustive-deps }, [overrideLayout]); + useEffect(() => { + if (useBookLayout === viewSettings.useBookLayout) return; + saveViewSettings(envConfig, bookKey, 'useBookLayout', useBookLayout); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [useBookLayout]); + useEffect(() => { if (doubleBorder === viewSettings.doubleBorder) return; saveViewSettings(envConfig, bookKey, 'doubleBorder', doubleBorder, false, false); @@ -527,10 +535,20 @@ const LayoutPanel: React.FC = ({ bookKey, onRegisterRese

{_('Paragraph')}

+
+ {_('Use Book Layout')} + setUseBookLayout(!useBookLayout)} + /> +
= ({ bookKey, onRegisterRese label={_('Line Spacing')} value={lineHeight} onChange={setLineHeight} + disabled={useBookLayout} min={1.0} max={3.0} step={0.1} @@ -550,6 +569,7 @@ const LayoutPanel: React.FC = ({ bookKey, onRegisterRese label={_('Word Spacing')} value={wordSpacing} onChange={setWordSpacing} + disabled={useBookLayout} min={-4} max={8} step={0.5} @@ -560,6 +580,7 @@ const LayoutPanel: React.FC = ({ bookKey, onRegisterRese label={_('Letter Spacing')} value={letterSpacing} onChange={setLetterSpacing} + disabled={useBookLayout} min={-2} max={4} step={0.5} @@ -569,6 +590,7 @@ const LayoutPanel: React.FC = ({ bookKey, onRegisterRese label={_('Text Indent')} value={textIndent} onChange={setTextIndent} + disabled={useBookLayout} min={-2} max={4} step={1} @@ -580,6 +602,7 @@ const LayoutPanel: React.FC = ({ bookKey, onRegisterRese type='checkbox' className='toggle' checked={fullJustification} + disabled={useBookLayout} onChange={() => setFullJustification(!fullJustification)} />
@@ -589,6 +612,7 @@ const LayoutPanel: React.FC = ({ bookKey, onRegisterRese type='checkbox' className='toggle' checked={hyphenation} + disabled={useBookLayout} onChange={() => setHyphenation(!hyphenation)} />
diff --git a/apps/readest-app/src/services/commandRegistry.ts b/apps/readest-app/src/services/commandRegistry.ts index 373c1f76..53967723 100644 --- a/apps/readest-app/src/services/commandRegistry.ts +++ b/apps/readest-app/src/services/commandRegistry.ts @@ -235,6 +235,12 @@ const layoutPanelItems = [ keywords: ['border', 'frame', 'vertical', 'mode'], section: 'Layout', }, + { + id: 'settings.layout.useBookLayout', + labelKey: _('Use Book Layout'), + keywords: ['paragraph', 'book', 'layout', 'default', 'original', 'preserve'], + section: 'Paragraph', + }, { id: 'settings.layout.paragraphMargin', labelKey: _('Paragraph Margin'), diff --git a/apps/readest-app/src/services/constants.ts b/apps/readest-app/src/services/constants.ts index 83245af1..52aeb557 100644 --- a/apps/readest-app/src/services/constants.ts +++ b/apps/readest-app/src/services/constants.ts @@ -221,9 +221,6 @@ export const DEFAULT_BOOK_STYLE: BookStyle = { fullJustification: true, hyphenation: true, theme: 'light', - overrideFont: false, - overrideLayout: false, - overrideColor: false, backgroundTextureId: 'none', backgroundOpacity: 0.6, backgroundSize: 'cover', @@ -233,6 +230,11 @@ export const DEFAULT_BOOK_STYLE: BookStyle = { userStylesheet: '', userUIStylesheet: '', + overrideFont: false, + overrideLayout: false, + overrideColor: false, + useBookLayout: false, + zoomMode: 'fit-page', spreadMode: 'auto', keepCoverSpread: true, diff --git a/apps/readest-app/src/types/book.ts b/apps/readest-app/src/types/book.ts index 589e8664..fd716296 100644 --- a/apps/readest-app/src/types/book.ts +++ b/apps/readest-app/src/types/book.ts @@ -185,9 +185,6 @@ export interface BookStyle { fullJustification: boolean; hyphenation: boolean; theme: string; - overrideFont: boolean; - overrideLayout: boolean; - overrideColor: boolean; backgroundTextureId: string; backgroundOpacity: number; backgroundSize: string; @@ -197,6 +194,11 @@ export interface BookStyle { userStylesheet: string; userUIStylesheet: string; + overrideFont: boolean; + overrideLayout: boolean; + overrideColor: boolean; + useBookLayout: boolean; + // fixed-layout specific zoomMode: 'fit-page' | 'fit-width' | 'original-size' | 'custom'; spreadMode: 'auto' | 'none'; diff --git a/apps/readest-app/src/utils/style.ts b/apps/readest-app/src/utils/style.ts index 7e398e4a..0cc9565c 100644 --- a/apps/readest-app/src/utils/style.ts +++ b/apps/readest-app/src/utils/style.ts @@ -238,46 +238,24 @@ const getColorStyles = ( return colorStyles; }; -const getLayoutStyles = ( - overrideLayout: boolean, +const getPageLayoutStyles = ( marginTop: number, marginRight: number, marginBottom: number, marginLeft: number, - paragraphMargin: number, - lineSpacing: number, - wordSpacing: number, - letterSpacing: number, - textIndent: number, - justify: boolean, - hyphenate: boolean, zoomLevel: number, writingMode: string, vertical: boolean, -) => { - const layoutStyle = ` +) => ` @namespace epub "http://www.idpf.org/2007/ops"; html { - --default-text-align: ${justify ? 'justify' : 'start'}; --margin-top: ${marginTop}px; --margin-right: ${marginRight}px; --margin-bottom: ${marginBottom}px; --margin-left: ${marginLeft}px; - hanging-punctuation: allow-end last; - orphans: 2; - widows: 2; - } - [align="left"] { text-align: left; } - [align="right"] { text-align: right; } - [align="center"] { text-align: center; } - [align="justify"] { text-align: justify; } - :is(hgroup, header) p { - text-align: unset; - hyphens: unset; } html, body { ${writingMode === 'auto' ? '' : `writing-mode: ${writingMode} !important;`} - text-align: var(--default-text-align); max-height: unset; -webkit-touch-callout: none; -webkit-user-select: text; @@ -306,6 +284,116 @@ const getLayoutStyles = ( position: absolute; inset: -10px; } + pre { + white-space: pre-wrap !important; + } + + .epubtype-footnote, + aside[epub|type~="endnote"], + aside[epub|type~="footnote"], + aside[epub|type~="note"], + aside[epub|type~="rearnote"] { + display: none; + } + + /* Now begins really dirty hacks to fix some badly designed epubs */ + body { + line-height: unset; + } + + .duokan-footnote-content, + .duokan-footnote-item { + display: none; + } + + div:has(> img, > svg) { + max-width: 100% !important; + } + + body.paginated-mode td:has(img), body.paginated-mode td :has(img) { + max-height: calc(var(--available-height) * 0.8 * 1px); + } + + /* some epubs set insane inline-block for p */ + p { + display: block; + } + + /* inline images without dimension */ + .ie6 img { + width: unset; + height: unset; + } + sup img { + height: 1em; + } + img.has-text-siblings { + ${vertical ? 'width: 1em;' : 'height: 1em;'} + vertical-align: baseline; + } + :is(div) > img.has-text-siblings[style*="object-fit"] { + display: block; + height: auto; + vertical-align: unset; + } + .duokan-footnote img:not([class]) { + width: 0.8em; + height: 0.8em; + } + div:has(img.singlepage) { + position: relative; + width: auto; + height: auto; + } + + /* page break */ + body.paginated-mode div[style*="page-break-after: always"], + body.paginated-mode div[style*="page-break-after:always"], + body.paginated-mode p[style*="page-break-after: always"], + body.paginated-mode p[style*="page-break-after:always"] { + margin-bottom: calc(var(--available-height) * 1px); + } + + .br { + display: flow-root; + } + + .h5_mainbody { + overflow: unset !important; + } +`; + +// Paragraph-scoped CSS controlled by the Paragraph section of the Layout +// panel. Gated by BookStyle.useBookLayout: when true, this chunk is skipped +// and the book's own paragraph formatting takes over. +const getParagraphLayoutStyles = ( + overrideLayout: boolean, + paragraphMargin: number, + lineSpacing: number, + wordSpacing: number, + letterSpacing: number, + textIndent: number, + justify: boolean, + hyphenate: boolean, + vertical: boolean, +) => ` + html { + --default-text-align: ${justify ? 'justify' : 'start'}; + hanging-punctuation: allow-end last; + orphans: 2; + widows: 2; + } + html, body { + text-align: var(--default-text-align); + } + [align="left"] { text-align: left; } + [align="right"] { text-align: right; } + [align="center"] { text-align: center; } + [align="justify"] { text-align: justify; } + :is(hgroup, header) p { + text-align: unset; + hyphens: unset; + } p, blockquote, dd, div:not(:has(*:not(b, a, em, i, strong, u, span))) { line-height: ${lineSpacing} ${overrideLayout ? '!important' : ''}; word-spacing: ${wordSpacing}px ${overrideLayout ? '!important' : ''}; @@ -374,22 +462,11 @@ const getLayoutStyles = ( orphans: 1; } - pre { - white-space: pre-wrap !important; - } - - .epubtype-footnote, - aside[epub|type~="endnote"], - aside[epub|type~="footnote"], - aside[epub|type~="note"], - aside[epub|type~="rearnote"] { - display: none; - } - - /* Now begins really dirty hacks to fix some badly designed epubs */ - body { - line-height: unset; - } + /* workaround for some badly designed epubs */ + div.left *, p.left * { text-align: left; } + div.right *, p.right * { text-align: right; } + div.center *, p.center * { text-align: center; } + div.justify *, p.justify * { text-align: justify; } img.pi { ${vertical ? 'transform: rotate(90deg);' : ''} @@ -399,79 +476,10 @@ const getLayoutStyles = ( ${vertical ? `vertical-align: unset;` : ''} } - .duokan-footnote-content, - .duokan-footnote-item { - display: none; - } - - div:has(> img, > svg) { - max-width: 100% !important; - } - - body.paginated-mode td:has(img), body.paginated-mode td :has(img) { - max-height: calc(var(--available-height) * 0.8 * 1px); - } - - /* some epubs set insane inline-block for p */ - p { - display: block; - } - - /* inline images without dimension */ - .ie6 img { - width: unset; - height: unset; - } - sup img { - height: 1em; - } - img.has-text-siblings { - ${vertical ? 'width: 1em;' : 'height: 1em;'} - vertical-align: baseline; - } - :is(div) > img.has-text-siblings[style*="object-fit"] { - display: block; - height: auto; - vertical-align: unset; - } - .duokan-footnote img:not([class]) { - width: 0.8em; - height: 0.8em; - } - div:has(img.singlepage) { - position: relative; - width: auto; - height: auto; - } - - /* page break */ - body.paginated-mode div[style*="page-break-after: always"], - body.paginated-mode div[style*="page-break-after:always"], - body.paginated-mode p[style*="page-break-after: always"], - body.paginated-mode p[style*="page-break-after:always"] { - margin-bottom: calc(var(--available-height) * 1px); - } - - /* workaround for some badly designed epubs */ - div.left *, p.left * { text-align: left; } - div.right *, p.right * { text-align: right; } - div.center *, p.center * { text-align: center; } - div.justify *, p.justify * { text-align: justify; } - - .br { - display: flow-root; - } - - .h5_mainbody { - overflow: unset !important; - } - .nonindent, .noindent { text-indent: unset !important; } `; - return layoutStyle; -}; export const getFootnoteStyles = () => ` .duokan-footnote-content, @@ -587,23 +595,28 @@ export const getStyles = (viewSettings: ViewSettings, themeCode?: ThemeCode) => if (!themeCode) { themeCode = getThemeCode(); } - const layoutStyles = getLayoutStyles( - viewSettings.overrideLayout!, + const pageLayoutStyles = getPageLayoutStyles( viewSettings.marginTopPx, viewSettings.marginRightPx, viewSettings.marginBottomPx, viewSettings.marginLeftPx, - viewSettings.paragraphMargin!, - viewSettings.lineHeight!, - viewSettings.wordSpacing!, - viewSettings.letterSpacing!, - viewSettings.textIndent!, - viewSettings.fullJustification!, - viewSettings.hyphenation!, 1.0, viewSettings.writingMode!, viewSettings.vertical!, ); + const paragraphLayoutStyles = viewSettings.useBookLayout + ? '' + : getParagraphLayoutStyles( + viewSettings.overrideLayout!, + viewSettings.paragraphMargin!, + viewSettings.lineHeight!, + viewSettings.wordSpacing!, + viewSettings.letterSpacing!, + viewSettings.textIndent!, + viewSettings.fullJustification!, + viewSettings.hyphenation!, + viewSettings.vertical!, + ); // scale the font size on-the-fly so that we can sync the same font size on different devices const isMobile = ['ios', 'android'].includes(getOSPlatform()); const fontScale = isMobile ? 1.25 : 1; @@ -629,7 +642,7 @@ export const getStyles = (viewSettings: ViewSettings, themeCode?: ThemeCode) => ); const translationStyles = getTranslationStyles(viewSettings.showTranslateSource!); const userStylesheet = viewSettings.userStylesheet!; - return `${layoutStyles}\n${fontStyles}\n${colorStyles}\n${translationStyles}\n${userStylesheet}`; + return `${pageLayoutStyles}\n${paragraphLayoutStyles}\n${fontStyles}\n${colorStyles}\n${translationStyles}\n${userStylesheet}`; }; export const applyTranslationStyle = (viewSettings: ViewSettings) => {