fix(reader): scope reading theme to viewport, wire up font/size/line-height to shadow DOM
Two root causes fixed: 1. Reading theme CSS variables were on document.body, leaking font/color into chrome UI. Now scoped to #reader-viewport so chrome keeps its own theme (system font, --text-primary colors) while the reading area uses reading theme colors/background. 2. getCSS() never received font family, font size, or line height settings. The settings UI (dropdowns, sliders) saved values but they were never injected into the book's shadow DOM. Now getCSS() accepts all four settings and generates proper CSS rules for them. Changes: - Wrap foliate-view in #reader-viewport div (absolute positioned between chrome bars) - getCSS() reads computed style from #reader-viewport, not document.body - getCSS() params expanded: fontFamily, fontSize, lineHeight, justify, hyphenate (removed unused 'spacing') - Added FONT_MAP to translate setting keys to CSS font-family values - applyTheme() targets #reader-viewport instead of document.body - Removed dead #reader-viewport typography rules from foliate-themes.css (shadow DOM doesn't inherit outer styles), kept only background-color
This commit is contained in:
@@ -43,7 +43,9 @@ templ Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookm
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<foliate-view id="reader-view" class="absolute inset-x-0 top-[52px] bottom-[52px]"></foliate-view>
|
<div id="reader-viewport" class="absolute inset-x-0 top-[52px] bottom-[52px]">
|
||||||
|
<foliate-view id="reader-view" class="block w-full h-full"></foliate-view>
|
||||||
|
</div>
|
||||||
@DictionaryPopup()
|
@DictionaryPopup()
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ func Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookma
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div></div></div><foliate-view id=\"reader-view\" class=\"absolute inset-x-0 top-[52px] bottom-[52px]\"></foliate-view>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div></div></div><div id=\"reader-viewport\" class=\"absolute inset-x-0 top-[52px] bottom-[52px]\"><foliate-view id=\"reader-view\" class=\"block w-full h-full\"></foliate-view></div>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -141,7 +141,7 @@ func ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
|
|||||||
var templ_7745c5c3_Var5 string
|
var templ_7745c5c3_Var5 string
|
||||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(metadata.Title)
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(metadata.Title)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 60, Col: 54}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 62, Col: 54}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -154,7 +154,7 @@ func ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
|
|||||||
var templ_7745c5c3_Var6 string
|
var templ_7745c5c3_Var6 string
|
||||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d/%d", progress.CurrentPage, progress.TotalPages))
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d/%d", progress.CurrentPage, progress.TotalPages))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 137, Col: 70}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 139, Col: 70}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -293,7 +293,7 @@ func ReaderBookmarksPanel(bookmarks []Bookmark) templ.Component {
|
|||||||
var templ_7745c5c3_Var11 string
|
var templ_7745c5c3_Var11 string
|
||||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(bookmark.CfiPosition)
|
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(bookmark.CfiPosition)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 349, Col: 38}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 351, Col: 38}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -306,7 +306,7 @@ func ReaderBookmarksPanel(bookmarks []Bookmark) templ.Component {
|
|||||||
var templ_7745c5c3_Var12 string
|
var templ_7745c5c3_Var12 string
|
||||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(bookmark.Title)
|
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(bookmark.Title)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 352, Col: 49}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 354, Col: 49}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -319,7 +319,7 @@ func ReaderBookmarksPanel(bookmarks []Bookmark) templ.Component {
|
|||||||
var templ_7745c5c3_Var13 string
|
var templ_7745c5c3_Var13 string
|
||||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(bookmark.Position)
|
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(bookmark.Position)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 354, Col: 27}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 356, Col: 27}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
|
|||||||
+47
-23
@@ -2,27 +2,48 @@ import "foliate-js/view.js";
|
|||||||
import { Alpine } from "../alpine";
|
import { Alpine } from "../alpine";
|
||||||
import { loadSettings, saveSettings } from "./settings-manager";
|
import { loadSettings, saveSettings } from "./settings-manager";
|
||||||
import { getToken } from "../storage";
|
import { getToken } from "../storage";
|
||||||
|
|
||||||
|
const FONT_MAP: Record<string, string> = {
|
||||||
|
literata: '"Literata"',
|
||||||
|
crimson: '"Crimson Pro"',
|
||||||
|
"source-serif": '"Source Serif 4"',
|
||||||
|
"eb-garamond": '"EB Garamond"',
|
||||||
|
libertinus: '"Libertinus Serif"',
|
||||||
|
"noto-serif": '"Noto Serif"',
|
||||||
|
"charis-sil": '"Charis SIL"',
|
||||||
|
"ibm-plex": '"IBM Plex Serif"',
|
||||||
|
};
|
||||||
|
|
||||||
const getCSS = ({
|
const getCSS = ({
|
||||||
spacing,
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
lineHeight,
|
||||||
justify,
|
justify,
|
||||||
hyphenate,
|
hyphenate,
|
||||||
}: {
|
}: {
|
||||||
spacing: number;
|
fontFamily: string;
|
||||||
|
fontSize: number;
|
||||||
|
lineHeight: number;
|
||||||
justify: boolean;
|
justify: boolean;
|
||||||
hyphenate: boolean;
|
hyphenate: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const style = getComputedStyle(document.body);
|
const el = document.getElementById("reader-viewport");
|
||||||
|
const style = getComputedStyle(el ?? document.body);
|
||||||
const bg = style.getPropertyValue("--reader-bg").trim() || "#fafafa";
|
const bg = style.getPropertyValue("--reader-bg").trim() || "#fafafa";
|
||||||
const text = style.getPropertyValue("--reader-text").trim() || "#1a1a1a";
|
const text = style.getPropertyValue("--reader-text").trim() || "#1a1a1a";
|
||||||
const link = style.getPropertyValue("--reader-link").trim() || "#0066cc";
|
const link = style.getPropertyValue("--reader-link").trim() || "#0066cc";
|
||||||
const selection =
|
const selection =
|
||||||
style.getPropertyValue("--reader-selection").trim() || "#b3d9ff";
|
style.getPropertyValue("--reader-selection").trim() || "#b3d9ff";
|
||||||
|
const font = FONT_MAP[fontFamily] ?? '"Literata"';
|
||||||
return `
|
return `
|
||||||
@namespace epub "http://www.idpf.org/2007/ops";
|
@namespace epub "http://www.idpf.org/2007/ops";
|
||||||
html {
|
html {
|
||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
background-color: ${bg};
|
background-color: ${bg};
|
||||||
color: ${text};
|
color: ${text};
|
||||||
|
font-family: ${font}, serif;
|
||||||
|
font-size: ${fontSize}px;
|
||||||
|
line-height: ${lineHeight};
|
||||||
}
|
}
|
||||||
::selection {
|
::selection {
|
||||||
background-color: ${selection};
|
background-color: ${selection};
|
||||||
@@ -36,7 +57,7 @@ const getCSS = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
p, li, blockquote, dd {
|
p, li, blockquote, dd {
|
||||||
line-height: ${spacing};
|
line-height: ${lineHeight};
|
||||||
text-align: ${justify ? "justify" : "start"};
|
text-align: ${justify ? "justify" : "start"};
|
||||||
-webkit-hyphens: ${hyphenate ? "auto" : "manual"};
|
-webkit-hyphens: ${hyphenate ? "auto" : "manual"};
|
||||||
hyphens: ${hyphenate ? "auto" : "manual"};
|
hyphens: ${hyphenate ? "auto" : "manual"};
|
||||||
@@ -94,11 +115,8 @@ document.addEventListener("alpine:init", () => {
|
|||||||
progressText: "",
|
progressText: "",
|
||||||
sliderValue: 0,
|
sliderValue: 0,
|
||||||
settings: null as ReaderSettings | null,
|
settings: null as ReaderSettings | null,
|
||||||
style: {
|
justify: true,
|
||||||
spacing: 1.4,
|
hyphenate: true,
|
||||||
justify: true,
|
|
||||||
hyphenate: true,
|
|
||||||
},
|
|
||||||
tocOpen: false,
|
tocOpen: false,
|
||||||
settingsOpen: false,
|
settingsOpen: false,
|
||||||
bookmarksOpen: false,
|
bookmarksOpen: false,
|
||||||
@@ -122,17 +140,16 @@ document.addEventListener("alpine:init", () => {
|
|||||||
this.readingFont = this.settings.reading_font || "literata";
|
this.readingFont = this.settings.reading_font || "literata";
|
||||||
this.fontSize = this.settings.font_size || 16;
|
this.fontSize = this.settings.font_size || 16;
|
||||||
this.lineHeight = this.settings.line_height || 1.6;
|
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) {
|
if (this.settings.reading_mode) {
|
||||||
this.readingMode = this.settings.reading_mode;
|
this.readingMode = this.settings.reading_mode;
|
||||||
} else {
|
} else {
|
||||||
this.readingMode = this.detectChromeDarkMode() ? "dark" : "light";
|
this.readingMode = this.detectChromeDarkMode() ? "dark" : "light";
|
||||||
}
|
}
|
||||||
if (this.readingMode === "dark") {
|
}
|
||||||
document.body.classList.add("dark");
|
const viewport = document.getElementById("reader-viewport")!;
|
||||||
}
|
viewport.classList.add(`theme-${this.readingTheme}`);
|
||||||
|
if (this.readingMode === "dark") {
|
||||||
|
viewport.classList.add("dark");
|
||||||
}
|
}
|
||||||
this.view = document.getElementById("reader-view") as any;
|
this.view = document.getElementById("reader-view") as any;
|
||||||
const resp = await fetch(config.fileUrl, {
|
const resp = await fetch(config.fileUrl, {
|
||||||
@@ -151,7 +168,7 @@ document.addEventListener("alpine:init", () => {
|
|||||||
this.zoomPercent = this.renderer.zoomPercent;
|
this.zoomPercent = this.renderer.zoomPercent;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.renderer.setStyles?.(getCSS(this.style));
|
this.renderer.setStyles?.(this.buildCSS());
|
||||||
}
|
}
|
||||||
this.view.addEventListener("load", (e: any) => {
|
this.view.addEventListener("load", (e: any) => {
|
||||||
const { doc } = e.detail;
|
const { doc } = e.detail;
|
||||||
@@ -285,13 +302,11 @@ document.addEventListener("alpine:init", () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
applyTheme() {
|
applyTheme() {
|
||||||
document.body.className = document.body.className
|
const viewport = document.getElementById("reader-viewport")!;
|
||||||
.replace(/theme-(?!tokyo|dracula|nord|solarized|monokai|one-dark|material|catppuccin)[\w-]+/g, "")
|
viewport.className = "absolute inset-x-0 top-[52px] bottom-[52px]";
|
||||||
.replace(/\bdark\b/g, "")
|
viewport.classList.add(`theme-${this.readingTheme}`);
|
||||||
.trim();
|
|
||||||
document.body.classList.add(`theme-${this.readingTheme}`);
|
|
||||||
if (this.readingMode === "dark") {
|
if (this.readingMode === "dark") {
|
||||||
document.body.classList.add("dark");
|
viewport.classList.add("dark");
|
||||||
}
|
}
|
||||||
this.applyStyles();
|
this.applyStyles();
|
||||||
saveSettings({
|
saveSettings({
|
||||||
@@ -321,7 +336,16 @@ document.addEventListener("alpine:init", () => {
|
|||||||
},
|
},
|
||||||
applyStyles() {
|
applyStyles() {
|
||||||
if (!this.renderer?.setStyles) return;
|
if (!this.renderer?.setStyles) return;
|
||||||
this.renderer.setStyles(getCSS(this.style));
|
this.renderer.setStyles(this.buildCSS());
|
||||||
|
},
|
||||||
|
buildCSS() {
|
||||||
|
return getCSS({
|
||||||
|
fontFamily: this.readingFont,
|
||||||
|
fontSize: this.fontSize,
|
||||||
|
lineHeight: this.lineHeight,
|
||||||
|
justify: this.justify,
|
||||||
|
hyphenate: this.hyphenate,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
async addBookmark() {
|
async addBookmark() {
|
||||||
const token = getToken();
|
const token = getToken();
|
||||||
|
|||||||
@@ -6,6 +6,20 @@
|
|||||||
CATEGORY 1: CLASSIC READING (6 themes)
|
CATEGORY 1: CLASSIC READING (6 themes)
|
||||||
Time-tested, comfortable for long sessions
|
Time-tested, comfortable for long sessions
|
||||||
========================================== */
|
========================================== */
|
||||||
|
#reader-viewport {
|
||||||
|
background-color: var(--reader-bg);
|
||||||
|
color: var(--reader-text);
|
||||||
|
font-family: var(--reader-font, "Literata", serif);
|
||||||
|
font-size: var(--reader-font-size, 16px);
|
||||||
|
line-height: var(--reader-line-height, 1.6);
|
||||||
|
}
|
||||||
|
#reader-viewport ::selection {
|
||||||
|
background-color: var(--reader-selection);
|
||||||
|
}
|
||||||
|
#reader-viewport a {
|
||||||
|
color: var(--reader-link);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
/* Light - Default theme */
|
/* Light - Default theme */
|
||||||
.theme-light {
|
.theme-light {
|
||||||
--reader-bg: #fafafa;
|
--reader-bg: #fafafa;
|
||||||
@@ -256,4 +270,6 @@
|
|||||||
--reader-link: #268bd2;
|
--reader-link: #268bd2;
|
||||||
--reader-selection: #073642;
|
--reader-selection: #073642;
|
||||||
}
|
}
|
||||||
|
#reader-viewport {
|
||||||
|
background-color: var(--reader-bg);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user