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:
2026-04-19 18:17:42 -04:00
parent 47e0e96ab8
commit 0589157b57
5 changed files with 224 additions and 95 deletions
+62 -70
View File
@@ -15,16 +15,6 @@ templ Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookm
<script src="/static/htmx.min.js"></script>
<script type="module" src="/static/reader.js"></script>
<link href="/static/style.css" rel="stylesheet"/>
<style>
.reader-icon {
display: block;
fill: none;
stroke: currentColor;
stroke-width: 2px;
stroke-linecap: round;
stroke-linejoin: round;
}
</style>
</head>
<body
x-data="readerShell"
@@ -36,19 +26,19 @@ templ Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookm
<div id="reader-panels" class="fixed inset-0 pointer-events-none z-30">
<!-- Left Sidebar (TOC, Settings) -->
<div id="left-sidebar" class="absolute left-0 top-0 bottom-0 pointer-events-auto flex flex-col">
<div id="toc-panel" class="panel-container pointer-events-auto" data-panel="toc">
<div id="toc-panel" x-show="tocOpen" x-transition class="panel-container pointer-events-auto" data-panel="toc">
@ReaderTOCPanel(metadata)
</div>
<div id="settings-panel" class="panel-container pointer-events-auto" data-panel="settings">
<div id="settings-panel" x-show="settingsOpen" x-transition class="panel-container pointer-events-auto" data-panel="settings">
@ReaderSettingsPanel()
</div>
</div>
<!-- Right Sidebar (Navigator, Bookmarks) -->
<div id="right-sidebar" class="absolute right-0 top-0 bottom-0 pointer-events-auto flex flex-col">
<div id="navigator-panel" class="panel-container pointer-events-auto" data-panel="navigator">
<div id="navigator-panel" x-show="navigatorOpen" x-transition class="panel-container pointer-events-auto" data-panel="navigator">
@ReaderNavigatorPanel()
</div>
<div id="bookmarks-panel" class="panel-container pointer-events-auto" data-panel="bookmarks">
<div id="bookmarks-panel" x-show="bookmarksOpen" x-transition class="panel-container pointer-events-auto" data-panel="bookmarks">
@ReaderBookmarksPanel(bookmarks)
</div>
</div>
@@ -69,7 +59,7 @@ templ ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
</a>
<h1 class="text-lg font-semibold">{ metadata.Title }</h1>
<button
data-action="open-settings"
@click="toggleSettings()"
class="p-2 rounded-lg hover:bg-gray-700"
title="Settings"
>
@@ -150,9 +140,9 @@ templ ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
<div class="w-px h-6 bg-gray-600 mx-1"></div>
<!-- Action buttons -->
<div class="flex items-center gap-1">
<button data-action="toggle-toc" class="p-2 rounded-lg hover:bg-gray-700" title="Table of Contents">📖</button>
<button data-action="add-bookmark" class="p-2 rounded-lg hover:bg-gray-700" title="Bookmark">🏷️</button>
<button data-action="add-note" class="p-2 rounded-lg hover:bg-gray-700" title="Note">📝</button>
<button @click="toggleTOC()" class="p-2 rounded-lg hover:bg-gray-700" title="Table of Contents">📖</button>
<button @click="addBookmark()" class="p-2 rounded-lg hover:bg-gray-700" title="Bookmark">🏷️</button>
<button @click="toggleBookmarks()" class="p-2 rounded-lg hover:bg-gray-700" title="Notes">📝</button>
</div>
</div>
</div>
@@ -161,16 +151,15 @@ templ ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
templ ReaderSettingsPanel() {
<div
id="settings-panel"
class="dockable-panel panel-collapsed"
class="dockable-panel"
data-panel="settings"
data-side="left"
x-ref="settingsPanel"
>
<div class="panel-header flex items-center justify-between p-3 cursor-pointer" data-action="toggle-panel">
<div class="panel-header flex items-center justify-between p-3 cursor-pointer" @click="toggleWindowShade($refs.settingsPanel)">
<h3 class="panel-title font-semibold">⚙️ Settings</h3>
<div class="panel-controls flex items-center gap-2">
<button class="panel-lock" data-action="lock-panel" title="Lock position">🔓</button>
<button class="window-shade-toggle" data-action="window-shade"></button>
<button class="window-shade-toggle"></button>
</div>
</div>
<div class="panel-content p-4 overflow-y-auto">
@@ -195,12 +184,12 @@ templ ReaderSettingsPanel() {
</select>
</label>
</div>
<!-- Typography (ebooks only) -->
<div class="mb-6" data-visible-for="ebook">
<!-- Reading Theme (reflowable only) -->
<div class="mb-6" x-show="!isFixedLayout">
<h3 class="font-semibold mb-2">Reading Theme</h3>
<label class="block mb-2">
<select name="reading_theme" class="w-full mt-1 px-3 py-2 rounded border">
<optgroup label="📖 Classic Reading">
<select name="reading_theme" x-model="readingTheme" @change="applyTheme()" class="w-full mt-1 px-3 py-2 rounded border">
<optgroup label="Classic Reading">
<option value="light">Light</option>
<option value="paper">Paper</option>
<option value="sepia">Sepia</option>
@@ -208,89 +197,95 @@ templ ReaderSettingsPanel() {
<option value="warm">Warm</option>
<option value="candlelight">Candlelight</option>
</optgroup>
<optgroup label="🌤️ Sky & Atmosphere">
<optgroup label="Sky and Atmosphere">
<option value="azure">Azure</option>
<option value="sky">Sky</option>
<option value="arctic">Arctic</option>
<option value="frost">Frost</option>
</optgroup>
<optgroup label="🌅 Sunset & Warmth">
<optgroup label="Sunset and Warmth">
<option value="dusk">Dusk</option>
<option value="sunset">Sunset</option>
<option value="twilight">Twilight</option>
</optgroup>
<optgroup label="🌲 Nature & Earth">
<optgroup label="Nature and Earth">
<option value="forest">Forest</option>
<option value="moss">Moss</option>
<option value="slate">Slate</option>
</optgroup>
<optgroup label="High Performance">
<optgroup label="High Performance">
<option value="oled">OLED</option>
<option value="solarized">Solarized</option>
</optgroup>
</select>
<p class="text-xs mt-1" style="color: var(--text-secondary)">18 themes organized by category</p>
</label>
</div>
<div class="mb-6" data-visible-for="ebook">
<!-- Typography (reflowable only) -->
<div class="mb-6" x-show="!isFixedLayout">
<h3 class="font-semibold mb-2">Typography</h3>
<label class="block mb-2">
Reading Font
<select name="reading_font" class="w-full mt-1 px-3 py-2 rounded border">
<option value="literata">Literata (Default - Designed for ebooks)</option>
<option value="crimson">Crimson Text (Screen-optimized)</option>
<option value="source-serif">Source Serif 4 (Adobe quality)</option>
<option value="eb-garamond">EB Garamond (Classic)</option>
<option value="libertinus">Libertinus Serif (Technical)</option>
<option value="noto-serif">Noto Serif (All languages)</option>
<option value="charis-sil">Charis SIL (Multilingual)</option>
<option value="ibm-plex">IBM Plex Serif (Modern)</option>
<select name="reading_font" x-model="readingFont" @change="applyFont()" class="w-full mt-1 px-3 py-2 rounded border">
<option value="literata">Literata (Default)</option>
<option value="crimson">Crimson Text</option>
<option value="source-serif">Source Serif 4</option>
<option value="eb-garamond">EB Garamond</option>
<option value="libertinus">Libertinus Serif</option>
<option value="noto-serif">Noto Serif</option>
<option value="charis-sil">Charis SIL</option>
<option value="ibm-plex">IBM Plex Serif</option>
</select>
<p class="text-xs ml-2" style="color: var(--text-secondary)">8 libre fonts bundled with Bookhoard</p>
</label>
<label class="block mb-2">
Font Size
<input type="range" name="font_size" min="12" max="24" value="16" class="w-full"/>
<span class="text-xs ml-2" style="color: var(--text-secondary)">12-24px</span>
<input type="range" name="font_size" x-model.number="fontSize" @change="applyFont()" min="12" max="24" class="w-full"/>
<span class="text-xs" x-text="fontSize + 'px'" style="color: var(--text-secondary)">16px</span>
</label>
<label class="block mb-2">
Line Height
<input type="range" name="line_height" min="1.0" max="2.5" step="0.1" value="1.6" class="w-full"/>
<span class="text-xs ml-2" style="color: var(--text-secondary)">1.0-2.5</span>
<input type="range" name="line_height" x-model.number="lineHeight" @change="applyFont()" min="1.0" max="2.5" step="0.1" class="w-full"/>
<span class="text-xs" x-text="lineHeight" style="color: var(--text-secondary)">1.6</span>
</label>
</div>
<!-- Navigation -->
<div class="mb-6">
<div class="mb-6" x-show="isFixedLayout">
<h3 class="font-semibold mb-2">Navigation</h3>
<label class="flex items-center mb-2">
<input type="checkbox" name="double_page_spread" class="mr-2"/>
Double Page Spread (Comics/Manga)
Double Page Spread
</label>
</div>
<button data-action="close-settings" class="w-full py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
<button @click="toggleSettings()" class="w-full py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
Done
</button>
</div>
</div>
}
templ ReaderTOCPanel(metadata ReaderMetadata) {
templ ReaderTOCPanel(_ ReaderMetadata) {
<div
id="toc-panel"
class="dockable-panel"
data-panel="toc"
data-side="left"
x-ref="tocPanel"
>
<div class="panel-header flex items-center justify-between p-3 cursor-pointer" data-action="toggle-panel">
<div class="panel-header flex items-center justify-between p-3 cursor-pointer" @click="toggleWindowShade($refs.tocPanel)">
<h3 class="panel-title font-semibold">📖 Table of Contents</h3>
<div class="panel-controls flex items-center gap-2">
<button class="panel-lock" data-action="lock-panel" title="Lock position">🔓</button>
<button class="window-shade-toggle" data-action="window-shade"></button>
<button class="window-shade-toggle"></button>
</div>
</div>
<div class="panel-content p-4 overflow-y-auto">
<nav id="toc-list" class="space-y-2">
<!-- TOC items populated by JavaScript -->
<nav id="toc-list" class="space-y-1">
<template x-for="item in tocItems" :key="item.href">
<a
href="#"
@click.prevent="goToTOCItem(item)"
class="block py-1 px-2 rounded hover:bg-gray-700 text-sm"
:style="'padding-left: ' + ((item.depth || 0) * 1 + 0.5) + 'rem'"
x-text="item.label"
></a>
</template>
</nav>
</div>
</div>
@@ -298,22 +293,19 @@ templ ReaderTOCPanel(metadata ReaderMetadata) {
templ ReaderNavigatorPanel() {
<div
id="navigator-panel"
class="dockable-panel"
data-panel="navigator"
data-side="right"
x-ref="navigatorPanel"
>
<div class="panel-header flex items-center justify-between p-3 cursor-pointer" data-action="toggle-panel">
<div class="panel-header flex items-center justify-between p-3 cursor-pointer" @click="toggleWindowShade($refs.navigatorPanel)">
<h3 class="panel-title font-semibold">🗺️ Navigator</h3>
<div class="panel-controls flex items-center gap-2">
<button class="panel-lock" data-action="lock-panel" title="Lock position">🔓</button>
<button class="window-shade-toggle" data-action="window-shade"></button>
<button class="window-shade-toggle"></button>
</div>
</div>
<div class="panel-content p-2 overflow-hidden">
<div id="navigator-viewport" class="relative w-full h-full">
<!-- Full page preview with draggable viewport box -->
<!-- JavaScript renders current page as scaled thumbnail with draggable viewport -->
</div>
</div>
</div>
@@ -321,16 +313,15 @@ templ ReaderNavigatorPanel() {
templ ReaderBookmarksPanel(bookmarks []Bookmark) {
<div
id="bookmarks-panel"
class="dockable-panel panel-collapsed"
class="dockable-panel"
data-panel="bookmarks"
data-side="right"
x-ref="bookmarksPanel"
>
<div class="panel-header flex items-center justify-between p-3 cursor-pointer" data-action="toggle-panel">
<div class="panel-header flex items-center justify-between p-3 cursor-pointer" @click="toggleWindowShade($refs.bookmarksPanel)">
<h3 class="panel-title font-semibold">🔖 Bookmarks</h3>
<div class="panel-controls flex items-center gap-2">
<button class="panel-lock" data-action="lock-panel" title="Lock position">🔓</button>
<button class="window-shade-toggle" data-action="window-shade"></button>
<button class="window-shade-toggle"></button>
</div>
</div>
<div class="panel-content p-4 overflow-y-auto">
@@ -339,7 +330,8 @@ templ ReaderBookmarksPanel(bookmarks []Bookmark) {
for _, bookmark := range bookmarks {
<a
href="#"
data-bookmark-id={ bookmark.ID }
@click.prevent="goToBookmarkTarget($el.dataset.cfi)"
data-cfi={ bookmark.CfiPosition }
class="block py-2 hover:bg-gray-700 rounded px-2"
>
<span class="font-medium">{ bookmark.Title }</span>
@@ -352,7 +344,7 @@ templ ReaderBookmarksPanel(bookmarks []Bookmark) {
} else {
<p class="text-sm" style="color: var(--text-secondary)">No bookmarks yet</p>
}
<button data-action="add-bookmark" class="w-full py-2 mt-4 bg-blue-600 text-white rounded hover:bg-blue-700">
<button @click="addBookmark()" class="w-full py-2 mt-4 bg-blue-600 text-white rounded hover:bg-blue-700">
+ Add Bookmark
</button>
</div>