feat(annotations + player): bookmark rows in the banner, stacked volume panel, badge parity for comics/manga
Annotations banner: audio/text bookmarks join notes and highlights — third row kind (bookmark glyph indicator, H:MM:SS / Page N / Chapter N friendly labels), edit-note/delete actions branch by kind, and the banner's export now covers all annotations. The archived ledger (ListDeletedAnnotationsForBook) already tombstoned bookmarks equally and Restore/Purge take annotation_type=bookmark — verified live: delete → restore brings the row back with its note intact. Volume: the dragger sits BELOW the mute icon in the panel (stacked flyout shape) with the horizontal slider from the last round. Badges: the real fix for comics/manga — format_group only knows 'comic_archive' (manga_type is unreliable across sources), so the comic/manga split is resolved from the LIBRARY type server-side. handlers.MediaBadgeKind + BookInfo.Badge at every construction site (dashboard SSR + sections API, collections, series, and the bookshelf search grid — which was also missing FormatGroup entirely, so no card ever badged there). Both renderers (templ + dashboard.ts) switch on the resolved badge. Verified: All-Books grid renders 2 Comic + 3 Manga badges, dashboard the audiobook one. Infra: header.templ main.js src gains ?v=<stamp> — browsers keyed an intermediate bundle by URL and served it across rebuilds (the stale 'Add note' the user saw); bump the stamp when JS stale-shadows.
This commit is contained in:
1 parent
2c714dcb60
commit
89665fb2bc
19 files changed
+273
-92
No files matched your search
+8
-1
@@ -217,13 +217,20 @@ Alpine.data("audioReader", () => ({
|
||||
const p = await progress.json();
|
||||
const chapter = Number(p.chapter ?? 0);
|
||||
const offset = Number(p.audio_offset_seconds ?? 0);
|
||||
if (Number.isFinite(p.percentage) && this.bookDuration > 0) {
|
||||
// Banner/deep-link jumps (?loc=audio:<seconds>) win over the
|
||||
// stored position: the user explicitly asked for this spot.
|
||||
const locParam = new URLSearchParams(window.location.search).get("loc") || "";
|
||||
if (locParam.startsWith("audio:")) {
|
||||
const at = Number(locParam.slice("audio:".length));
|
||||
if (Number.isFinite(at)) this.seekTo(at);
|
||||
} else if (Number.isFinite(p.percentage) && this.bookDuration > 0) {
|
||||
const at = Number(p.percentage) * this.bookDuration;
|
||||
this.seekTo(at);
|
||||
} else if (chapter > 0 && this.chapters[chapter]?.start != null) {
|
||||
this.seekTo(this.chapters[chapter].start + offset);
|
||||
}
|
||||
this.currentChapterIdx = Math.max(0, chapter);
|
||||
this.tickChapter();
|
||||
}
|
||||
} catch {
|
||||
// Offline start: still playable; sync resumes when reachable.
|
||||
|
||||
Reference in new issue
Block a user