From d30d1c17ae6dda6abe5d712ff09636a184d0941d Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Thu, 6 Mar 2025 17:58:32 +0800 Subject: [PATCH] rtl: rtl page layout from settings and book language, closes #359 (#500) --- .../app/reader/components/FoliateViewer.tsx | 28 +++++++++++-------- .../components/settings/LayoutPanel.tsx | 26 ++++++++++++++--- apps/readest-app/src/utils/book.ts | 18 ++++++++++-- 3 files changed, 53 insertions(+), 19 deletions(-) diff --git a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx index 5f55ae82..1cc6ea9c 100644 --- a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx +++ b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx @@ -10,7 +10,7 @@ import { useProgressSync } from '../hooks/useProgressSync'; import { useProgressAutoSave } from '../hooks/useProgressAutoSave'; import { useAutoHideScrollbar } from '../hooks/useAutoHideScrollbar'; import { getStyles, mountAdditionalFonts } from '@/utils/style'; -import { getBookDirFromWritingMode } from '@/utils/book'; +import { getBookDirFromLanguage, getBookDirFromWritingMode } from '@/utils/book'; import { useTheme } from '@/hooks/useTheme'; import { handleKeydown, @@ -58,8 +58,9 @@ const FoliateViewer: React.FC<{ if (detail.doc) { const writingDir = viewRef.current?.renderer.setStyles && getDirection(detail.doc); const viewSettings = getViewSettings(bookKey)!; - viewSettings.vertical = writingDir?.vertical || false; - viewSettings.rtl = writingDir?.rtl || false; + viewSettings.vertical = + writingDir?.vertical || viewSettings.writingMode.includes('vertical') || false; + viewSettings.rtl = writingDir?.rtl || viewSettings.writingMode.includes('rl') || false; setViewSettings(bookKey, viewSettings); if (viewSettings.scrolled && shouldAutoHideScrollbar) { handleScrollbarAutoHide(detail.doc); @@ -137,22 +138,25 @@ const FoliateViewer: React.FC<{ document.body.append(view); containerRef.current?.appendChild(view); + const viewSettings = getViewSettings(bookKey)!; + const writingMode = viewSettings.writingMode; + if (writingMode) { + const settingsDir = getBookDirFromWritingMode(writingMode); + const languageDir = getBookDirFromLanguage(bookDoc.metadata.language); + if (settingsDir !== 'auto') { + bookDoc.dir = settingsDir; + } else if (languageDir !== 'auto') { + bookDoc.dir = languageDir; + } + } + await view.open(bookDoc); // make sure we can listen renderer events after opening book viewRef.current = view; setFoliateView(bookKey, view); - const viewSettings = getViewSettings(bookKey)!; view.renderer.setStyles?.(getStyles(viewSettings, themeCode)); - const writingMode = viewSettings.writingMode; - if (writingMode) { - const dir = getBookDirFromWritingMode(writingMode); - if (dir !== 'auto') { - view.book.dir = dir; - } - } - const isScrolled = viewSettings.scrolled!; const marginPx = viewSettings.marginPx!; const gapPercent = viewSettings.gapPercent!; diff --git a/apps/readest-app/src/app/reader/components/settings/LayoutPanel.tsx b/apps/readest-app/src/app/reader/components/settings/LayoutPanel.tsx index 61e7e0f5..1a03123f 100644 --- a/apps/readest-app/src/app/reader/components/settings/LayoutPanel.tsx +++ b/apps/readest-app/src/app/reader/components/settings/LayoutPanel.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react'; import { MdOutlineAutoMode } from 'react-icons/md'; -import { MdOutlineTextRotationDown, MdOutlineTextRotationNone } from 'react-icons/md'; +import { MdOutlineTextRotationNone, MdTextRotateVertical } from 'react-icons/md'; +import { TbTextDirectionRtl } from 'react-icons/tb'; import { useSettingsStore } from '@/store/settingsStore'; import { useReaderStore } from '@/store/readerStore'; @@ -175,12 +176,19 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { useEffect(() => { // global settings are not supported for writing mode + const prevWritingMode = viewSettings.writingMode; viewSettings.writingMode = writingMode; setViewSettings(bookKey, viewSettings); if (view) { view.renderer.setStyles?.(getStyles(viewSettings, themeCode)); view.book.dir = getBookDirFromWritingMode(writingMode); } + if ( + prevWritingMode !== writingMode && + (writingMode === 'horizontal-rl' || prevWritingMode === 'horizontal-rl') + ) { + setTimeout(() => window.location.reload(), 100); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [writingMode]); @@ -196,11 +204,12 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { }, [overrideLayout]); const langCode = getBookLangCode(bookData.bookDoc?.metadata?.language); - const isCJKBook = langCode === 'zh' || langCode === 'ja' || langCode === 'ko'; + const mightBeRTLBook = + langCode === 'zh' || langCode === 'ja' || langCode === 'ko' || langCode === ''; return (
- {isCJKBook && ( + {mightBeRTLBook && (

{_('Writing Mode')}

@@ -228,7 +237,16 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { className={`btn btn-ghost btn-circle ${writingMode === 'vertical-rl' ? 'btn-active bg-base-300' : ''}`} onClick={() => setWritingMode('vertical-rl')} > - + + +
+ +
+
diff --git a/apps/readest-app/src/utils/book.ts b/apps/readest-app/src/utils/book.ts index f2fbd24b..71c3e743 100644 --- a/apps/readest-app/src/utils/book.ts +++ b/apps/readest-app/src/utils/book.ts @@ -57,9 +57,9 @@ export const listFormater = (narrow = false, lang = userLang) => { export const getBookLangCode = (lang: string | string[] | undefined) => { try { const bookLang = typeof lang === 'string' ? lang : lang?.[0]; - return bookLang ? bookLang.split('-')[0]! : 'en'; + return bookLang ? bookLang.split('-')[0]! : ''; } catch { - return 'en'; + return ''; } }; @@ -67,7 +67,7 @@ export const formatAuthors = ( contributors: string | Contributor | [string | Contributor], bookLang?: string | string[], ) => { - const langCode = getBookLangCode(bookLang); + const langCode = getBookLangCode(bookLang) || 'en'; return Array.isArray(contributors) ? listFormater(langCode === 'zh', langCode).format( contributors.map((contributor) => @@ -90,6 +90,10 @@ export const formatLanguage = (lang: string | string[] | undefined) => { return Array.isArray(lang) ? lang.join(', ') : lang; }; +export const primaryLanguage = (lang: string | string[] | undefined) => { + return Array.isArray(lang) ? lang[0] : lang; +}; + export const formatDate = (date: string | number | Date | undefined) => { if (!date) return; try { @@ -131,3 +135,11 @@ export const getBookDirFromWritingMode = (writingMode: WritingMode) => { return 'auto'; } }; + +export const getBookDirFromLanguage = (language: string | string[] | undefined) => { + const lang = primaryLanguage(language); + if (!lang) return 'auto'; + const rtlLanguages = new Set(['ar', 'he', 'fa', 'ur', 'dv', 'ps', 'sd', 'yi']); + const primaryLang = lang.split('-')[0]!.toLowerCase(); + return rtlLanguages.has(primaryLang) ? 'rtl' : 'auto'; +};