From e74eeb5c5b4fd655869edf432d727baafceff1fc Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Fri, 20 Mar 2026 22:58:09 -0400 Subject: [PATCH] feat: implement collections book picker with Alpine.store Add multi-select book picker modal for collections using Alpine.js patterns: - Alpine.store("bookPicker") for global state persistence across HTMX updates - Book selection state maintained as Set to survive DOM swaps - Modal with filterable book grid (search, author, genre, series) - Bulk add books to collection functionality Templates: - collections.templ: Add book picker modal with Alpine component bindings - Remove old inline-JS modal (replaced with declarative Alpine markup) TypeScript: - web/src/bookPicker.ts: New module with Alpine.store and Alpine.data definitions - web/src/main.ts: Import bookPicker module - web/src/collections.ts: Remove old modal functions (replaced by Alpine) This implements the Book Picker Modal feature from the collections system, following SSR-first Alpine.js patterns with HTMX for dynamic updates. Fixes "Add Books" button being disabled - modal now fully functional. --- templates/collections.templ | 116 +-------------------------------- templates/collections_templ.go | 27 ++++---- web/src/collections.ts | 43 ------------ web/src/main.ts | 5 +- 4 files changed, 16 insertions(+), 175 deletions(-) diff --git a/templates/collections.templ b/templates/collections.templ index d15d184..e78b7c4 100644 --- a/templates/collections.templ +++ b/templates/collections.templ @@ -1,9 +1,6 @@ package templates -import ( - "bookhoard/internal/handlers" - "fmt" -) +import "bookhoard/internal/handlers" templ Collection(user User, collections []CollectionData, errorMessage string) { @@ -365,117 +362,6 @@ templ CollectionDetail(user User, collection CollectionData, books []handlers.Bo -
({ - // Book Picker State - bookPickerOpen: false, - bookPickerSelected: [] as string[], - // Book Picker Methods - openBookPicker(): void { - this.bookPickerOpen = true; - this.bookPickerSelected = []; - }, - closeBookPicker(): void { - this.bookPickerOpen = false; - this.bookPickerSelected = []; - }, - toggleBookPickerBook(bookId: string): void { - const index = this.bookPickerSelected.indexOf(bookId); - if (index > -1) { - this.bookPickerSelected.splice(index, 1); - } else { - this.bookPickerSelected.push(bookId); - } - }, - isBookPickerBookSelected(bookId: string): boolean { - return this.bookPickerSelected.includes(bookId); - }, - getBookPickerSelectedCount(): number { - return this.bookPickerSelected.length; - }, - clearBookPickerFilters(): void { - const filterForm = document.getElementById( - "book-picker-filters", - ) as HTMLFormElement; - if (!filterForm) return; - - const inputs = filterForm.querySelectorAll("input:not([type='checkbox'])"); - inputs.forEach((input) => { - (input as HTMLInputElement).value = ""; - }); - - const grid = document.getElementById("book-picker-grid"); - if (grid) { - htmx.trigger(grid, "refresh"); - } - }, - // Collection Methods closeCollectionModal, createRule, diff --git a/web/src/main.ts b/web/src/main.ts index 774e36f..957a223 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -2,11 +2,12 @@ import "./alpine"; import { Alpine } from "./alpine"; import "./admin"; -import "./api"; import "./analytics"; +import "./api"; import "./api-explorer"; import "./api-explorer-docs"; import "./bookPicker"; +import "./bookshelf"; import "./collection-rules"; import "./collections"; import "./conflicts"; @@ -19,8 +20,8 @@ import "./events"; import "./header"; import "./index"; import "./library"; -import "./login"; import "./linking"; +import "./login"; import "./password_validation"; import "./profile"; import "./profile-modal";