From afeb3f5b451b7381969a37914d07e8c3d2bd08b4 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sun, 19 Apr 2026 14:27:56 -0400 Subject: [PATCH] refactor(reader): rewrite reader module for foliate-js pan/zoom integration Major rewrite of the web reader to properly interface with @bookhoard/foliate-js, replacing the abandoned panel-detection architecture with direct pan and zoom support built into the foliate-js FixedLayout renderer. Template (reader.templ): - Fix critical bug: x-init config was using literal strings '{ readerData.X }' inside a quoted attribute, which templ treated as raw text and never interpolated. Values were never actually passed to JavaScript. Now uses fmt.Sprintf() with templ's expression attribute syntax ={ }. - Pass fileUrl from server so foliate-js can open books directly. - Redesign bottom bar with foliate-js parity: left/right navigation buttons, progress slider with tick marks, and zoom controls (zoom out, percentage display, zoom in, magnifier, pan/select mode toggle for PDFs). - Remove panel editor button and enablePanelDetection config. - Add SVG icon styles for consistent reader controls. Go types (templates/types.go): - Expand ReaderMetadata with FormatGroup, MangaType, ReadingDirection, FileURL, and LibraryID fields needed by the reader frontend. Router (internal/router/reader.go): - Populate new ReaderMetadata fields from database values. - Construct FileURL from library ID and file path for the /uploads/library-{id}/* file serving route. Reader JS (reader.ts): - Full rewrite modeled on foliate-js Reader class, adapted for Alpine.js. Opens books via view.open(fileUrl), accesses view.renderer for zoom/pan/navigation, and wires up keyboard shortcuts (+/-/0 for zoom, arrows for nav, Escape for magnifier). - Uses view.isFixedLayout instead of importing FixedLayout class, avoiding a TypeScript module resolution issue with the Vite alias. Settings manager (settings-manager.ts): - Remove dependency on deleted ReaderContext event bus. - Export loadSettings/saveSettings/syncSettings directly as standalone async functions. Cleanup: - Delete reader-context.ts and reader-events.ts (over-engineered event system replaced by direct function calls). - Remove panel_zoom_enabled from ReaderSettings type. --- internal/router/reader.go | 25 +-- templates/reader.templ | 118 ++++++++++---- templates/reader_templ.go | 155 +++++++++--------- templates/types.go | 23 +-- web/src/reader/core/reader-context.ts | 82 ---------- web/src/reader/core/reader-events.ts | 42 ----- web/src/reader/reader.ts | 219 +++++++++++++++++++++----- web/src/reader/settings-manager.ts | 66 +------- web/src/types/reader.d.ts | 1 - 9 files changed, 382 insertions(+), 349 deletions(-) delete mode 100644 web/src/reader/core/reader-context.ts delete mode 100644 web/src/reader/core/reader-events.ts diff --git a/internal/router/reader.go b/internal/router/reader.go index cbf3178..f413a9a 100644 --- a/internal/router/reader.go +++ b/internal/router/reader.go @@ -6,6 +6,7 @@ import ( "bookhoard/internal/services" "bookhoard/templates" "bytes" + "fmt" "net/http" "github.com/google/uuid" @@ -82,16 +83,22 @@ func registerReaderRoutes(cfg *Config) { }) // Convert to template types mediaUUID, _ := uuid.FromBytes(mediaItem.ID.Bytes[0:16]) + libUUID, _ := uuid.FromBytes(mediaItem.LibraryID.Bytes[0:16]) metadata := templates.ReaderMetadata{ - MediaItemID: mediaUUID.String(), - Title: mediaItem.Title, - Author: textToString(mediaItem.Author), - CoverImagePath: textToString(mediaItem.CoverImagePath), - LibraryType: mediaItem.FormatGroup, - MimeType: textToString(mediaItem.MimeType), - FilePath: mediaItem.FilePath, - TotalPages: int(mediaItem.PageCount.Int32), - ChapterCount: int(mediaItem.ChapterCount.Int32), + MediaItemID: mediaUUID.String(), + Title: mediaItem.Title, + Author: textToString(mediaItem.Author), + CoverImagePath: textToString(mediaItem.CoverImagePath), + LibraryType: mediaItem.FormatGroup, + MimeType: textToString(mediaItem.MimeType), + FilePath: mediaItem.FilePath, + TotalPages: int(mediaItem.PageCount.Int32), + ChapterCount: int(mediaItem.ChapterCount.Int32), + FormatGroup: mediaItem.FormatGroup, + MangaType: textToString(mediaItem.MangaType), + ReadingDirection: textToString(mediaItem.ReadingDirection), + LibraryID: libUUID.String(), + FileURL: fmt.Sprintf("/uploads/library-%s/%s", libUUID.String(), mediaItem.FilePath), } // Progress conversion (inline) progressUUID, _ := uuid.FromBytes(progress.ID.Bytes[0:16]) diff --git a/templates/reader.templ b/templates/reader.templ index 5b01d82..4fe6908 100644 --- a/templates/reader.templ +++ b/templates/reader.templ @@ -15,18 +15,23 @@ templ Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookm + @ReaderChrome(user, metadata, progress) @@ -77,25 +82,80 @@ templ ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
-
-
+
+ + + + + + + + +
+ + + +
+ +
{ fmt.Sprintf("%d/%d", progress.CurrentPage, progress.TotalPages) }
-
- - - - - + +
+ +
+ + +
@@ -189,7 +249,7 @@ templ ReaderSettingsPanel() { -

8 libre fonts bundled with Bookhoard

+

8 libre fonts bundled with Bookhoard