feat(reader): add light/dark mode toggle for reading themes

Add explicit light/dark mode toggle switch to the reading theme settings.
The reading mode defaults based on the chrome theme (dark chrome themes
like tokyo-night default to dark reading mode).

- Add readingMode property to readerShell Alpine component
- Add toggleReadingMode() method that toggles dark class on body
- Add detectChromeDarkMode() to infer default from chrome theme
- Update applyTheme() to add/remove dark class and persist reading_mode
- Add toggle switch UI in settings panel (blue pill style, next to
  Reading Theme heading)
- Add reading_mode to default settings in settings-manager
This commit is contained in:
2026-04-19 20:05:42 -04:00
parent b983f2cd2e
commit 48a8716a25
5 changed files with 53 additions and 10 deletions
+31 -3
View File
@@ -84,7 +84,8 @@ document.addEventListener("alpine:init", () => {
bookmarksOpen: false,
navigatorOpen: false,
tocItems: [] as any[],
readingTheme: "dark" as string,
readingTheme: "light" as string,
readingMode: "light" as string,
readingFont: "literata" as string,
fontSize: 16 as number,
lineHeight: 1.6 as number,
@@ -97,13 +98,21 @@ document.addEventListener("alpine:init", () => {
}) {
this.settings = await loadSettings();
if (this.settings) {
this.readingTheme = this.settings.reading_theme || "dark";
this.readingTheme = this.settings.reading_theme || "light";
this.readingFont = this.settings.reading_font || "literata";
this.fontSize = this.settings.font_size || 16;
this.lineHeight = this.settings.line_height || 1.6;
if (this.settings.reading_theme) {
document.body.classList.add(`theme-${this.settings.reading_theme}`);
}
if (this.settings.reading_mode) {
this.readingMode = this.settings.reading_mode;
} else {
this.readingMode = this.detectChromeDarkMode() ? "dark" : "light";
}
if (this.readingMode === "dark") {
document.body.classList.add("dark");
}
}
this.view = document.getElementById("reader-view") as any;
const resp = await fetch(config.fileUrl, {
@@ -258,10 +267,29 @@ document.addEventListener("alpine:init", () => {
applyTheme() {
document.body.className = document.body.className
.replace(/theme-(?!tokyo|dracula|nord|solarized|monokai|one-dark|material|catppuccin)[\w-]+/g, "")
.replace(/\bdark\b/g, "")
.trim();
document.body.classList.add(`theme-${this.readingTheme}`);
if (this.readingMode === "dark") {
document.body.classList.add("dark");
}
this.applyStyles();
saveSettings({ reading_theme: this.readingTheme });
saveSettings({
reading_theme: this.readingTheme,
reading_mode: this.readingMode,
});
},
toggleReadingMode() {
this.readingMode = this.readingMode === "dark" ? "light" : "dark";
this.applyTheme();
},
detectChromeDarkMode(): boolean {
const darkChromeThemes = [
"theme-tokyo-night", "theme-dracula", "theme-nord",
"theme-monokai", "theme-one-dark", "theme-material",
"theme-catppuccin",
];
return darkChromeThemes.some((t) => document.body.classList.contains(t));
},
applyFont() {
this.applyStyles();
+2 -1
View File
@@ -70,7 +70,8 @@ export function getDefaultSettings(): ReaderSettings {
chrome_behavior: "auto-hide",
progress_mode: "pages",
chrome_theme: "tokyo-night",
reading_theme: "dark",
reading_theme: "light",
reading_mode: "dark",
reading_font: "literata",
tap_zone_size: 30,
auto_scroll: false,
+1 -1
View File
File diff suppressed because one or more lines are too long