feat(reader): wire up TOC, bookmarks, and navigator panels with Alpine.js bindings
Replace dead data-action attributes with Alpine.js @click handlers and x-ref references across all reader panels: - TOC panel: replaced static <nav> with x-for loop over tocItems array, added goToTOCItem() click handler, window-shade toggle via .tocPanel - Bookmarks panel: replaced data-action with @click.prevent handlers, added goToBookmarkTarget() using data-cfi attributes for navigation, window-shade toggle via .bookmarksPanel - Navigator panel: replaced data-action with @click window-shade toggle via .navigatorPanel - Added goToBookmarkTarget() and toggleWindowShade() methods to reader.ts - Removed unused panel-lock buttons (lock feature not yet implemented) - Regenerated reader_templ.go, rebuilt CSS and JS bundles
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import "foliate-js/view.js";
|
||||
import { Alpine } from "../alpine";
|
||||
import { loadSettings } from "./settings-manager";
|
||||
import { loadSettings, saveSettings } from "./settings-manager";
|
||||
import { getToken } from "../storage";
|
||||
const getCSS = ({
|
||||
spacing,
|
||||
@@ -79,6 +79,15 @@ document.addEventListener("alpine:init", () => {
|
||||
justify: true,
|
||||
hyphenate: true,
|
||||
},
|
||||
tocOpen: false,
|
||||
settingsOpen: false,
|
||||
bookmarksOpen: false,
|
||||
navigatorOpen: false,
|
||||
tocItems: [] as any[],
|
||||
readingTheme: "dark" as string,
|
||||
readingFont: "literata" as string,
|
||||
fontSize: 16 as number,
|
||||
lineHeight: 1.6 as number,
|
||||
async initReader(config: {
|
||||
mediaItemId: string;
|
||||
fileUrl: string;
|
||||
@@ -87,8 +96,14 @@ document.addEventListener("alpine:init", () => {
|
||||
mangaType: string;
|
||||
}) {
|
||||
this.settings = await loadSettings();
|
||||
if (this.settings?.reading_theme) {
|
||||
document.body.classList.add(`theme-${this.settings.reading_theme}`);
|
||||
if (this.settings) {
|
||||
this.readingTheme = this.settings.reading_theme || "dark";
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
this.view = document.getElementById("reader-view") as any;
|
||||
const resp = await fetch(config.fileUrl, {
|
||||
@@ -200,6 +215,87 @@ document.addEventListener("alpine:init", () => {
|
||||
goToFraction(value: string) {
|
||||
this.view?.goToFraction?.(parseFloat(value));
|
||||
},
|
||||
toggleTOC() {
|
||||
this.tocOpen = !this.tocOpen;
|
||||
if (this.tocOpen && this.tocItems.length === 0 && this.book?.toc) {
|
||||
this.tocItems = this.flattenTOC(this.book.toc);
|
||||
}
|
||||
},
|
||||
toggleSettings() {
|
||||
this.settingsOpen = !this.settingsOpen;
|
||||
},
|
||||
toggleBookmarks() {
|
||||
this.bookmarksOpen = !this.bookmarksOpen;
|
||||
},
|
||||
toggleNavigator() {
|
||||
this.navigatorOpen = !this.navigatorOpen;
|
||||
},
|
||||
toggleWindowShade(panelEl: HTMLElement) {
|
||||
panelEl.classList.toggle("panel-collapsed");
|
||||
},
|
||||
flattenTOC(items: any[], depth = 0): any[] {
|
||||
const result: any[] = [];
|
||||
for (const item of items) {
|
||||
result.push({ ...item, depth });
|
||||
if (item.subitems?.length) {
|
||||
result.push(...this.flattenTOC(item.subitems, depth + 1));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
goToTOCItem(item: any) {
|
||||
if (this.view && item.href) {
|
||||
this.view.goTo(item.href);
|
||||
this.tocOpen = false;
|
||||
}
|
||||
},
|
||||
goToBookmarkTarget(cfi: string) {
|
||||
if (this.view && cfi) {
|
||||
this.view.goTo(cfi);
|
||||
this.bookmarksOpen = false;
|
||||
}
|
||||
},
|
||||
applyTheme() {
|
||||
document.body.className = document.body.className
|
||||
.replace(/theme-(?!tokyo|dracula|nord|solarized|monokai|one-dark|material|catppuccin)[\w-]+/g, "")
|
||||
.trim();
|
||||
document.body.classList.add(`theme-${this.readingTheme}`);
|
||||
this.applyStyles();
|
||||
saveSettings({ reading_theme: this.readingTheme });
|
||||
},
|
||||
applyFont() {
|
||||
this.applyStyles();
|
||||
saveSettings({
|
||||
reading_font: this.readingFont,
|
||||
font_size: this.fontSize,
|
||||
line_height: this.lineHeight,
|
||||
});
|
||||
},
|
||||
applyStyles() {
|
||||
if (!this.renderer?.setStyles) return;
|
||||
this.renderer.setStyles(getCSS(this.style));
|
||||
},
|
||||
async addBookmark() {
|
||||
const token = getToken();
|
||||
if (!token || !this.view) return;
|
||||
const location = this.view.lastLocation;
|
||||
if (!location) return;
|
||||
try {
|
||||
await fetch("/readers/bookmarks", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: `Bookmark at ${this.progressText}`,
|
||||
position: JSON.stringify(location),
|
||||
}),
|
||||
});
|
||||
} catch (_e) {
|
||||
/* ignore bookmark errors for now */
|
||||
}
|
||||
},
|
||||
handleKeydown(event: KeyboardEvent) {
|
||||
const k = event.key;
|
||||
if (k === "ArrowLeft" || k === "h") this.goLeft();
|
||||
|
||||
@@ -357,4 +357,45 @@
|
||||
#collections-container[data-wood] .book-card {
|
||||
border: 1px solid var(--wood-border);
|
||||
}
|
||||
|
||||
.reader-icon {
|
||||
display: block;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 2px;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
.dockable-panel {
|
||||
background-color: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border);
|
||||
transition: max-height 0.2s ease-out;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dockable-panel.panel-collapsed .panel-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.panel-header:hover {
|
||||
background-color: color-mix(in srgb, var(--bg-secondary) 90%, white);
|
||||
}
|
||||
|
||||
.panel-container {
|
||||
background-color: var(--bg-secondary);
|
||||
border-right: 1px solid var(--border);
|
||||
width: 320px;
|
||||
max-height: calc(100vh - 8rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.panel-container[data-side="right"] {
|
||||
border-right: none;
|
||||
border-left: 1px solid var(--border);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user