feat(reader): page thumbnails tab + reliable PDF contents
Investigation: the contents drawer read book.toc, which makePDF builds from pdf.getOutline() — verified against the real library PDF (Head First SQL) through the exact vendored pdf.js build AND the exact range transport the browser uses: 18 chapter entries come back. So the source is right; manga-scan PDFs and CBZs simply have no embedded outline, which made Contents look broken exactly where users expect page-based navigation. - TOC now populates eagerly right after the book opens (toggle-time lazy population removed), so an existing outline can never silently miss due to timing; the drawer keeps the honest empty-state text for books without outlines. - New 'Pages' tab in the contents drawer for fixed-layout books: a Kavita-style thumbnail grid (3-up, current page highlighted and scrolled into view, click to jump — recorded on the back-to- location stack). Thumbnails render client-side: PDFs via the in-memory pdf.js document (small viewport render, Alpine.raw unwrap); comics via the page's image blob drawn down to a 110px canvas, then unloading the full-size blob so thumbnailling doesn't hoard page images. Lazy via IntersectionObserver scoped to the drawer's scroll container (200px margin), canvases cached at module level so revisits are instant; failures warn in console and allow retry. The backend /readers/thumbnails endpoint turned out to be an empty stub, so nothing server-side was worth wiring.
This commit is contained in:
+27
-1
@@ -521,8 +521,18 @@ templ drawerHeader(title string) {
|
||||
|
||||
templ ReaderTOCDrawer() {
|
||||
@drawerHeader("Contents")
|
||||
<!-- Fixed-layout books get a Pages (thumbnails) tab; outline TOC may be
|
||||
absent in manga-scan PDFs and CBZs. -->
|
||||
<div class="reader-tabs" x-show="isFixedLayout">
|
||||
<button :class="tocTab === 'contents' ? 'active' : ''" @click="tocTab = 'contents'">
|
||||
Contents <span class="reader-tab-count" x-text="tocItems.length">0</span>
|
||||
</button>
|
||||
<button :class="tocTab === 'pages' ? 'active' : ''" @click="tocTab = 'pages'; initPageThumbs()">
|
||||
Pages <span class="reader-tab-count" x-text="pageThumbList.length || ''">0</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="reader-drawer-body">
|
||||
<nav id="toc-list" class="space-y-1">
|
||||
<nav id="toc-list" class="space-y-1" x-show="tocTab === 'contents' || !isFixedLayout">
|
||||
<template x-for="item in tocItems" :key="item.href">
|
||||
<a
|
||||
href="#"
|
||||
@@ -536,6 +546,22 @@ templ ReaderTOCDrawer() {
|
||||
<p class="text-sm py-2" style="color: var(--text-secondary)">No table of contents</p>
|
||||
</template>
|
||||
</nav>
|
||||
<div id="page-thumb-grid" class="reader-thumb-grid" x-show="isFixedLayout && tocTab === 'pages'">
|
||||
<template x-for="p in pageThumbList" :key="p.index">
|
||||
<button
|
||||
class="reader-thumb"
|
||||
:class="p.index === currentPageIndex ? 'active' : ''"
|
||||
@click="goToPage(p.index)"
|
||||
:title="'Page ' + (p.index + 1)"
|
||||
>
|
||||
<div class="reader-thumb-img" :data-page="p.index"></div>
|
||||
<span class="reader-thumb-num" x-text="p.index + 1">1</span>
|
||||
</button>
|
||||
</template>
|
||||
<template x-if="pageThumbList.length === 0">
|
||||
<p class="text-sm py-2" style="color: var(--text-secondary)">No pages</p>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user