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.
This commit is contained in:
+31
-37
@@ -47,7 +47,7 @@ templ BookShelf(
|
|||||||
id="library-select"
|
id="library-select"
|
||||||
class="w-full px-3 py-2 border rounded-lg"
|
class="w-full px-3 py-2 border rounded-lg"
|
||||||
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
||||||
hx-get="/api/media-items/filtered"
|
hx-get="/api/media-items/search"
|
||||||
hx-target="#books-grid"
|
hx-target="#books-grid"
|
||||||
hx-trigger="change"
|
hx-trigger="change"
|
||||||
hx-include="#filter-form"
|
hx-include="#filter-form"
|
||||||
@@ -76,45 +76,39 @@ templ BookShelf(
|
|||||||
placeholder="Search title, author..."
|
placeholder="Search title, author..."
|
||||||
class="w-full px-3 py-2 border rounded-lg"
|
class="w-full px-3 py-2 border rounded-lg"
|
||||||
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
||||||
hx-get="/api/media-items/filtered"
|
hx-get="/api/media-items/search"
|
||||||
hx-target="#books-grid"
|
hx-target="#books-grid"
|
||||||
hx-trigger="keyup changed delay:300ms"
|
hx-trigger="keyup changed delay:300ms"
|
||||||
hx-include="#filter-form"
|
hx-include="#filter-form"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<!-- Author Filter -->
|
<!-- Author Filter -->
|
||||||
|
<!-- Author Filter with Autocomplete -->
|
||||||
<div class="flex-1 min-w-[150px]">
|
<div class="flex-1 min-w-[150px]">
|
||||||
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary)">
|
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary)">
|
||||||
Author
|
Author
|
||||||
</label>
|
</label>
|
||||||
<input
|
<div class="relative" x-data="{ authorValues: [] }">
|
||||||
type="text"
|
<input
|
||||||
name="author_filter"
|
type="text"
|
||||||
placeholder="Filter by author"
|
name="author_filter"
|
||||||
class="w-full px-3 py-2 border rounded-lg"
|
placeholder="Filter by author"
|
||||||
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
class="w-full px-3 py-2 border rounded-lg"
|
||||||
hx-get="/api/media-items/filtered"
|
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
||||||
hx-target="#books-grid"
|
hx-get="/api/media-items/search"
|
||||||
hx-trigger="change"
|
hx-target="#books-grid"
|
||||||
hx-include="#filter-form"
|
hx-trigger="change"
|
||||||
/>
|
hx-include="#filter-form"
|
||||||
</div>
|
list="author-datalist"
|
||||||
<!-- Series Filter -->
|
@focus="fetchAuthorValues($el)"
|
||||||
<div class="flex-1 min-w-[150px]">
|
@input.debounce.300ms="if($el.value === '') authorValues = []"
|
||||||
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary)">
|
/>
|
||||||
Series
|
<datalist id="author-datalist">
|
||||||
</label>
|
<template x-for="item in authorValues" :key="item.value">
|
||||||
<input
|
<option :value="item.value" x-text="`${item.value} (${item.count})`"></option>
|
||||||
type="text"
|
</template>
|
||||||
name="series_filter"
|
</datalist>
|
||||||
placeholder="Filter by series"
|
</div>
|
||||||
class="w-full px-3 py-2 border rounded-lg"
|
|
||||||
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
|
||||||
hx-get="/api/media-items/filtered"
|
|
||||||
hx-target="#books-grid"
|
|
||||||
hx-trigger="change"
|
|
||||||
hx-include="#filter-form"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- Genre Filter -->
|
<!-- Genre Filter -->
|
||||||
<div class="flex-1 min-w-[150px]">
|
<div class="flex-1 min-w-[150px]">
|
||||||
@@ -127,7 +121,7 @@ templ BookShelf(
|
|||||||
placeholder="Filter by genre"
|
placeholder="Filter by genre"
|
||||||
class="w-full px-3 py-2 border rounded-lg"
|
class="w-full px-3 py-2 border rounded-lg"
|
||||||
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
||||||
hx-get="/api/media-items/filtered"
|
hx-get="/api/media-items/search"
|
||||||
hx-target="#books-grid"
|
hx-target="#books-grid"
|
||||||
hx-trigger="change"
|
hx-trigger="change"
|
||||||
hx-include="#filter-form"
|
hx-include="#filter-form"
|
||||||
@@ -145,7 +139,7 @@ templ BookShelf(
|
|||||||
placeholder="From"
|
placeholder="From"
|
||||||
class="w-full px-3 py-2 border rounded-lg"
|
class="w-full px-3 py-2 border rounded-lg"
|
||||||
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
||||||
hx-get="/api/media-items/filtered"
|
hx-get="/api/media-items/search"
|
||||||
hx-target="#books-grid"
|
hx-target="#books-grid"
|
||||||
hx-trigger="change"
|
hx-trigger="change"
|
||||||
hx-include="#filter-form"
|
hx-include="#filter-form"
|
||||||
@@ -156,7 +150,7 @@ templ BookShelf(
|
|||||||
placeholder="To"
|
placeholder="To"
|
||||||
class="w-full px-3 py-2 border rounded-lg"
|
class="w-full px-3 py-2 border rounded-lg"
|
||||||
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
||||||
hx-get="/api/media-items/filtered"
|
hx-get="/api/media-items/search"
|
||||||
hx-target="#books-grid"
|
hx-target="#books-grid"
|
||||||
hx-trigger="change"
|
hx-trigger="change"
|
||||||
hx-include="#filter-form"
|
hx-include="#filter-form"
|
||||||
@@ -171,7 +165,7 @@ templ BookShelf(
|
|||||||
name="has_cover"
|
name="has_cover"
|
||||||
value="true"
|
value="true"
|
||||||
class="w-4 h-4 rounded"
|
class="w-4 h-4 rounded"
|
||||||
hx-get="/api/media-items/filtered"
|
hx-get="/api/media-items/search"
|
||||||
hx-target="#books-grid"
|
hx-target="#books-grid"
|
||||||
hx-trigger="change"
|
hx-trigger="change"
|
||||||
hx-include="#filter-form"
|
hx-include="#filter-form"
|
||||||
@@ -188,7 +182,7 @@ templ BookShelf(
|
|||||||
name="sort"
|
name="sort"
|
||||||
class="w-full px-3 py-2 border rounded-lg"
|
class="w-full px-3 py-2 border rounded-lg"
|
||||||
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
||||||
hx-get="/api/media-items/filtered"
|
hx-get="/api/media-items/search"
|
||||||
hx-target="#books-grid"
|
hx-target="#books-grid"
|
||||||
hx-trigger="change"
|
hx-trigger="change"
|
||||||
hx-include="#filter-form"
|
hx-include="#filter-form"
|
||||||
@@ -295,7 +289,7 @@ templ BookShelf(
|
|||||||
type="button"
|
type="button"
|
||||||
class="px-4 py-2 rounded-lg border disabled:opacity-50"
|
class="px-4 py-2 rounded-lg border disabled:opacity-50"
|
||||||
style="border-color: var(--border); color: var(--text-primary);"
|
style="border-color: var(--border); color: var(--text-primary);"
|
||||||
hx-get="/api/media-items/filtered?library_id={ currentLibraryID }&limit={ limit }&offset={ offset - limit }"
|
hx-get="/api/media-items/search?library_id={ currentLibraryID }&limit={ limit }&offset={ offset - limit }"
|
||||||
hx-target="#books-grid"
|
hx-target="#books-grid"
|
||||||
hx-include="#filter-form"
|
hx-include="#filter-form"
|
||||||
disabled?={ offset <= 0 }
|
disabled?={ offset <= 0 }
|
||||||
@@ -308,7 +302,7 @@ templ BookShelf(
|
|||||||
<button
|
<button
|
||||||
class="px-4 py-2 rounded-lg border disabled:opacity-50"
|
class="px-4 py-2 rounded-lg border disabled:opacity-50"
|
||||||
style="border-color: var(--border); color: var(--text-primary);"
|
style="border-color: var(--border); color: var(--text-primary);"
|
||||||
hx-get="/api/media-items/filtered?library_id={ currentLibraryID }&limit={ limit }&offset={ offset + limit }"
|
hx-get="/api/media-items/search?library_id={ currentLibraryID }&limit={ limit }&offset={ offset + limit }"
|
||||||
hx-target="#books-grid"
|
hx-target="#books-grid"
|
||||||
hx-include="#filter-form"
|
hx-include="#filter-form"
|
||||||
disabled?={ offset+limit >= count }
|
disabled?={ offset+limit >= count }
|
||||||
|
|||||||
Reference in New Issue
Block a user