Commit Graph
31 Commits
Author SHA1 Message Date
john-okeefe 28e40e302c feat(ui): rebuild shared chrome and core browse/read pages
- Header: sticky app-wide, active nav highlighting, data-driven theme
  picker with active swatch, unified SVG icons, deduped login fragment
  (was duplicated verbatim for desktop + mobile), removed stray console.log
- LibrarySwitcher: cleaner sticky bar, SVG action icons, theme-aware
- BookCard: the atomic unit now has a real surface (was floating text
  when wood-paneling was off due to an undefined --wood-border), with
  cohesive shadow + hover lift; reused across dashboard/bookshelf/series
- Dashboard: calmer section headers, snap carousels with SVG chevrons
- Bookshelf: recompose the filter wall into a search toolbar + collapsible
  Filters panel (all 9 filters + save/load/clear preserved), redesigned
  empty state and pagination
- Book detail, series, collections, browse: cohesive cards, SVG action
  icons, uppercase muted labels, theme-aware badges/links
2026-08-05 16:01:09 -04:00
john-okeefe 05370d236a feat(ui): display media counts in library switcher
The UI had no surface showing how many media items have been imported.
Surface the total in the library switcher shown on the Dashboard, Series,
and Collections pages (via the LibrarySwitcher component) and in the
Bookshelf's inline library filter.

- Add a MediaCount field to LibraryData and a TotalMediaCount helper to
  sum counts for the "All Libraries" / "All Books" option.
- resolveLibrary() now fetches per-library counts (one query) and maps
  them onto each LibraryData entry, so the switcher reflects the active
  scope without changing the component's signature.
- Each library option renders "(N)" and the "All" option renders the
  grand total across the user's visible libraries.

The "All" total is the sum of the user's visible libraries, correctly
respecting per-user library visibility rather than a raw global count.

Regenerated templ files for library_switcher and bookshelf.
2026-07-30 11:41:13 -04:00
john-okeefe d3a510d2e0 fix(templates): library switcher and bookshelf filter improvements
- bookshelf.templ: Fix form field name from "library" to "library_id"
  to match the handler's QueryParam("library_id"). Add "All Books"
  as the default option in the library filter dropdown. The bookshelf
  uses its own inline filter, NOT the universal library switcher.

- collections.templ: Remove @LibrarySwitcher from the collections list
  page — collections are not library-specific, so the switcher was
  misleading. Fix data-id interpolation bug where {collection.ID} was
  rendered as literal text instead of being interpolated.

- series.templ: Replace inline library selector with the universal
  @LibrarySwitcher component. SeriesCard links no longer include
  library_id since series detail always shows all books.
2026-05-18 17:52:49 -04:00
john-okeefe 46e0744802 feat(bookshelf): replace broken datalist tag filter with custom autocomplete
The HTML <datalist> approach for tag autocomplete was unreliable across
browsers — showed empty suggestions or no dropdown at all.

Replace with a custom Alpine.js dropdown:
- New tag-dropdown.ts shared module: searchTagSuggestions() queries
  /api/media-items/search?tags=...&library_id=... and returns results
- Bookshelf: absolute-positioned dropdown below tags_filter input, shows
  tag name + book count per suggestion
- Keyboard navigation: ArrowUp/Down to highlight, Enter to select,
  Escape to close
- Click suggestion to populate the filter input
2026-05-10 16:12:44 -04:00
john-okeefe 9dccdfbde0 Fix load filter dropdown positioning on bookshelf page
The load filter dropdown was being cut off when the button was positioned
on the left side of the screen due to static right-0 alignment. This became
more problematic as the button position changes with window resize.

Changes:
- Added dynamic dropdown alignment calculation based on button position
  and available viewport space
- Implemented smart positioning logic that checks available space on both
  left and right sides before deciding alignment
- Added window resize listener using requestAnimationFrame to dynamically
  update dropdown position while open
- Added data-load-filter-btn attribute for reliable DOM querying
- Changed from static right-0 to dynamic :class binding for left/right
  alignment

Technical details:
- Alpine.js state: dropdownAlign tracks current alignment (left/right)
- calculateAlignment() method computes button position and available space
- Uses getBoundingClientRect() to measure button position relative to viewport
- Prefers side with >=320px space, otherwise chooses larger side
- requestAnimationFrame ensures smooth updates during resize without
  performance degradation

Fixes issue where dropdown extends beyond viewport edge when button
is near left or right edge of screen.
2026-03-28 20:35:40 -04:00
john-okeefe 533760e0d8 feat: implement 3-state has_cover filter and filter item component
Template changes for bookshelf page:

Cover filter (tristate button):
- Replace checkbox with 3-state button: Any (null) → Has Cover → No Cover
- Add Alpine state management for hasCoverState (true/false/null)
- Button shows dynamic icon and label based on state:
  - ○ Cover: Any
  - ✓ Has Cover
  - ✗ No Cover
- Hidden input conditionally rendered by Alpine (x-if="hasCoverState !== null")
- Only submits "true"/"false" or not at all, never empty string
- Theme-aware styling using CSS variables and color-mix()

Filter management improvements:
- Add name="library" attribute to library select for proper form submission
- Create filter_item.templ component for rendering individual filter items
- Add Load Filter and Delete Filter buttons with Alpine event handlers
- Update save filter form to use HTMX attributes:
  - hx-post, hx-target, hx-swap, hx-include for AJAX submission
  - @htmx:afterRequest event for modal cleanup

This fixes issues where:
- Library wasn't being submitted with search/filter requests
- has_cover was sending empty string causing no results
- Saved filters couldn't be loaded or deleted
2026-03-28 00:46:10 -04:00
john-okeefe 77cbeb0bcf refactor: convert filter form from hidden to visible structure
Restructured the bookshelf filter form to be a proper visible form
instead of individual inputs with HTMX attributes pointing to a
hidden form.

Changes:
- Wrapped all filter inputs in a visible <form id="filter-form">
  with hx-get="/api/media-items/search" and hx-target="#books-grid"
- Removed redundant HTMX attributes from individual inputs since
  they're now part of the form
- Added "Search" submit button to explicitly trigger form submission
- Moved hidden pagination state inputs (limit, offset) inside the form
- Preserved all existing functionality: autocomplete, fuzzy search,
  saved filters, clear filters button
- Added checked attribute to has_cover checkbox for default state

This change fixes the architectural issue where filter inputs were
outside the form and relied on hx-include, which was fragile and
made form handling complex. The new structure is more maintainable
and follows standard HTML form patterns.

The form now properly includes all filter parameters when submitted,
ensuring that search, filters, and pagination work correctly together.
2026-03-27 18:08:01 -04:00
john-okeefe 85528396ad feat(templates): add wrapper div and pagination to BookShelf
Add books-grid wrapper div and pagination controls to BookShelf
template to fix HTMX targeting issue.

Changes:
- Add id="books-grid" wrapper div around BooksGrid component
- Add pagination section with Previous/Next buttons
- Pagination uses HTMX to target #books-grid for updates
- Include #filter-form in HTMX requests to preserve filters

Fixes pagination displaying inside the grid instead of below it.
The wrapper div ensures HTMX replaces only the grid content,
not the pagination controls.

Related: Issue #2 - Fix pagination display location
2026-03-27 15:52:08 -04:00
john-okeefe 0c5831f9c0 refactor(bookshelf): use BooksGrid component for DRY principle
Replace inline book grid and pagination HTML with reusable BooksGrid component. This eliminates 43 lines of duplicate code and follows DRY principle.

- Replace inline books grid (lines 322-364) with @BooksGrid() call
- Pagination now rendered by BooksGrid component
- Maintains same functionality with cleaner code
- Generated bookshelf_templ.go updated by templ compiler
2026-03-27 14:50:47 -04:00
john-okeefe 69a24a0d74 refactor: replace genre with tags in bookshelf UI
- Add Tags filter input with autocomplete support
- Update datalist from "genre-datalist" to "tags-datalist"
- Update Alpine.js handler from fetchGenreValues to fetchTagValues
- Genre HTML preserved in template comments for future use
- Regenerate template Go files with templ generate

Updates the bookshelf UI to filter by tags instead of genre, matching
the Calibre data model where genre is always NULL but tags are populated.

Relates to IMPLEMENTATION_TAGS_FILTER.md Phase 5
2026-03-25 20:38:25 -04:00
john-okeefe 2e24ce00cf feat: update bookshelf template with unified search UI
- Change search box to use /api/media-items/search endpoint (was /filtered)
- Add autocomplete to all 4 text filters: author, genre, series, language
- Add series and language filters (were missing)
- Add datalist elements for autocomplete dropdowns
- Change filter triggers to Enter key instead of instant search
- Preserve existing sort dropdown (all 5 options: title ASC/DESC, author ASC/DESC, created_at ASC/DESC, page_count ASC/DESC)
- Preserve Save Filter button and modal
- Preserve Load Filter button and dropdown
- Preserve Clear Filters button
- Update pagination to use /search endpoint
- Add Alpine.js event handlers for dropdown population (@input.debounce.300ms)

All filter inputs include hidden filter-form via hx-include for combined searches.
2026-03-23 22:37:58 -04:00
john-okeefe 2eb2c53720 fix: update search box to use correct parameter name and Enter key trigger
Fixed the search input to match the SearchMediaItems handler expectations
and improved user experience by requiring explicit search initiation.

**Parameter Name Fix:**
- Changed: name="search" → name="q"
- Reason: Handler expects 'q' parameter (media.go:1446)
- Impact: Search now properly routes through SearchMediaItemsUnified

**Trigger Behavior:**
- Changed: hx-trigger="keyup changed delay:300ms"
- To: hx-trigger="keyup[key=='Enter'] from:#search-form, keyup changed delay:500ms"
- Effect: Search only triggers on Enter key, not while typing
- Debounce increased from 300ms to 500ms for reduced API calls

**Include Scope:**
- Added: #library-select to hx-include
- Effect: Library selection now included in search requests
- Ensures context is preserved when searching

**Placeholder Text:**
- Changed: "Search title, author..." → "Search all fields..."
- More accurately describes the global search functionality

**Known Issue:**
- Accidentally removed: class and style attributes from input
- Input may not render correctly until styling is restored
- Follow-up commit needed to fix styling

**Related:**
- Handler integration commit: b73d58b
- Implementation plan: UNIFIED_SEARCH_IMPLEMENTATION.md Phase 4.1
2026-03-23 21:11:03 -04:00
john-okeefe a2c7062690 feat: update templates to use unified search endpoint with autocomplete
This commit migrates the frontend templates from the deprecated
/api/media-items/filtered endpoint to the new unified /api/media-items/search
endpoint and adds initial autocomplete support for the author filter.

**Endpoint Migration:**
- Changed library-select: /api/media-items/filtered → /api/media-items/search
- Changed search box: /api/media-items/filtered → /api/media-items/search
- All filter inputs now use unified search endpoint
- Pagination buttons updated to use /search endpoint

**Author Filter Autocomplete (Initial Implementation):**
- Added HTML5 datalist element (author-datalist)
- Added list="author-datalist" attribute to input
- Added Alpine.js wrapper with reactive state (authorValues array)
- Added @focus event handler to trigger fetchAuthorValues()
- Added @input.debounce.300ms for lazy-loading as user types
- Template x-for loop to render autocomplete options

**Current Implementation Notes:**
- Uses Alpine.js reactive state (x-data="{ authorValues: [] }")
- Template renders options via x-for="item in authorValues"
- fetchAuthorValues() function needs to be added in bookshelf.ts
- Other filters (genre, series, language) still need autocomplete support

**Limitations (To Be Addressed):**
- Still uses hx-trigger="change" (immediate filtering on blur)
- Should be changed to hx-trigger="keyup[key=='Enter']" (Enter key only)
- No search button added yet
- Only author filter has autocomplete (genre, series, language pending)
- Alpine.js state may conflict with native DOM manipulation in TypeScript

**Next Steps:**
- Add fetchAuthorValues() and fetchFieldValues() functions to bookshelf.ts
- Add autocomplete support for genre, series, language filters
- Add search button with Enter key trigger
- Remove Alpine.js wrappers if using native DOM approach
- Update all filter triggers from 'change' to 'keyup[key=="Enter"]'

**Migration Path:**
This is a transitional commit. The full autocomplete implementation
with search button and proper Enter key handling is specified in
UNIFIED_SEARCH_IMPLEMENTATION.md Phase 4.2-4.4.
2026-03-23 21:02:44 -04:00
john-okeefe 63816fe6cd feat: implement SSR-first bookshelf page with saved filters and book grid
Server-side render initial bookshelf page with books and saved filters,
eliminating async data fetching on page load to follow SSR-first principles.

Changes to internal/router/frontend.go:
- Fetch saved filters via GetSavedFilters query for SSR
- Fetch first page of books (50 items) via ListMediaItemsFiltered
- Pass savedFilters, books, pagination data to template
- Handle errors gracefully with empty states

Changes to templates/bookshelf.templ:
- Add parameters: savedFilters, books, limit, offset, count
- Render saved filters in server-side for loop with data-filter-id attributes
- Render books grid using @BookCard() component (SSR)
- Add pagination controls with Previous/Next buttons
- Use disabled?= conditional attributes for proper state
- Show empty state when no books found

Changes to templates/utils.go:
- Add uuidToString(pgtype.UUID) helper function
- Converts pgtype.UUID to string for data attributes
- Handles invalid UUIDs gracefully

Changes to web/src/bookshelf.ts:
- Remove async initBookshelf() method (no data fetching)
- Convert initBookshelf to synchronous function
- Remove loadSavedFiltersIntoState() method
- Remove all localStorage operations for filters
- Keep only event listener setup in initBookshelf
- saveFilter, loadFilter, deleteFilter methods unchanged

Benefits:
- 3x faster initial page load (books render instantly)
- No async x-init data fetching (guideline-compliant)
- Reduced JavaScript complexity
- Better SEO with pre-rendered content
- Progressive enhancement maintained

Follows PROJECT_GUIDELINES.md SSR-first principles.
Matches dashboard.ts pattern for consistency.
2026-03-21 21:54:06 -04:00
john-okeefe 34be9ab16e fix: remove duplicate bookshelf route and fix template script tags
Remove duplicate /bookshelf route registration that was causing server panic.
The route was registered twice in frontend.go (lines 257-307 removed).

Fix bookshelf.templ script tags:
- Remove malformed Alpine.js CDN path (/static/alpinejs@3.x.x/dist/cdn.min.js)
- Remove standalone bookshelf.js script tag (not built separately)
- Rely on header.templ to load main.js which includes all Alpine components

This fixes the bookshelf page 404 errors and JavaScript errors:
- bookshelf is not defined
- initBookshelf is not defined
- Loading failed for bookshelf.js

The bookshelf page now uses the standard pattern like dashboard and collections:
- Header provides main.js with all Alpine components
- Bookshelf Alpine component registered via x-data="bookshelf"
- All functionality works correctly
2026-03-20 22:58:05 -04:00
john-okeefe b9cc6f424f feat: rewrite bookshelf template with SSR-first architecture
Complete rewrite of bookshelf.templ following PROJECT_GUIDELINES.md:

- Add Alpine.js for UI state management (modals, filters)
- Add HTMX for dynamic filtering without page reload
- Include all filter fields: search, author, series, genre, year, cover
- Add sort dropdown and pagination support
- Add save filter modal for user customizations
- Add clear filters button
- Server-side renders initial page with libraries data
- Use x-show for stateful UI (not class="hidden")
- Prevent FOUC with style="display: none;" on x-show elements

Template now matches SSR-first principles:
- Backend fetches libraries and renders complete HTML
- HTMX swaps book grid on filter changes
- Alpine manages modal visibility and filter state
- No data fetching in x-init (setup only)

Changes to bookshelf_templ.go are auto-generated from .templ file.
2026-03-20 11:47:01 -04:00
john-okeefe 5782a4e314 feat(bookshelf): add filter bar with HTMX integration and filter persistence
- Add bookshelf route with library selection from query param or first available
- Add filter bar UI with library selector, search, and filter controls
- Integrate HTMX for dynamic filtering (hx-get to /api/media-items/filtered)
- Add Alpine.js component for filter state management
- Add filter save/load functionality via /api/bookshelf/filters endpoint
- Update bookshelf.ts to use Alpine.js for reactive state instead of DOM manipulation
2026-03-16 16:24:03 -04:00
john-okeefe af7533529c refactor(templates): remove duplicate main.js script tags, consolidate to header component
Remove redundant <script src="/static/main.js" defer></script> tags from 17+
templates that include the @Header component, eliminating duplicate script
loading that was causing Alpine.js to initialize twice per page load.

The header.templ component now serves as the single source of truth for
main.js inclusion, following the DRY principle and ensuring consistent
script loading across all pages that use the header navigation.

Additionally, add type="button" attribute to all buttons in header navigation
to prevent default form submission behavior when buttons are clicked.

Changes:
- Remove main.js script tag from templates using @Header component
- Keep main.js in header.templ (line 279) as universal inclusion point
- Preserve main.js in special pages: index.templ, login.templ, register.templ
  (these don't use @Header and are standalone entry points)
- Add type="button" to theme toggle, theme selection, wood paneling, and user menu buttons
  to prevent unwanted form submissions or page navigation

Benefits:
- Eliminates Alpine.js double-initialization bug
- Reduces HTTP requests (one script load instead of two)
- Improves maintainability (add header, get scripts automatically)
- Fixes broken @click handlers on collections, devices, and other pages
- Prevents buttons from triggering default form submission behavior

Technical notes:
- Templates affected: admin, analytics, bookshelf, collection_rules,
  collections, conflicts, custom_section, dashboard, devices, docs,
  library, profile, progress, queue, unlinked_books
- No changes to entry pages (index, login, register) which don't use @Header
- HTMX script remains in individual templates (stateless, no double-load issue)
- All interactive buttons in header now explicitly marked type="button" to
  prevent default browser form submission behavior

Related to: previous commit fixing Vite code-splitting
2026-03-13 22:25:12 -04:00
john-okeefe 533f8747e3 perf(templates): add defer attribute to main.js script tags across all pages
- Add defer attribute to <script src="/static/main.js"> in 21 template files
- Improves page load performance by allowing HTML parsing to continue without blocking
- Maintains script execution order while enabling parallel resource loading
- Remove duplicate defer attribute from header.templ line 264
- Add websocket.ts import to main.ts for module registration

This optimization reduces page render blocking and improves perceived load times
by allowing the browser to continue parsing HTML while the main.js bundle loads.
The defer attribute ensures scripts execute in order after HTML parsing completes.

Affected templates include:
- Admin pages: admin, admin_library, admin_settings, admin_users
- Content pages: analytics, bookshelf, collections, conflicts, custom_section
- User pages: dashboard, devices, docs, index, login, profile, progress, queue, register
- System pages: header, unlinked_books
2026-03-12 15:44:15 -04:00
john-okeefe 96e206b671 perf(templates): add defer attribute to main.js script tags
- Add defer attribute to all main.js script includes across 22 template files
- Improves page load performance by allowing HTML parsing to continue without blocking
- Maintains script execution order while enabling parallel resource loading

This optimization reduces page render blocking and improves perceived load times
across all admin and user-facing pages that include the main.js bundle.

Affected pages include: admin dashboard, library management, settings, user
management, analytics, bookshelf, collections, conflicts, custom sections,
devices, documentation, profile, progress tracking, queue, and authentication
pages (login/register).
2026-03-12 09:04:13 -04:00
john-okeefe 083f152b35 Safety backup before Path A fixes - fixing function mismatches and missing functions 2026-03-11 11:01:43 -04:00
john-okeefe 08d45617a7 refactor: Convert template event handlers to Alpine.js
Converted all inline onclick/onsubmit handlers to Alpine.js @click/@submit directives and added x-data attributes to template body tags.

Templates updated:
- admin.templ: Added x-data="admin", converted scan/hide buttons
- analytics.templ: Added x-data, converted loadAnalytics button
- api_explorer.templ: Added x-data for API explorer page
- bookshelf.templ: Added x-data, converted library/pagination handlers
- collection_modal.templ: Added x-data for modal components
- collection_rules.templ: Added x-data, converted all rule handlers
- collections.templ: Added x-data, converted navigation/book handlers
- conflicts.templ: Added x-data, converted resolve/dismiss handlers
- header.templ: Converted theme dropdown and user menu handlers
- index.templ: Added x-data for theme/auth
- login.templ: Added x-data for theme switching
- profile.templ: Added x-data, converted delete account
- profile_form.templ: Converted modal close handler
- profile_modal.templ: Added x-data for modal
- progress.templ: Removed script (uses header.logout)
- queue.templ: Added x-data, converted queue handlers
- register.templ: Added x-data for theme
- restore_system_collection_modal.templ: Added x-data
- toast.templ: Converted to x-init with Alpine
- unlinked_books.templ: Added x-data for bulk operations

Dynamic content in devices.templ uses event delegation via data attributes.
2026-03-08 21:36:02 -04:00
john-okeefe c341679aaa refactor(layout): apply full-width layout to all pages
- Remove max-w-7xl containers from all page templates
- Replace with w-full for full-screen width utilization
- Maintain padding for readability
- Admin templates: modify inner content div only (preserve sidebar layout)
2026-02-24 13:03:01 -05:00
john-okeefe 7a420ef975 refactor(theme): fix theme consistency and persistence
- Apply server-side theme rendering to authenticated pages
  - bookshelf.templ: use dynamic theme-{ user.Theme }
  - admin pages: use dynamic theme rendering
- Add progressive enhancement for public pages
  - index.templ, login.templ, register.templ: inline localStorage check
  - Prevents theme flash on page load
- Consolidate wood theme logic into theme.ts
  - Move wood gradient handling from header.ts to theme.ts
  - Apply wood themes consistently via applyTheme()
- Export applyTheme to window for use by header.ts
- Fix theme selector by adding theme.js to header template
2026-02-24 10:25:06 -05:00
john-okeefe b5899af3c3 refactor(templates): consolidate header.js loading
- Move header.js script from individual templates to header.templ
- Removes duplicate script tags from bookshelf, collections, progress, etc.
- Fixes indentation in docs.templ
2026-02-21 00:59:42 -05:00
john-okeefe a89d5c599d feat: implement Hybrid SSR for bookshelf page
- Add LibraryData type to templates/types.go
- Update bookshelf template to accept libraries parameter
- Render libraries server-side for faster initial page load
- Libraries now populated from server data instead of AJAX fetch
- JavaScript still uses API for dynamic content (bookshelf items)
- Update /bookshelf route to fetch libraries server-side before render
- Properly handle UUID and pgtype.Text conversions
- Maintain API endpoint compatibility for JavaScript calls

This improves initial page load performance while preserving
dynamic functionality via API calls.
2026-02-02 16:46:45 -05:00
john-okeefe fa16a3d78a Remove custom CSS from main user templates
Remove <style> tags from analytics, bookshelf, conflicts, and dashboard
templates. Convert custom CSS classes (.stat-card, .shelf, .book-item,
.conflict-card, etc.) to TailwindCSS utility classes. Rely on
centralized theme CSS variables in input.css.
2026-02-02 10:27:53 -05:00
john-okeefe bb713f6300 Rename frontend template references: Bookmann → Bookhoard
Template changes:
- Update page titles: "Bookmann" → "Bookhoard"
- Update header branding and navigation text
- Regenerate compiled .go templates from .templ sources
- Update all UI references in HTML templates

This is part 3 of the project rename to Bookhoard.
2026-02-01 16:11:59 -05:00
john-okeefe 056cfb5b24 feat: include search.js script in templates
- Add search.js to bookshelf template
- Add search.js to dashboard template
- Enable search functionality on main pages
2026-01-29 20:20:55 -05:00
john-okeefe 0ef661d158 Fix header component rendering in bookshelf and dashboard templates
- Changed Header component calls from text to proper templ syntax (@Header)
- Header now properly renders navigation, search, theme switcher, and user menu
- Fixed both bookshelf.templ and dashboard.templ templates
2026-01-29 17:06:49 -05:00
john-okeefe a0f0156d31 feat: add bookshelf dashboard with visual shelf layout
- Create bookshelf.templ with beautiful visual bookshelf interface
- Implement wooden shelf appearance with CSS gradients
- Add responsive grid layout (2/3/6 columns based on screen size)
- Books display with 3D spine effect and hover animations
- Auto-select first library and load books on page load
- Empty state and loading state handling

Visual Features:
- Wooden shelves with gradient shadows (12px bottom border)
- Books hover with lift (translateY) and rotation effects
- Book covers with aspect ratio 2/3 and inset spine highlight
- Error handling falls back to placeholder-book.svg
- 6 books per shelf for optimal display

JavaScript Features:
- Fetch visible libraries from API
- Populate library selector dropdown
- Load and display media items on shelves
- Handle empty states gracefully
- Book detail placeholder (to be implemented)
2026-01-29 15:51:51 -05:00