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:
+62
-70
@@ -15,16 +15,6 @@ templ Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookm
|
|||||||
<script src="/static/htmx.min.js"></script>
|
<script src="/static/htmx.min.js"></script>
|
||||||
<script type="module" src="/static/reader.js"></script>
|
<script type="module" src="/static/reader.js"></script>
|
||||||
<link href="/static/style.css" rel="stylesheet"/>
|
<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>
|
</head>
|
||||||
<body
|
<body
|
||||||
x-data="readerShell"
|
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">
|
<div id="reader-panels" class="fixed inset-0 pointer-events-none z-30">
|
||||||
<!-- Left Sidebar (TOC, Settings) -->
|
<!-- Left Sidebar (TOC, Settings) -->
|
||||||
<div id="left-sidebar" class="absolute left-0 top-0 bottom-0 pointer-events-auto flex flex-col">
|
<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)
|
@ReaderTOCPanel(metadata)
|
||||||
</div>
|
</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()
|
@ReaderSettingsPanel()
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Right Sidebar (Navigator, Bookmarks) -->
|
<!-- Right Sidebar (Navigator, Bookmarks) -->
|
||||||
<div id="right-sidebar" class="absolute right-0 top-0 bottom-0 pointer-events-auto flex flex-col">
|
<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()
|
@ReaderNavigatorPanel()
|
||||||
</div>
|
</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)
|
@ReaderBookmarksPanel(bookmarks)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,7 +59,7 @@ templ ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
|
|||||||
</a>
|
</a>
|
||||||
<h1 class="text-lg font-semibold">{ metadata.Title }</h1>
|
<h1 class="text-lg font-semibold">{ metadata.Title }</h1>
|
||||||
<button
|
<button
|
||||||
data-action="open-settings"
|
@click="toggleSettings()"
|
||||||
class="p-2 rounded-lg hover:bg-gray-700"
|
class="p-2 rounded-lg hover:bg-gray-700"
|
||||||
title="Settings"
|
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>
|
<div class="w-px h-6 bg-gray-600 mx-1"></div>
|
||||||
<!-- Action buttons -->
|
<!-- Action buttons -->
|
||||||
<div class="flex items-center gap-1">
|
<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 @click="toggleTOC()" 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 @click="addBookmark()" 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="toggleBookmarks()" class="p-2 rounded-lg hover:bg-gray-700" title="Notes">📝</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -161,16 +151,15 @@ templ ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
|
|||||||
|
|
||||||
templ ReaderSettingsPanel() {
|
templ ReaderSettingsPanel() {
|
||||||
<div
|
<div
|
||||||
id="settings-panel"
|
class="dockable-panel"
|
||||||
class="dockable-panel panel-collapsed"
|
|
||||||
data-panel="settings"
|
data-panel="settings"
|
||||||
data-side="left"
|
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>
|
<h3 class="panel-title font-semibold">⚙️ Settings</h3>
|
||||||
<div class="panel-controls flex items-center gap-2">
|
<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">─</button>
|
||||||
<button class="window-shade-toggle" data-action="window-shade">─</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-content p-4 overflow-y-auto">
|
<div class="panel-content p-4 overflow-y-auto">
|
||||||
@@ -195,12 +184,12 @@ templ ReaderSettingsPanel() {
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<!-- Typography (ebooks only) -->
|
<!-- Reading Theme (reflowable only) -->
|
||||||
<div class="mb-6" data-visible-for="ebook">
|
<div class="mb-6" x-show="!isFixedLayout">
|
||||||
<h3 class="font-semibold mb-2">Reading Theme</h3>
|
<h3 class="font-semibold mb-2">Reading Theme</h3>
|
||||||
<label class="block mb-2">
|
<label class="block mb-2">
|
||||||
<select name="reading_theme" class="w-full mt-1 px-3 py-2 rounded border">
|
<select name="reading_theme" x-model="readingTheme" @change="applyTheme()" class="w-full mt-1 px-3 py-2 rounded border">
|
||||||
<optgroup label="📖 Classic Reading">
|
<optgroup label="Classic Reading">
|
||||||
<option value="light">Light</option>
|
<option value="light">Light</option>
|
||||||
<option value="paper">Paper</option>
|
<option value="paper">Paper</option>
|
||||||
<option value="sepia">Sepia</option>
|
<option value="sepia">Sepia</option>
|
||||||
@@ -208,89 +197,95 @@ templ ReaderSettingsPanel() {
|
|||||||
<option value="warm">Warm</option>
|
<option value="warm">Warm</option>
|
||||||
<option value="candlelight">Candlelight</option>
|
<option value="candlelight">Candlelight</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
<optgroup label="🌤️ Sky & Atmosphere">
|
<optgroup label="Sky and Atmosphere">
|
||||||
<option value="azure">Azure</option>
|
<option value="azure">Azure</option>
|
||||||
<option value="sky">Sky</option>
|
<option value="sky">Sky</option>
|
||||||
<option value="arctic">Arctic</option>
|
<option value="arctic">Arctic</option>
|
||||||
<option value="frost">Frost</option>
|
<option value="frost">Frost</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
<optgroup label="🌅 Sunset & Warmth">
|
<optgroup label="Sunset and Warmth">
|
||||||
<option value="dusk">Dusk</option>
|
<option value="dusk">Dusk</option>
|
||||||
<option value="sunset">Sunset</option>
|
<option value="sunset">Sunset</option>
|
||||||
<option value="twilight">Twilight</option>
|
<option value="twilight">Twilight</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
<optgroup label="🌲 Nature & Earth">
|
<optgroup label="Nature and Earth">
|
||||||
<option value="forest">Forest</option>
|
<option value="forest">Forest</option>
|
||||||
<option value="moss">Moss</option>
|
<option value="moss">Moss</option>
|
||||||
<option value="slate">Slate</option>
|
<option value="slate">Slate</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
<optgroup label="⚡ High Performance">
|
<optgroup label="High Performance">
|
||||||
<option value="oled">OLED</option>
|
<option value="oled">OLED</option>
|
||||||
<option value="solarized">Solarized</option>
|
<option value="solarized">Solarized</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
<p class="text-xs mt-1" style="color: var(--text-secondary)">18 themes organized by category</p>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</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>
|
<h3 class="font-semibold mb-2">Typography</h3>
|
||||||
<label class="block mb-2">
|
<label class="block mb-2">
|
||||||
Reading Font
|
Reading Font
|
||||||
<select name="reading_font" class="w-full mt-1 px-3 py-2 rounded border">
|
<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 - Designed for ebooks)</option>
|
<option value="literata">Literata (Default)</option>
|
||||||
<option value="crimson">Crimson Text (Screen-optimized)</option>
|
<option value="crimson">Crimson Text</option>
|
||||||
<option value="source-serif">Source Serif 4 (Adobe quality)</option>
|
<option value="source-serif">Source Serif 4</option>
|
||||||
<option value="eb-garamond">EB Garamond (Classic)</option>
|
<option value="eb-garamond">EB Garamond</option>
|
||||||
<option value="libertinus">Libertinus Serif (Technical)</option>
|
<option value="libertinus">Libertinus Serif</option>
|
||||||
<option value="noto-serif">Noto Serif (All languages)</option>
|
<option value="noto-serif">Noto Serif</option>
|
||||||
<option value="charis-sil">Charis SIL (Multilingual)</option>
|
<option value="charis-sil">Charis SIL</option>
|
||||||
<option value="ibm-plex">IBM Plex Serif (Modern)</option>
|
<option value="ibm-plex">IBM Plex Serif</option>
|
||||||
</select>
|
</select>
|
||||||
<p class="text-xs ml-2" style="color: var(--text-secondary)">8 libre fonts bundled with Bookhoard</p>
|
|
||||||
</label>
|
</label>
|
||||||
<label class="block mb-2">
|
<label class="block mb-2">
|
||||||
Font Size
|
Font Size
|
||||||
<input type="range" name="font_size" min="12" max="24" value="16" class="w-full"/>
|
<input type="range" name="font_size" x-model.number="fontSize" @change="applyFont()" min="12" max="24" class="w-full"/>
|
||||||
<span class="text-xs ml-2" style="color: var(--text-secondary)">12-24px</span>
|
<span class="text-xs" x-text="fontSize + 'px'" style="color: var(--text-secondary)">16px</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="block mb-2">
|
<label class="block mb-2">
|
||||||
Line Height
|
Line Height
|
||||||
<input type="range" name="line_height" min="1.0" max="2.5" step="0.1" value="1.6" class="w-full"/>
|
<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 ml-2" style="color: var(--text-secondary)">1.0-2.5</span>
|
<span class="text-xs" x-text="lineHeight" style="color: var(--text-secondary)">1.6</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<!-- Navigation -->
|
<!-- Navigation -->
|
||||||
<div class="mb-6">
|
<div class="mb-6" x-show="isFixedLayout">
|
||||||
<h3 class="font-semibold mb-2">Navigation</h3>
|
<h3 class="font-semibold mb-2">Navigation</h3>
|
||||||
<label class="flex items-center mb-2">
|
<label class="flex items-center mb-2">
|
||||||
<input type="checkbox" name="double_page_spread" class="mr-2"/>
|
<input type="checkbox" name="double_page_spread" class="mr-2"/>
|
||||||
Double Page Spread (Comics/Manga)
|
Double Page Spread
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</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
|
Done
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
templ ReaderTOCPanel(metadata ReaderMetadata) {
|
templ ReaderTOCPanel(_ ReaderMetadata) {
|
||||||
<div
|
<div
|
||||||
id="toc-panel"
|
|
||||||
class="dockable-panel"
|
class="dockable-panel"
|
||||||
data-panel="toc"
|
data-panel="toc"
|
||||||
data-side="left"
|
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>
|
<h3 class="panel-title font-semibold">📖 Table of Contents</h3>
|
||||||
<div class="panel-controls flex items-center gap-2">
|
<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">─</button>
|
||||||
<button class="window-shade-toggle" data-action="window-shade">─</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-content p-4 overflow-y-auto">
|
<div class="panel-content p-4 overflow-y-auto">
|
||||||
<nav id="toc-list" class="space-y-2">
|
<nav id="toc-list" class="space-y-1">
|
||||||
<!-- TOC items populated by JavaScript -->
|
<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>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -298,22 +293,19 @@ templ ReaderTOCPanel(metadata ReaderMetadata) {
|
|||||||
|
|
||||||
templ ReaderNavigatorPanel() {
|
templ ReaderNavigatorPanel() {
|
||||||
<div
|
<div
|
||||||
id="navigator-panel"
|
|
||||||
class="dockable-panel"
|
class="dockable-panel"
|
||||||
data-panel="navigator"
|
data-panel="navigator"
|
||||||
data-side="right"
|
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>
|
<h3 class="panel-title font-semibold">🗺️ Navigator</h3>
|
||||||
<div class="panel-controls flex items-center gap-2">
|
<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">─</button>
|
||||||
<button class="window-shade-toggle" data-action="window-shade">─</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-content p-2 overflow-hidden">
|
<div class="panel-content p-2 overflow-hidden">
|
||||||
<div id="navigator-viewport" class="relative w-full h-full">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -321,16 +313,15 @@ templ ReaderNavigatorPanel() {
|
|||||||
|
|
||||||
templ ReaderBookmarksPanel(bookmarks []Bookmark) {
|
templ ReaderBookmarksPanel(bookmarks []Bookmark) {
|
||||||
<div
|
<div
|
||||||
id="bookmarks-panel"
|
class="dockable-panel"
|
||||||
class="dockable-panel panel-collapsed"
|
|
||||||
data-panel="bookmarks"
|
data-panel="bookmarks"
|
||||||
data-side="right"
|
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>
|
<h3 class="panel-title font-semibold">🔖 Bookmarks</h3>
|
||||||
<div class="panel-controls flex items-center gap-2">
|
<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">─</button>
|
||||||
<button class="window-shade-toggle" data-action="window-shade">─</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-content p-4 overflow-y-auto">
|
<div class="panel-content p-4 overflow-y-auto">
|
||||||
@@ -339,7 +330,8 @@ templ ReaderBookmarksPanel(bookmarks []Bookmark) {
|
|||||||
for _, bookmark := range bookmarks {
|
for _, bookmark := range bookmarks {
|
||||||
<a
|
<a
|
||||||
href="#"
|
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"
|
class="block py-2 hover:bg-gray-700 rounded px-2"
|
||||||
>
|
>
|
||||||
<span class="font-medium">{ bookmark.Title }</span>
|
<span class="font-medium">{ bookmark.Title }</span>
|
||||||
@@ -352,7 +344,7 @@ templ ReaderBookmarksPanel(bookmarks []Bookmark) {
|
|||||||
} else {
|
} else {
|
||||||
<p class="text-sm" style="color: var(--text-secondary)">No bookmarks yet</p>
|
<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
|
+ Add Bookmark
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+21
-21
@@ -44,14 +44,14 @@ 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, 2, " - Bookhoard Reader</title><link rel=\"manifest\" href=\"/static/manifest.json\"><link href=\"/static/reader-fonts.css\" rel=\"stylesheet\"><link href=\"/static/foliate-themes.css\" rel=\"stylesheet\"><script src=\"/static/htmx.min.js\"></script><script type=\"module\" src=\"/static/reader.js\"></script><link href=\"/static/style.css\" rel=\"stylesheet\"><style>\n\t\t\t\t.reader-icon {\n\t\t\t\t\tdisplay: block;\n\t\t\t\t\tfill: none;\n\t\t\t\t\tstroke: currentColor;\n\t\t\t\t\tstroke-width: 2px;\n\t\t\t\t\tstroke-linecap: round;\n\t\t\t\t\tstroke-linejoin: round;\n\t\t\t\t}\n\t\t\t</style></head><body x-data=\"readerShell\" x-init=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " - Bookhoard Reader</title><link rel=\"manifest\" href=\"/static/manifest.json\"><link href=\"/static/reader-fonts.css\" rel=\"stylesheet\"><link href=\"/static/foliate-themes.css\" rel=\"stylesheet\"><script src=\"/static/htmx.min.js\"></script><script type=\"module\" src=\"/static/reader.js\"></script><link href=\"/static/style.css\" rel=\"stylesheet\"></head><body x-data=\"readerShell\" x-init=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var3 string
|
var templ_7745c5c3_Var3 string
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(`initReader({mediaItemId:'%s',fileUrl:'%s',formatGroup:'%s',readingDirection:'%s',mangaType:'%s'})`, metadata.MediaItemID, metadata.FileURL, metadata.FormatGroup, metadata.ReadingDirection, metadata.MangaType))
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(`initReader({mediaItemId:'%s',fileUrl:'%s',formatGroup:'%s',readingDirection:'%s',mangaType:'%s'})`, metadata.MediaItemID, metadata.FileURL, metadata.FormatGroup, metadata.ReadingDirection, metadata.MangaType))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 31, Col: 233}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 21, Col: 233}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -65,7 +65,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, 4, "<!-- Dockable Panels Container --><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\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<!-- Dockable Panels Container --><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\" x-show=\"tocOpen\" x-transition class=\"panel-container pointer-events-auto\" data-panel=\"toc\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,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, 5, "</div><div id=\"settings-panel\" class=\"panel-container pointer-events-auto\" data-panel=\"settings\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div><div id=\"settings-panel\" x-show=\"settingsOpen\" x-transition class=\"panel-container pointer-events-auto\" data-panel=\"settings\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,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, 6, "</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\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</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\" x-show=\"navigatorOpen\" x-transition class=\"panel-container pointer-events-auto\" data-panel=\"navigator\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,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, 7, "</div><div id=\"bookmarks-panel\" class=\"panel-container pointer-events-auto\" data-panel=\"bookmarks\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</div><div id=\"bookmarks-panel\" x-show=\"bookmarksOpen\" x-transition class=\"panel-container pointer-events-auto\" data-panel=\"bookmarks\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -141,26 +141,26 @@ 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: 70, Col: 54}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 60, 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 {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</h1><button data-action=\"open-settings\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Settings\">⚙️</button></div></div><!-- Bottom bar --><div class=\"fixed bottom-0 left-0 right-0 bg-opacity-95 backdrop-blur border-t z-40\" style=\"background-color: var(--bg-primary);\"><div class=\"flex items-center px-2 py-2 gap-1\"><!-- Left navigation --><button @click=\"goLeft()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Go Left\" aria-label=\"Go left\"><svg class=\"reader-icon\" width=\"24\" height=\"24\" aria-hidden=\"true\"><path d=\"M 15 6 L 9 12 L 15 18\"></path></svg></button><!-- Progress slider --><input id=\"progress-slider\" type=\"range\" min=\"0\" max=\"1\" step=\"any\" list=\"tick-marks\" @input=\"goToFraction($event.target.value)\" class=\"grow\"> <datalist id=\"tick-marks\"></datalist><!-- Right navigation --><button @click=\"goRight()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Go Right\" aria-label=\"Go right\"><svg class=\"reader-icon\" width=\"24\" height=\"24\" aria-hidden=\"true\"><path d=\"M 9 6 L 15 12 L 9 18\"></path></svg></button><!-- Separator --><div class=\"w-px h-6 bg-gray-600 mx-1\"></div><!-- Zoom controls (fixed-layout only) --><template x-if=\"isFixedLayout\"><div class=\"flex items-center gap-1\"><!-- Zoom out --><button @click=\"zoomOut()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Zoom Out\" aria-label=\"Zoom out\"><svg class=\"reader-icon\" width=\"20\" height=\"20\" aria-hidden=\"true\"><path d=\"M 5 10 L 15 10\"></path></svg></button><!-- Zoom percentage --><button @click=\"resetZoom()\" class=\"text-xs px-1 rounded-lg hover:bg-gray-700 min-w-[3rem]\" title=\"Reset Zoom\" aria-label=\"Reset zoom\"><span x-text=\"zoomPercent + '%'\">100%</span></button><!-- Zoom in --><button @click=\"zoomIn()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Zoom In\" aria-label=\"Zoom in\"><svg class=\"reader-icon\" width=\"20\" height=\"20\" aria-hidden=\"true\"><path d=\"M 10 5 L 10 15 M 5 10 L 15 10\"></path></svg></button><!-- Magnifier --><button @click=\"toggleMagnifier()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Magnifier\" aria-label=\"Toggle magnifier\"><svg class=\"reader-icon\" width=\"20\" height=\"20\" aria-hidden=\"true\"><circle cx=\"9\" cy=\"9\" r=\"5\"></circle> <path d=\"M 13 13 L 18 18\"></path></svg></button><!-- Pan/Select mode (PDF only) --><button x-show=\"isPDF\" @click=\"toggleInteractionMode()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Pan/Select Mode\" aria-label=\"Toggle pan/select mode\"><svg class=\"reader-icon\" width=\"20\" height=\"20\" aria-hidden=\"true\"><path d=\"M 5 3 v 12 M 5 15 l -2 2 M 5 15 l 2 2 M 5 3 l 3 3\"></path></svg></button></div></template><!-- Separator --><div class=\"w-px h-6 bg-gray-600 mx-1\"></div><!-- Progress display --><div id=\"progress-display\" x-text=\"progressText\" data-progress-mode=\"pages\" class=\"text-sm min-w-[4rem] text-center\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</h1><button @click=\"toggleSettings()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Settings\">⚙️</button></div></div><!-- Bottom bar --><div class=\"fixed bottom-0 left-0 right-0 bg-opacity-95 backdrop-blur border-t z-40\" style=\"background-color: var(--bg-primary);\"><div class=\"flex items-center px-2 py-2 gap-1\"><!-- Left navigation --><button @click=\"goLeft()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Go Left\" aria-label=\"Go left\"><svg class=\"reader-icon\" width=\"24\" height=\"24\" aria-hidden=\"true\"><path d=\"M 15 6 L 9 12 L 15 18\"></path></svg></button><!-- Progress slider --><input id=\"progress-slider\" type=\"range\" min=\"0\" max=\"1\" step=\"any\" list=\"tick-marks\" @input=\"goToFraction($event.target.value)\" class=\"grow\"> <datalist id=\"tick-marks\"></datalist><!-- Right navigation --><button @click=\"goRight()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Go Right\" aria-label=\"Go right\"><svg class=\"reader-icon\" width=\"24\" height=\"24\" aria-hidden=\"true\"><path d=\"M 9 6 L 15 12 L 9 18\"></path></svg></button><!-- Separator --><div class=\"w-px h-6 bg-gray-600 mx-1\"></div><!-- Zoom controls (fixed-layout only) --><template x-if=\"isFixedLayout\"><div class=\"flex items-center gap-1\"><!-- Zoom out --><button @click=\"zoomOut()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Zoom Out\" aria-label=\"Zoom out\"><svg class=\"reader-icon\" width=\"20\" height=\"20\" aria-hidden=\"true\"><path d=\"M 5 10 L 15 10\"></path></svg></button><!-- Zoom percentage --><button @click=\"resetZoom()\" class=\"text-xs px-1 rounded-lg hover:bg-gray-700 min-w-[3rem]\" title=\"Reset Zoom\" aria-label=\"Reset zoom\"><span x-text=\"zoomPercent + '%'\">100%</span></button><!-- Zoom in --><button @click=\"zoomIn()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Zoom In\" aria-label=\"Zoom in\"><svg class=\"reader-icon\" width=\"20\" height=\"20\" aria-hidden=\"true\"><path d=\"M 10 5 L 10 15 M 5 10 L 15 10\"></path></svg></button><!-- Magnifier --><button @click=\"toggleMagnifier()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Magnifier\" aria-label=\"Toggle magnifier\"><svg class=\"reader-icon\" width=\"20\" height=\"20\" aria-hidden=\"true\"><circle cx=\"9\" cy=\"9\" r=\"5\"></circle> <path d=\"M 13 13 L 18 18\"></path></svg></button><!-- Pan/Select mode (PDF only) --><button x-show=\"isPDF\" @click=\"toggleInteractionMode()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Pan/Select Mode\" aria-label=\"Toggle pan/select mode\"><svg class=\"reader-icon\" width=\"20\" height=\"20\" aria-hidden=\"true\"><path d=\"M 5 3 v 12 M 5 15 l -2 2 M 5 15 l 2 2 M 5 3 l 3 3\"></path></svg></button></div></template><!-- Separator --><div class=\"w-px h-6 bg-gray-600 mx-1\"></div><!-- Progress display --><div id=\"progress-display\" x-text=\"progressText\" data-progress-mode=\"pages\" class=\"text-sm min-w-[4rem] text-center\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
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: 147, Col: 70}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 137, 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 {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div><!-- Separator --><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></div></div></div></div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div><!-- Separator --><div class=\"w-px h-6 bg-gray-600 mx-1\"></div><!-- Action buttons --><div class=\"flex items-center gap-1\"><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></div>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -189,7 +189,7 @@ func ReaderSettingsPanel() templ.Component {
|
|||||||
templ_7745c5c3_Var7 = templ.NopComponent
|
templ_7745c5c3_Var7 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div id=\"settings-panel\" class=\"dockable-panel panel-collapsed\" data-panel=\"settings\" data-side=\"left\"><div class=\"panel-header flex items-center justify-between p-3 cursor-pointer\" data-action=\"toggle-panel\"><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></div></div><div class=\"panel-content p-4 overflow-y-auto\"><!-- Display settings --><div class=\"mb-6\"><h3 class=\"font-semibold mb-2\">Display</h3><label class=\"block mb-2\">Chrome Behavior <select name=\"chrome_behavior\" class=\"w-full mt-1 px-3 py-2 rounded border\"><option value=\"auto-hide\">Auto Hide</option> <option value=\"always-visible\">Always Visible</option> <option value=\"hide-on-scroll\">Hide on Scroll</option></select></label> <label class=\"block mb-2\">Progress Mode <select name=\"progress_mode\" class=\"w-full mt-1 px-3 py-2 rounded border\"><option value=\"pages\">Pages</option> <option value=\"chapter\">Chapter</option> <option value=\"percentage\">Percentage</option> <option value=\"time-left\">Time Left</option></select></label></div><!-- Typography (ebooks only) --><div class=\"mb-6\" data-visible-for=\"ebook\"><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\"><option value=\"light\">Light</option> <option value=\"paper\">Paper</option> <option value=\"sepia\">Sepia</option> <option value=\"parchment\">Parchment</option> <option value=\"warm\">Warm</option> <option value=\"candlelight\">Candlelight</option></optgroup> <optgroup label=\"🌤️ Sky & 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\"><option value=\"dusk\">Dusk</option> <option value=\"sunset\">Sunset</option> <option value=\"twilight\">Twilight</option></optgroup> <optgroup label=\"🌲 Nature & Earth\"><option value=\"forest\">Forest</option> <option value=\"moss\">Moss</option> <option value=\"slate\">Slate</option></optgroup> <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\"><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><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></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></label></div><!-- Navigation --><div class=\"mb-6\"><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)</label></div><button data-action=\"close-settings\" class=\"w-full py-2 bg-blue-600 text-white rounded hover:bg-blue-700\">Done</button></div></div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div 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\" @click=\"toggleWindowShade($refs.settingsPanel)\"><h3 class=\"panel-title font-semibold\">⚙️ Settings</h3><div class=\"panel-controls flex items-center gap-2\"><button class=\"window-shade-toggle\">─</button></div></div><div class=\"panel-content p-4 overflow-y-auto\"><!-- Display settings --><div class=\"mb-6\"><h3 class=\"font-semibold mb-2\">Display</h3><label class=\"block mb-2\">Chrome Behavior <select name=\"chrome_behavior\" class=\"w-full mt-1 px-3 py-2 rounded border\"><option value=\"auto-hide\">Auto Hide</option> <option value=\"always-visible\">Always Visible</option> <option value=\"hide-on-scroll\">Hide on Scroll</option></select></label> <label class=\"block mb-2\">Progress Mode <select name=\"progress_mode\" class=\"w-full mt-1 px-3 py-2 rounded border\"><option value=\"pages\">Pages</option> <option value=\"chapter\">Chapter</option> <option value=\"percentage\">Percentage</option> <option value=\"time-left\">Time Left</option></select></label></div><!-- 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\" 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> <option value=\"parchment\">Parchment</option> <option value=\"warm\">Warm</option> <option value=\"candlelight\">Candlelight</option></optgroup> <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 and Warmth\"><option value=\"dusk\">Dusk</option> <option value=\"sunset\">Sunset</option> <option value=\"twilight\">Twilight</option></optgroup> <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\"><option value=\"oled\">OLED</option> <option value=\"solarized\">Solarized</option></optgroup></select></label></div><!-- 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\" 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></label> <label class=\"block mb-2\">Font Size <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\" 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\" 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</label></div><button @click=\"toggleSettings()\" class=\"w-full py-2 bg-blue-600 text-white rounded hover:bg-blue-700\">Done</button></div></div>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -197,7 +197,7 @@ func ReaderSettingsPanel() templ.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReaderTOCPanel(metadata ReaderMetadata) templ.Component {
|
func ReaderTOCPanel(_ ReaderMetadata) templ.Component {
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
@@ -218,7 +218,7 @@ func ReaderTOCPanel(metadata ReaderMetadata) templ.Component {
|
|||||||
templ_7745c5c3_Var8 = templ.NopComponent
|
templ_7745c5c3_Var8 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div id=\"toc-panel\" class=\"dockable-panel\" data-panel=\"toc\" data-side=\"left\"><div class=\"panel-header flex items-center justify-between p-3 cursor-pointer\" data-action=\"toggle-panel\"><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></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></div></div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div 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\" @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=\"window-shade-toggle\">─</button></div></div><div class=\"panel-content p-4 overflow-y-auto\"><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>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -247,7 +247,7 @@ func ReaderNavigatorPanel() templ.Component {
|
|||||||
templ_7745c5c3_Var9 = templ.NopComponent
|
templ_7745c5c3_Var9 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<div id=\"navigator-panel\" class=\"dockable-panel\" data-panel=\"navigator\" data-side=\"right\"><div class=\"panel-header flex items-center justify-between p-3 cursor-pointer\" data-action=\"toggle-panel\"><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></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>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<div 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\" @click=\"toggleWindowShade($refs.navigatorPanel)\"><h3 class=\"panel-title font-semibold\">🗺️ Navigator</h3><div class=\"panel-controls flex items-center gap-2\"><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\"></div></div></div>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -276,7 +276,7 @@ func ReaderBookmarksPanel(bookmarks []Bookmark) templ.Component {
|
|||||||
templ_7745c5c3_Var10 = templ.NopComponent
|
templ_7745c5c3_Var10 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<div id=\"bookmarks-panel\" class=\"dockable-panel panel-collapsed\" data-panel=\"bookmarks\" data-side=\"right\"><div class=\"panel-header flex items-center justify-between p-3 cursor-pointer\" data-action=\"toggle-panel\"><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></div></div><div class=\"panel-content p-4 overflow-y-auto\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<div 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\" @click=\"toggleWindowShade($refs.bookmarksPanel)\"><h3 class=\"panel-title font-semibold\">🔖 Bookmarks</h3><div class=\"panel-controls flex items-center gap-2\"><button class=\"window-shade-toggle\">─</button></div></div><div class=\"panel-content p-4 overflow-y-auto\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -286,14 +286,14 @@ func ReaderBookmarksPanel(bookmarks []Bookmark) templ.Component {
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
for _, bookmark := range bookmarks {
|
for _, bookmark := range bookmarks {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<a href=\"#\" data-bookmark-id=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<a href=\"#\" @click.prevent=\"goToBookmarkTarget($el.dataset.cfi)\" data-cfi=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var11 string
|
var templ_7745c5c3_Var11 string
|
||||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(bookmark.ID)
|
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: 342, Col: 37}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 334, 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: 345, Col: 49}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 337, 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: 347, Col: 27}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 339, 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 {
|
||||||
@@ -340,7 +340,7 @@ func ReaderBookmarksPanel(bookmarks []Bookmark) templ.Component {
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<button data-action=\"add-bookmark\" class=\"w-full py-2 mt-4 bg-blue-600 text-white rounded hover:bg-blue-700\">+ Add Bookmark</button></div></div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<button @click=\"addBookmark()\" class=\"w-full py-2 mt-4 bg-blue-600 text-white rounded hover:bg-blue-700\">+ Add Bookmark</button></div></div>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import "foliate-js/view.js";
|
import "foliate-js/view.js";
|
||||||
import { Alpine } from "../alpine";
|
import { Alpine } from "../alpine";
|
||||||
import { loadSettings } from "./settings-manager";
|
import { loadSettings, saveSettings } from "./settings-manager";
|
||||||
import { getToken } from "../storage";
|
import { getToken } from "../storage";
|
||||||
const getCSS = ({
|
const getCSS = ({
|
||||||
spacing,
|
spacing,
|
||||||
@@ -79,6 +79,15 @@ document.addEventListener("alpine:init", () => {
|
|||||||
justify: true,
|
justify: true,
|
||||||
hyphenate: 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: {
|
async initReader(config: {
|
||||||
mediaItemId: string;
|
mediaItemId: string;
|
||||||
fileUrl: string;
|
fileUrl: string;
|
||||||
@@ -87,8 +96,14 @@ document.addEventListener("alpine:init", () => {
|
|||||||
mangaType: string;
|
mangaType: string;
|
||||||
}) {
|
}) {
|
||||||
this.settings = await loadSettings();
|
this.settings = await loadSettings();
|
||||||
if (this.settings?.reading_theme) {
|
if (this.settings) {
|
||||||
document.body.classList.add(`theme-${this.settings.reading_theme}`);
|
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;
|
this.view = document.getElementById("reader-view") as any;
|
||||||
const resp = await fetch(config.fileUrl, {
|
const resp = await fetch(config.fileUrl, {
|
||||||
@@ -200,6 +215,87 @@ document.addEventListener("alpine:init", () => {
|
|||||||
goToFraction(value: string) {
|
goToFraction(value: string) {
|
||||||
this.view?.goToFraction?.(parseFloat(value));
|
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) {
|
handleKeydown(event: KeyboardEvent) {
|
||||||
const k = event.key;
|
const k = event.key;
|
||||||
if (k === "ArrowLeft" || k === "h") this.goLeft();
|
if (k === "ArrowLeft" || k === "h") this.goLeft();
|
||||||
|
|||||||
@@ -357,4 +357,45 @@
|
|||||||
#collections-container[data-wood] .book-card {
|
#collections-container[data-wood] .book-card {
|
||||||
border: 1px solid var(--wood-border);
|
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