feat(facets+ui): clickable facet values everywhere, genre prettify, active-filter chips
Frontend half of the faceted-navigation Phase 2. 'Everything filterable should be clickable': every displayed facet value links to the bookshelf pre-filtered by its raw stored value; display forms are prettified at render, never in the DB. - templates/facet_links.templ: FacetLink (inline links) + FacetBadge (badge links) to /bookshelf?<facet>_filter=<value>; the raw value rides the param while the shown text is prettified, so matching and display can never disagree. Series/tags keep their dedicated pages by design; the card media-type icon stays unlinked (a derived kind, not a column value — deep-linking it would send wrong values). - book_detail.templ: publisher, language, edition, genre, imprint cells + narrators line + age-rating/story-arc badges are now links. - prettifyGenre (templates/utils.go + unit tests): title-cases only entirely-lowercase values; small words stay down except first position; & preserved. TS port for chip labels documents the twin. - bookshelf.templ + bookshelf.ts: active-filter chips bar — Alpine renders chips from client-local form state (sanctioned island); one bubbled htmx:afterRequest hook keeps chips + shareable URL in sync; removeFilterChip clears the facet and re-applies. URL deep links now accept all 18 facet params (hidden inputs where the drawer has no field). Pins changelog: Batch B/C entry in docs/contract_pins.md. Rendering ownership guard (make lint-rendering) green.
This commit is contained in:
1 parent
7e586df48a
commit
bbedbfb13f
10 files changed
+990
-545
No files matched your search
@@ -83,7 +83,13 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string, tomb
|
||||
}
|
||||
if book.IsAudio() && len(book.Narrators) > 0 {
|
||||
<h3 class="text-sm md:text-base mb-5" style="color: var(--text-secondary)">
|
||||
Narrated by { strings.Join(book.Narrators, ", ") }
|
||||
Narrated by
|
||||
for i, narratorName := range book.Narrators {
|
||||
if i > 0 {
|
||||
<span>, </span>
|
||||
}
|
||||
@FacetLink("narrator", narratorName, narratorName)
|
||||
}
|
||||
</h3>
|
||||
}
|
||||
<!-- Action buttons -->
|
||||
@@ -216,13 +222,13 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string, tomb
|
||||
<span class="chip" style="background-color: var(--accent); color: var(--bg-primary);">{ strings.ToUpper(book.ReadingDirection.String) }</span>
|
||||
}
|
||||
if book.AgeRating.Valid && book.AgeRating.String != "" {
|
||||
<span class="badge" style="border: 1px solid var(--border); color: var(--text-secondary);">{ book.AgeRating.String }</span>
|
||||
@FacetBadge("age_rating", book.AgeRating.String, book.AgeRating.String)
|
||||
}
|
||||
if book.IsBlackAndWhite.Valid && book.IsBlackAndWhite.Bool {
|
||||
<span class="badge" style="border: 1px solid var(--border); color: var(--text-secondary);">B&W</span>
|
||||
}
|
||||
if book.StoryArc.Valid && book.StoryArc.String != "" {
|
||||
<span class="badge" style="border: 1px solid var(--border); color: var(--text-secondary);">{ book.StoryArc.String }</span>
|
||||
@FacetBadge("story_arc", book.StoryArc.String, book.StoryArc.String)
|
||||
}
|
||||
</div>
|
||||
if len(book.Tags) > 0 {
|
||||
@@ -377,7 +383,7 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string, tomb
|
||||
if book.Publisher.Valid && book.Publisher.String != "" {
|
||||
<div>
|
||||
<p class="text-xs uppercase tracking-wide" style="color: var(--text-secondary)">Publisher</p>
|
||||
<p style="color: var(--text-primary)">{ book.Publisher.String }</p>
|
||||
<p style="color: var(--text-primary)">@FacetLink("publisher", book.Publisher.String, book.Publisher.String)</p>
|
||||
</div>
|
||||
}
|
||||
if book.DatePublished.Valid {
|
||||
@@ -395,13 +401,20 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string, tomb
|
||||
if book.Language.Valid && book.Language.String != "" {
|
||||
<div>
|
||||
<p class="text-xs uppercase tracking-wide" style="color: var(--text-secondary)">Language</p>
|
||||
<p class="capitalize" style="color: var(--text-primary)">{ book.Language.String }</p>
|
||||
<p style="color: var(--text-primary)">@FacetLink("language", book.Language.String, languageDisplay(book.Language.String))</p>
|
||||
</div>
|
||||
}
|
||||
if book.IsAudio() && len(book.Narrators) > 0 {
|
||||
<div>
|
||||
<p class="text-xs uppercase tracking-wide" style="color: var(--text-secondary)">Narrator</p>
|
||||
<p style="color: var(--text-primary)">{ strings.Join(book.Narrators, ", ") }</p>
|
||||
<p style="color: var(--text-primary)">
|
||||
for i, narratorName := range book.Narrators {
|
||||
if i > 0 {
|
||||
<span>, </span>
|
||||
}
|
||||
@FacetLink("narrator", narratorName, narratorName)
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
if book.IsAudio() && book.AudioDuration() > 0 {
|
||||
@@ -419,7 +432,7 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string, tomb
|
||||
if book.Edition.Valid && book.Edition.String != "" {
|
||||
<div>
|
||||
<p class="text-xs uppercase tracking-wide" style="color: var(--text-secondary)">Edition</p>
|
||||
<p style="color: var(--text-primary)">{ book.Edition.String }</p>
|
||||
<p style="color: var(--text-primary)">@FacetLink("edition", book.Edition.String, book.Edition.String)</p>
|
||||
</div>
|
||||
}
|
||||
if book.PageCount.Valid && book.PageCount.Int32 > 0 {
|
||||
@@ -431,7 +444,7 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string, tomb
|
||||
if book.Genre.Valid && book.Genre.String != "" {
|
||||
<div>
|
||||
<p class="text-xs uppercase tracking-wide" style="color: var(--text-secondary)">Genre</p>
|
||||
<p style="color: var(--text-primary)">{ book.Genre.String }</p>
|
||||
<p style="color: var(--text-primary)">@FacetLink("genre", book.Genre.String, prettifyGenre(book.Genre.String))</p>
|
||||
</div>
|
||||
}
|
||||
if book.SeriesCount.Valid && book.SeriesCount.Int32 > 0 {
|
||||
@@ -449,7 +462,7 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string, tomb
|
||||
if book.Imprint.Valid && book.Imprint.String != "" {
|
||||
<div>
|
||||
<p class="text-xs uppercase tracking-wide" style="color: var(--text-secondary)">Imprint</p>
|
||||
<p style="color: var(--text-primary)">{ book.Imprint.String }</p>
|
||||
<p style="color: var(--text-primary)">@FacetLink("imprint", book.Imprint.String, book.Imprint.String)</p>
|
||||
</div>
|
||||
}
|
||||
if book.CopyrightYear.Valid && book.CopyrightYear.Int32 > 0 {
|
||||
|
||||
Reference in new issue
Block a user