Typography was in the stylesheet foliate prepends, which the book overrides; moved to the appended one. The window keydown handler was never bound. Also: wider reading area, page on a card, arrows moved onto it, spread gutter, and the sidebar stays open on chapter clicks.
87 lines
2.8 KiB
TypeScript
87 lines
2.8 KiB
TypeScript
import { FONT_STACKS } from '$lib/theme/presets';
|
|
import { readerSettingsSchema, type ReaderSettings } from '$lib/schema/reader';
|
|
|
|
export const READER_SETTINGS_STORAGE_KEY = 'chitai:reader-settings';
|
|
|
|
/** Georgia — the app's own title face, and a reasonable default for long prose. */
|
|
export const DEFAULT_READER_SETTINGS: ReaderSettings = {
|
|
fontFamily: FONT_STACKS[1].value,
|
|
fontSize: 18,
|
|
fontWeight: 400,
|
|
lineHeight: 1.6,
|
|
letterSpacing: 0,
|
|
margin: 48,
|
|
gap: 6,
|
|
// foliate caps the reading area at this times the column count, so 1000 gives
|
|
// a 2000px spread — enough to fill a large display rather than leaving the
|
|
// book as a narrow strip. Its own default of 720 is noticeably tight.
|
|
maxInlineSize: 1000,
|
|
maxColumnCount: 2,
|
|
flow: 'paginated',
|
|
justify: true,
|
|
hyphenate: true
|
|
};
|
|
|
|
/** Reused from the app theme: system stacks only, so nothing silently falls back. */
|
|
export const READER_FONT_OPTIONS = FONT_STACKS;
|
|
|
|
/** Slider bounds, kept next to the schema they mirror. */
|
|
export const READER_BOUNDS = {
|
|
fontSize: { min: 12, max: 32, step: 1 },
|
|
fontWeight: { min: 300, max: 700, step: 100 },
|
|
lineHeight: { min: 1, max: 2.5, step: 0.05 },
|
|
letterSpacing: { min: -0.05, max: 0.2, step: 0.01 },
|
|
margin: { min: 0, max: 120, step: 4 },
|
|
gap: { min: 0, max: 15, step: 1 },
|
|
maxInlineSize: { min: 400, max: 2400, step: 20 }
|
|
} as const;
|
|
|
|
/**
|
|
* Reads stored settings, falling back to defaults on anything unusable.
|
|
*
|
|
* Unknown keys are stripped and missing ones filled, so settings added in a
|
|
* later release do not invalidate what a reader already has stored.
|
|
*/
|
|
export function loadReaderSettings(): ReaderSettings {
|
|
try {
|
|
const raw = localStorage.getItem(READER_SETTINGS_STORAGE_KEY);
|
|
if (!raw) return DEFAULT_READER_SETTINGS;
|
|
const parsed = readerSettingsSchema.safeParse({
|
|
...DEFAULT_READER_SETTINGS,
|
|
...JSON.parse(raw)
|
|
});
|
|
return parsed.success ? parsed.data : DEFAULT_READER_SETTINGS;
|
|
} catch {
|
|
return DEFAULT_READER_SETTINGS;
|
|
}
|
|
}
|
|
|
|
export function saveReaderSettings(settings: ReaderSettings) {
|
|
try {
|
|
localStorage.setItem(READER_SETTINGS_STORAGE_KEY, JSON.stringify(settings));
|
|
} catch (error) {
|
|
// A full quota must not stop anyone reading.
|
|
console.warn('Could not save reader settings', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The half of the settings that foliate takes as renderer attributes.
|
|
*
|
|
* The renderer has no JS property API — these must be set with setAttribute.
|
|
* Note there is no `margin` shorthand upstream, and no `spread`: on a reflowable
|
|
* book a two-page spread is max-column-count 2.
|
|
*/
|
|
export function toRendererAttributes(s: ReaderSettings): Record<string, string> {
|
|
return {
|
|
flow: s.flow,
|
|
gap: `${s.gap}%`,
|
|
'margin-top': `${s.margin}px`,
|
|
'margin-bottom': `${s.margin}px`,
|
|
'margin-left': `${s.margin}px`,
|
|
'margin-right': `${s.margin}px`,
|
|
'max-inline-size': `${s.maxInlineSize}px`,
|
|
'max-column-count': String(s.maxColumnCount)
|
|
};
|
|
}
|