fix(reader): authenticate book file fetch and inject reading theme colors
Books were failing to load because foliate-js fetches the file URL without auth headers, getting rejected by JWT middleware. Also, reading themes were not being applied because getCSS() didn't inject background/text colors into the book iframe. - Fetch book file with Bearer token, pass as File (not URL) to view.open() so foliate-js can detect format via filename extension - Add reading theme class to body so foliate-themes.css activates the correct --reader-bg/--reader-text CSS variables - Update getCSS() to read theme colors from outer page and embed them in the iframe CSS string (background-color, color, link color, selection color) - Fix Alpine.start() deadlock: move call outside the alpine:init listener so Alpine actually initializes - Remove unused tocItem from relocate handler destructuring - Replace apiGet/apiPut with direct fetch in settings-manager to fix /api prefix mismatch (reader routes are at /readers/*, not /api/readers/*)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import "foliate-js/view.js";
|
||||
import { Alpine } from "../alpine";
|
||||
import { loadSettings, saveSettings } from "./settings-manager";
|
||||
import { loadSettings } from "./settings-manager";
|
||||
import { getToken } from "../storage";
|
||||
const getCSS = ({
|
||||
spacing,
|
||||
justify,
|
||||
@@ -9,14 +10,29 @@ const getCSS = ({
|
||||
spacing: number;
|
||||
justify: boolean;
|
||||
hyphenate: boolean;
|
||||
}) => `
|
||||
}) => {
|
||||
const style = getComputedStyle(document.body);
|
||||
const bg = style.getPropertyValue("--reader-bg").trim() || "#fafafa";
|
||||
const text = style.getPropertyValue("--reader-text").trim() || "#1a1a1a";
|
||||
const link = style.getPropertyValue("--reader-link").trim() || "#0066cc";
|
||||
const selection =
|
||||
style.getPropertyValue("--reader-selection").trim() || "#b3d9ff";
|
||||
return `
|
||||
@namespace epub "http://www.idpf.org/2007/ops";
|
||||
html {
|
||||
color-scheme: light dark;
|
||||
background-color: ${bg};
|
||||
color: ${text};
|
||||
}
|
||||
::selection {
|
||||
background-color: ${selection};
|
||||
}
|
||||
a:link {
|
||||
color: ${link};
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
a:link {
|
||||
color: lightblue;
|
||||
color: ${link};
|
||||
}
|
||||
}
|
||||
p, li, blockquote, dd {
|
||||
@@ -44,6 +60,7 @@ const getCSS = ({
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
};
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data("readerShell", () => ({
|
||||
view: null as any,
|
||||
@@ -70,8 +87,17 @@ document.addEventListener("alpine:init", () => {
|
||||
mangaType: string;
|
||||
}) {
|
||||
this.settings = await loadSettings();
|
||||
if (this.settings?.reading_theme) {
|
||||
document.body.classList.add(`theme-${this.settings.reading_theme}`);
|
||||
}
|
||||
this.view = document.getElementById("reader-view") as any;
|
||||
await this.view.open(config.fileUrl);
|
||||
const resp = await fetch(config.fileUrl, {
|
||||
headers: { Authorization: `Bearer ${getToken()}` },
|
||||
});
|
||||
const blob = await resp.blob();
|
||||
const fileName = new URL(config.fileUrl, window.location.origin).pathname;
|
||||
const file = new File([blob], fileName, { type: blob.type });
|
||||
await this.view.open(file);
|
||||
this.renderer = this.view.renderer;
|
||||
this.book = this.view.book;
|
||||
this.isFixedLayout = this.view.isFixedLayout;
|
||||
@@ -90,7 +116,7 @@ document.addEventListener("alpine:init", () => {
|
||||
);
|
||||
});
|
||||
this.view.addEventListener("relocate", (e: any) => {
|
||||
const { fraction, location, tocItem, pageItem } = e.detail;
|
||||
const { fraction, location, pageItem } = e.detail;
|
||||
const percent = new Intl.NumberFormat("en", {
|
||||
style: "percent",
|
||||
}).format(fraction);
|
||||
@@ -188,5 +214,6 @@ document.addEventListener("alpine:init", () => {
|
||||
}
|
||||
},
|
||||
}));
|
||||
Alpine.start();
|
||||
});
|
||||
|
||||
Alpine.start();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { apiGet, apiPut } from "../api";
|
||||
import { getToken } from "../storage";
|
||||
const LOCALSTORAGE_KEY = "reader_settings_local";
|
||||
export async function loadSettings(): Promise<ReaderSettings> {
|
||||
@@ -8,7 +7,12 @@ export async function loadSettings(): Promise<ReaderSettings> {
|
||||
return local ? JSON.parse(local) : getDefaultSettings();
|
||||
}
|
||||
try {
|
||||
const response = await apiGet("/readers/settings");
|
||||
const response = await fetch("/readers/settings", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const settings = await response.json();
|
||||
localStorage.setItem(LOCALSTORAGE_KEY, JSON.stringify(settings));
|
||||
return settings;
|
||||
@@ -28,7 +32,14 @@ export async function saveSettings(
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await apiPut("/readers/settings", settings);
|
||||
await fetch("/readers/settings", {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(settings),
|
||||
});
|
||||
localStorage.setItem(LOCALSTORAGE_KEY, JSON.stringify(updated));
|
||||
} catch (error) {
|
||||
localStorage.setItem(LOCALSTORAGE_KEY, JSON.stringify(updated));
|
||||
@@ -41,7 +52,14 @@ export async function syncSettings(): Promise<void> {
|
||||
const token = getToken();
|
||||
if (token) {
|
||||
try {
|
||||
await apiPut("/readers/settings", settings);
|
||||
await fetch("/readers/settings", {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(settings),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to sync settings:", error);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user