From aaa8e46b81bda8da0b4ebddbc802167eb52472e5 Mon Sep 17 00:00:00 2001 From: mattenarle10 Date: Sat, 27 Jun 2026 11:38:03 +0800 Subject: [PATCH 1/6] fix: polish reading display controls --- src/app.css | 16 +++- src/app.tsx | 34 ++++++- src/components/chrome/breadcrumb.tsx | 12 +++ src/components/chrome/theme-button.tsx | 36 +++++++- src/components/chrome/title-bar.tsx | 12 +++ src/hooks/use-selection-sync-text.ts | 123 +------------------------ src/lib/index.ts | 9 ++ src/lib/storage.ts | 3 + src/lib/theme.ts | 2 +- src/lib/writing-display.ts | 48 ++++++++++ src/locales/en.json | 11 ++- src/styles/editor/prose.css | 4 +- src/styles/tokens.css | 18 ++-- tests/writing-display.test.ts | 28 ++++++ 14 files changed, 216 insertions(+), 140 deletions(-) create mode 100644 tests/writing-display.test.ts diff --git a/src/app.css b/src/app.css index c68df99..f26dbb6 100644 --- a/src/app.css +++ b/src/app.css @@ -20,8 +20,12 @@ .mdv-app { --mdv-writing-font-size: 14px; --mdv-writing-line-height: 1.55; + --mdv-prose-font-family: var(--font-ui); --mdv-prose-font-size: 15px; --mdv-prose-line-height: 1.65; + --mdv-reading-font-size: 16px; + --mdv-reading-content-width: 880px; + --mdv-reading-prose-width: 720px; display: grid; grid-template-rows: var(--titlebar-h) var(--breadcrumb-h) 1fr var(--statusbar-h); height: 100vh; @@ -62,13 +66,17 @@ display: flex; } +html.is-mac .mdv-app.has-hidden-titlebar:not(.is-reading) .mdv-breadcrumb { + padding-left: 84px; +} + /* iA Writer-style reading mode: wide max-width, generous breathing room, subtle warmer tint, and slow calm fade-in */ .mdv-app.is-reading .mdv-shell > .mdv-preview { flex: 1; min-width: 0; - padding-left: max(56px, calc((100% - 880px) / 2)); - padding-right: max(56px, calc((100% - 880px) / 2)); + padding-left: max(56px, calc((100% - var(--mdv-reading-content-width)) / 2)); + padding-right: max(56px, calc((100% - var(--mdv-reading-content-width)) / 2)); padding-top: 80px; padding-bottom: 96px; background: color-mix(in srgb, var(--bg) 96%, var(--fg) 4%); @@ -76,8 +84,8 @@ } .mdv-app.is-reading .mdv-shell > .mdv-preview .mdv-prose { - max-width: 720px; - font-size: 16px; + max-width: var(--mdv-reading-prose-width); + font-size: var(--mdv-reading-font-size); line-height: 1.75; } diff --git a/src/app.tsx b/src/app.tsx index 17de59e..04f28de 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -37,6 +37,9 @@ import { getContextBundleStats, getWhatsNewToastMessage, isSupportedTextPath, + normalizeProseFontFamily, + normalizeReadingFontSize, + normalizeReadingWidth, normalizeWritingFontSize, normalizeWritingLineHeight, PdfExportError, @@ -47,6 +50,9 @@ import { removeEntry, STORAGE_KEYS, useI18n, + type ProseFontFamily, + type ReadingFontSize, + type ReadingWidth, type WritingDisplay, type WritingFontSize, type WritingLineHeight, @@ -260,6 +266,18 @@ export function App() { STORAGE_KEYS.writingLineHeight, DEFAULT_WRITING_DISPLAY.lineHeight, ); + const [readingFontSize, setReadingFontSize] = usePersistedState( + STORAGE_KEYS.readingFontSize, + DEFAULT_WRITING_DISPLAY.readingFontSize, + ); + const [readingWidth, setReadingWidth] = usePersistedState( + STORAGE_KEYS.readingWidth, + DEFAULT_WRITING_DISPLAY.readingWidth, + ); + const [proseFontFamily, setProseFontFamily] = usePersistedState( + STORAGE_KEYS.proseFontFamily, + DEFAULT_WRITING_DISPLAY.proseFontFamily, + ); const [dragActive, setDragActive] = useState(false); const [stagedPaths, setStagedPaths] = useState([]); const [stagedTokenLabel, setStagedTokenLabel] = useState("0"); @@ -269,8 +287,11 @@ export function App() { () => ({ fontSize: normalizeWritingFontSize(writingFontSize), lineHeight: normalizeWritingLineHeight(writingLineHeight), + readingFontSize: normalizeReadingFontSize(readingFontSize), + readingWidth: normalizeReadingWidth(readingWidth), + proseFontFamily: normalizeProseFontFamily(proseFontFamily), }), - [writingFontSize, writingLineHeight], + [writingFontSize, writingLineHeight, readingFontSize, readingWidth, proseFontFamily], ); const writingDisplayStyle = useMemo( @@ -281,7 +302,10 @@ export function App() { const resetWritingDisplay = useCallback(() => { setWritingFontSize(DEFAULT_WRITING_DISPLAY.fontSize); setWritingLineHeight(DEFAULT_WRITING_DISPLAY.lineHeight); - }, [setWritingFontSize, setWritingLineHeight]); + setReadingFontSize(DEFAULT_WRITING_DISPLAY.readingFontSize); + setReadingWidth(DEFAULT_WRITING_DISPLAY.readingWidth); + setProseFontFamily(DEFAULT_WRITING_DISPLAY.proseFontFamily); + }, [setWritingFontSize, setWritingLineHeight, setReadingFontSize, setReadingWidth, setProseFontFamily]); const handleToggleSidebar = useCallback(() => { setSidebarOpen((v: boolean) => !v); @@ -815,6 +839,9 @@ export function App() { writingDisplay={writingDisplay} onWritingFontSizeChange={setWritingFontSize} onWritingLineHeightChange={setWritingLineHeight} + onReadingFontSizeChange={setReadingFontSize} + onReadingWidthChange={setReadingWidth} + onProseFontFamilyChange={setProseFontFamily} onResetWritingDisplay={resetWritingDisplay} /> @@ -839,6 +866,9 @@ export function App() { writingDisplay={writingDisplay} onWritingFontSizeChange={setWritingFontSize} onWritingLineHeightChange={setWritingLineHeight} + onReadingFontSizeChange={setReadingFontSize} + onReadingWidthChange={setReadingWidth} + onProseFontFamilyChange={setProseFontFamily} onResetWritingDisplay={resetWritingDisplay} /> diff --git a/src/components/chrome/breadcrumb.tsx b/src/components/chrome/breadcrumb.tsx index 20759bb..67d22ad 100644 --- a/src/components/chrome/breadcrumb.tsx +++ b/src/components/chrome/breadcrumb.tsx @@ -18,6 +18,9 @@ import { startWindowDrag, useI18n, type Translate, + type ProseFontFamily, + type ReadingFontSize, + type ReadingWidth, type WritingDisplay, type WritingFontSize, type WritingLineHeight, @@ -48,6 +51,9 @@ type BreadcrumbProps = { writingDisplay: WritingDisplay; onWritingFontSizeChange: (value: WritingFontSize) => void; onWritingLineHeightChange: (value: WritingLineHeight) => void; + onReadingFontSizeChange: (value: ReadingFontSize) => void; + onReadingWidthChange: (value: ReadingWidth) => void; + onProseFontFamilyChange: (value: ProseFontFamily) => void; onResetWritingDisplay: () => void; }; @@ -93,6 +99,9 @@ export function Breadcrumb({ writingDisplay, onWritingFontSizeChange, onWritingLineHeightChange, + onReadingFontSizeChange, + onReadingWidthChange, + onProseFontFamilyChange, onResetWritingDisplay, }: BreadcrumbProps) { const { t } = useI18n(); @@ -181,6 +190,9 @@ export function Breadcrumb({ writingDisplay={writingDisplay} onWritingFontSizeChange={onWritingFontSizeChange} onWritingLineHeightChange={onWritingLineHeightChange} + onReadingFontSizeChange={onReadingFontSizeChange} + onReadingWidthChange={onReadingWidthChange} + onProseFontFamilyChange={onProseFontFamilyChange} onResetWritingDisplay={onResetWritingDisplay} /> diff --git a/src/components/chrome/theme-button.tsx b/src/components/chrome/theme-button.tsx index 4003357..a330149 100644 --- a/src/components/chrome/theme-button.tsx +++ b/src/components/chrome/theme-button.tsx @@ -20,13 +20,19 @@ import { Button, Icon, Popover } from "@/components/primitives"; import { getSystemTheme, LANGUAGE_CHOICES, + PROSE_FONT_FAMILY_OPTIONS, previewTheme, + READING_FONT_SIZE_OPTIONS, + READING_WIDTH_OPTIONS, THEME_GROUPS, WRITING_FONT_SIZE_OPTIONS, WRITING_LINE_HEIGHT_OPTIONS, useI18n, useThemeMode, useTransparency, + type ProseFontFamily, + type ReadingFontSize, + type ReadingWidth, type Theme, type ThemeMode, type WritingDisplay, @@ -58,6 +64,9 @@ type ThemeButtonProps = { writingDisplay: WritingDisplay; onWritingFontSizeChange: (value: WritingFontSize) => void; onWritingLineHeightChange: (value: WritingLineHeight) => void; + onReadingFontSizeChange: (value: ReadingFontSize) => void; + onReadingWidthChange: (value: ReadingWidth) => void; + onProseFontFamilyChange: (value: ProseFontFamily) => void; onResetWritingDisplay: () => void; }; @@ -109,6 +118,9 @@ export function ThemeButton({ writingDisplay, onWritingFontSizeChange, onWritingLineHeightChange, + onReadingFontSizeChange, + onReadingWidthChange, + onProseFontFamilyChange, onResetWritingDisplay, }: ThemeButtonProps) { const { language, setLanguage, t } = useI18n(); @@ -245,7 +257,8 @@ export function ThemeButton({ {opacity >= 100 ? t("title.off") : `${100 - opacity}%`} /{" "} {t(`writing.font.${writingDisplay.fontSize}`)} /{" "} - {t(`writing.spacing.${writingDisplay.lineHeight}`)} + {t(`writing.spacing.${writingDisplay.lineHeight}`)} /{" "} + {t(`reading.width.${writingDisplay.readingWidth}`)} @@ -289,6 +302,27 @@ export function ThemeButton({ labelFor={(option) => t(`writing.spacing.${option}`)} onChange={onWritingLineHeightChange} /> + t(`writing.font.${option}`)} + onChange={onReadingFontSizeChange} + /> + t(`reading.width.${option}`)} + onChange={onReadingWidthChange} + /> + t(`prose.font.${option}`)} + onChange={onProseFontFamilyChange} + /> - ) : null} - + ) : null} +