diff --git a/docs/contract_pins.md b/docs/contract_pins.md index 3ea1487b..e92431f2 100644 --- a/docs/contract_pins.md +++ b/docs/contract_pins.md @@ -180,3 +180,25 @@ cover_image_path, has_conflict, format_group, badge`. input leaves `genre` empty). Existing rows are unaffected until a rescan/re-extract. +## 2026-10-19 — Faceted navigation Batch B/C: chips, FacetLink, facets endpoint + +- `GET /api/media-items/facets?facet=X&library_id=...` is a NEW pinned + surface (registry-driven value+count listing for the filter panel): + 200 → `{facet, values[]: {value, display?, count}}` (display present + only for language codes → names); 400 for unknown facets and for + bool/range facets (they render as toggles, not lists); 403 for an + explicit library_id outside the requester's visible set. +- `GET /api/media-items/search` (JSON) and its HTMX grid partial now + share ONE param parser (`handlers.ParseMediaSearchParams`). Behavior + notes: the HTMX branch previously dropped `publisher_filter` and + `narrator_filter` silently — it now honors every facet param; and an + explicit hidden-library `library_id` is now a 403 on BOTH branches + (was: silent empty set on JSON). Response shapes are untouched; the + search row pin (47 keys) is unchanged. +- `language_filter` now normalizes comma elements through the ingest + language map, so display names ("English") match rows stored as ISO + codes ("en"). The param name is unchanged; single-code values behave + identically. +- Web: the bookshelf gains an active-filter chips bar (client-local + form state rendered by Alpine; templ owns the markup) and + `applyURLFilters` accepts the new facet params. diff --git a/templates/book_detail.templ b/templates/book_detail.templ index e3345ff4..b8eb65eb 100644 --- a/templates/book_detail.templ +++ b/templates/book_detail.templ @@ -83,7 +83,13 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string, tomb } if book.IsAudio() && len(book.Narrators) > 0 {

- Narrated by { strings.Join(book.Narrators, ", ") } + Narrated by + for i, narratorName := range book.Narrators { + if i > 0 { + , + } + @FacetLink("narrator", narratorName, narratorName) + }

} @@ -216,13 +222,13 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string, tomb { strings.ToUpper(book.ReadingDirection.String) } } if book.AgeRating.Valid && book.AgeRating.String != "" { - { book.AgeRating.String } + @FacetBadge("age_rating", book.AgeRating.String, book.AgeRating.String) } if book.IsBlackAndWhite.Valid && book.IsBlackAndWhite.Bool { B&W } if book.StoryArc.Valid && book.StoryArc.String != "" { - { book.StoryArc.String } + @FacetBadge("story_arc", book.StoryArc.String, book.StoryArc.String) } 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 != "" {

Publisher

-

{ book.Publisher.String }

+

@FacetLink("publisher", book.Publisher.String, book.Publisher.String)

} 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 != "" {

Language

-

{ book.Language.String }

+

@FacetLink("language", book.Language.String, languageDisplay(book.Language.String))

} if book.IsAudio() && len(book.Narrators) > 0 {

Narrator

-

{ strings.Join(book.Narrators, ", ") }

+

+ for i, narratorName := range book.Narrators { + if i > 0 { + , + } + @FacetLink("narrator", narratorName, narratorName) + } +

} 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 != "" {

Edition

-

{ book.Edition.String }

+

@FacetLink("edition", book.Edition.String, book.Edition.String)

} 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 != "" {

Genre

-

{ book.Genre.String }

+

@FacetLink("genre", book.Genre.String, prettifyGenre(book.Genre.String))

} 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 != "" {

Imprint

-

{ book.Imprint.String }

+

@FacetLink("imprint", book.Imprint.String, book.Imprint.String)

} if book.CopyrightYear.Valid && book.CopyrightYear.Int32 > 0 { diff --git a/templates/book_detail_templ.go b/templates/book_detail_templ.go index 7323ac04..240ad52c 100644 --- a/templates/book_detail_templ.go +++ b/templates/book_detail_templ.go @@ -338,34 +338,41 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var19 string - templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(strings.Join(book.Narrators, ", ")) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 86, Col: 57} + for i, narratorName := range book.Narrators { + if i > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, ", ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = FacetLink("narrator", narratorName, narratorName).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if book.ReadingProgress != nil && book.ReadingProgress.Percentage.Valid && book.ReadingProgress.Percentage.Float64 >= 1.0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } if book.ActiveConflict != nil || book.ReadingProgress != nil { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "Sync Progress ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if user.Role == "admin" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "Edit") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if book.CommunityRating.Valid && book.CommunityRating.Float64 > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "
Community: ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "
Community: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -550,119 +557,106 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var26 string - templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f / 10", book.CommunityRating.Float64)) + var templ_7745c5c3_Var25 string + templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f / 10", book.CommunityRating.Float64)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 195, Col: 84} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 201, Col: 84} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if book.Explicit { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "Explicit ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "Explicit ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } if book.Series.Valid && book.Series.String != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var27 string + templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(book.Series.String) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 215, Col: 30} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "\" class=\"chip hover:opacity-90\" style=\"background-color: var(--accent); color: var(--bg-primary); text-decoration: none;\">") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var28 string - templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(book.Series.String) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 209, Col: 30} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if book.SeriesNumber.Valid && book.SeriesNumber.Float64 > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "#") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "#") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var29 string - templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(fmtNumber(book.SeriesNumber.Float64)) + var templ_7745c5c3_Var28 string + templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(fmtNumber(book.SeriesNumber.Float64)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 211, Col: 50} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 217, Col: 50} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } if book.ReadingDirection.Valid && book.ReadingDirection.String != "" && book.ReadingDirection.String != "auto" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var30 string - templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(strings.ToUpper(book.ReadingDirection.String)) + var templ_7745c5c3_Var29 string + templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(strings.ToUpper(book.ReadingDirection.String)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 216, Col: 142} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 222, Col: 142} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } if book.AgeRating.Valid && book.AgeRating.String != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var31 string - templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(book.AgeRating.String) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 219, Col: 123} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, " ") + templ_7745c5c3_Err = FacetBadge("age_rating", book.AgeRating.String, book.AgeRating.String).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -674,99 +668,86 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs } } if book.StoryArc.Valid && book.StoryArc.String != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "") + templ_7745c5c3_Err = FacetBadge("story_arc", book.StoryArc.String, book.StoryArc.String).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(book.Tags) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, tag := range book.Tags { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var31 string + templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(tag) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 242, Col: 16} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if book.Review != nil && book.Review.Review != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var32 string - templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(book.StoryArc.String) + templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(book.Review.Review) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 225, Col: 122} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 255, Col: 61} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "

My Review

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if len(book.Tags) > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for _, tag := range book.Tags { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var34 string - templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(tag) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 236, Col: 16} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if book.Review != nil && book.Review.Review != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var35 string - templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(book.Review.Review) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 249, Col: 61} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "

My Review

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if book.Description.Valid && book.Description.String != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "

Synopsis

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "

Synopsis

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -774,17 +755,36 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if book.Summary.Valid && book.Summary.String != "" && book.Summary.String != book.Description.String { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "

Comic Summary

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = UnsafeHTML(bluemonday.UGCPolicy().Sanitize(book.Summary.String)).ToComponent().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - if book.Summary.Valid && book.Summary.String != "" && book.Summary.String != book.Description.String { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "

Comic Summary

") + if book.MetadataNotes.Valid && book.MetadataNotes.String != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "

Metadata Notes

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = UnsafeHTML(bluemonday.UGCPolicy().Sanitize(book.Summary.String)).ToComponent().Render(ctx, templ_7745c5c3_Buffer) + var templ_7745c5c3_Var33 string + templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(book.MetadataNotes.String) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 286, Col: 92} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -793,49 +793,30 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs return templ_7745c5c3_Err } } - if book.MetadataNotes.Valid && book.MetadataNotes.String != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "

Metadata Notes

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var36 string - templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(book.MetadataNotes.String) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 280, Col: 92} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if book.ReadingProgress != nil { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var37 string - templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(book.ProgressHeader()) + var templ_7745c5c3_Var34 string + templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(book.ProgressHeader()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 286, Col: 95} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 292, Col: 95} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if book.ActiveConflict != nil { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -843,121 +824,140 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "

Progress conflict detected — click \"Sync Progress\" to review and resolve.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "

Progress conflict detected — click \"Sync Progress\" to review and resolve.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "\">
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if book.IsAudio() { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "

Position

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "

Position

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var39 string - templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(book.AudioCurrentLabel() + " / " + book.AudioDurationLabel()) + var templ_7745c5c3_Var36 string + templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(book.AudioCurrentLabel() + " / " + book.AudioDurationLabel()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 305, Col: 144} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 311, Col: 144} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "

Progress

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "

Progress

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var40 string - templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f", book.ReadingProgress.Percentage.Float64*100)) + var templ_7745c5c3_Var37 string + templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f", book.ReadingProgress.Percentage.Float64*100)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 310, Col: 135} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 316, Col: 135} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "%

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "%

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } if book.ReadingProgress.CurrentPage.Valid && book.ReadingProgress.TotalPages.Valid { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "

Page

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "

Page

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var41 string - templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(book.ReadingProgress.CurrentPage.Int32) + var templ_7745c5c3_Var38 string + templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(book.ReadingProgress.CurrentPage.Int32) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 316, Col: 87} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 322, Col: 87} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, " / ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, " / ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var42 string - templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(book.ReadingProgress.TotalPages.Int32) + var templ_7745c5c3_Var39 string + templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(book.ReadingProgress.TotalPages.Int32) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 316, Col: 131} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 322, Col: 131} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } if book.ReadingProgress.LastReadAt.Valid { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var43 string - templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(book.LastReadLabel()) + var templ_7745c5c3_Var40 string + templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(book.LastReadLabel()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 321, Col: 111} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 327, Col: 111} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var44 string - templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(FormatTimestamptzInTimezone(book.ReadingProgress.LastReadAt, user.Timezone)) + var templ_7745c5c3_Var41 string + templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(FormatTimestamptzInTimezone(book.ReadingProgress.LastReadAt, user.Timezone)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 322, Col: 140} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 328, Col: 140} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if book.ReadingProgress.LastSyncSource.Valid { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "

Source

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var42 string + templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(book.ReadingProgress.LastSyncSource.String) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 334, Col: 110} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -966,76 +966,75 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs return templ_7745c5c3_Err } } - if book.ReadingProgress.LastSyncSource.Valid { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "

Source

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if book.BookStats != nil { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var43 string + templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(book.StatsHeader()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 343, Col: 92} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var44 string + templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(book.TimeStatLabel()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 346, Col: 110} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if book.BookStats.TotalMinutes >= 60 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var45 string - templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(book.ReadingProgress.LastSyncSource.String) + templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%dh %dmin", book.BookStats.TotalMinutes/60, book.BookStats.TotalMinutes%60)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 328, Col: 110} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 348, Col: 157} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if book.BookStats != nil { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var46 string - templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(book.StatsHeader()) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 337, Col: 92} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var47 string - templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(book.TimeStatLabel()) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 340, Col: 110} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if book.BookStats.TotalMinutes >= 60 { + } else { templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var48 string - templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%dh %dmin", book.BookStats.TotalMinutes/60, book.BookStats.TotalMinutes%60)) + var templ_7745c5c3_Var46 string + templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d min", book.BookStats.TotalMinutes)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 342, Col: 157} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 350, Col: 119} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1043,53 +1042,53 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var49 string - templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d min", book.BookStats.TotalMinutes)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 344, Col: 119} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "

Sessions

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "

Sessions

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var50 string - templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", book.BookStats.SessionCount)) + var templ_7745c5c3_Var47 string + templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", book.BookStats.SessionCount)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 349, Col: 114} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 355, Col: 114} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if book.IsAudio() { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "

Time listened

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "

Time listened

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var51 string - templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinStringErrs(book.TimeListenedLabel()) + var templ_7745c5c3_Var48 string + templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(book.TimeListenedLabel()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 354, Col: 106} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 360, Col: 106} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var51)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "

Pages Synced

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var49 string + templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", book.BookStats.TotalPages)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 365, Col: 113} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1097,60 +1096,56 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "

Pages Synced

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var52 string - templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", book.BookStats.TotalPages)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 359, Col: 113} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 124, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 125, "

Finished

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "

Finished

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if book.BookStats.Finished { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, "

✓

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 124, "

✓

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 127, "

—

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 125, "

—

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 128, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "

Metadata

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 127, "

Metadata

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if book.Publisher.Valid && book.Publisher.String != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 130, "

Publisher

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 128, "

Publisher

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var53 string - templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinStringErrs(book.Publisher.String) + templ_7745c5c3_Err = FacetLink("publisher", book.Publisher.String, book.Publisher.String).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 380, Col: 69} + return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53)) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if book.DatePublished.Valid { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 130, "

Published

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var50 string + templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(book.DatePublished.Time.Format("01-02-2006")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 392, Col: 92} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1159,17 +1154,17 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs return templ_7745c5c3_Err } } - if book.DatePublished.Valid { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 132, "

Published

") + if book.Isbn.Valid && book.Isbn.String != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 132, "

ISBN

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var54 string - templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(book.DatePublished.Time.Format("01-02-2006")) + var templ_7745c5c3_Var51 string + templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinStringErrs(book.Isbn.String) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 386, Col: 92} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 398, Col: 64} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var51)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1178,17 +1173,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs return templ_7745c5c3_Err } } - if book.Isbn.Valid && book.Isbn.String != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, "

ISBN

") + if book.Language.Valid && book.Language.String != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, "

Language

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var55 string - templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(book.Isbn.String) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 392, Col: 64} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55)) + templ_7745c5c3_Err = FacetLink("language", book.Language.String, languageDisplay(book.Language.String)).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1197,38 +1187,26 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs return templ_7745c5c3_Err } } - if book.Language.Valid && book.Language.String != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "

Language

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var56 string - templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(book.Language.String) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 398, Col: 87} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 137, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } if book.IsAudio() && len(book.Narrators) > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, "

Narrator

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "

Narrator

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var57 string - templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(strings.Join(book.Narrators, ", ")) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 404, Col: 82} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err + for i, narratorName := range book.Narrators { + if i > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 137, ", ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = FacetLink("narrator", narratorName, narratorName).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } } templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, "

") if templ_7745c5c3_Err != nil { @@ -1240,12 +1218,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var58 string - templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(book.AudioDurationLabel()) + var templ_7745c5c3_Var52 string + templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(book.AudioDurationLabel()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 410, Col: 94} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 423, Col: 94} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1265,12 +1243,7 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var59 string - templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(book.Edition.String) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 422, Col: 67} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59)) + templ_7745c5c3_Err = FacetLink("edition", book.Edition.String, book.Edition.String).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1284,12 +1257,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var60 string - templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.JoinStringErrs(book.PageCount.Int32) + var templ_7745c5c3_Var53 string + templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinStringErrs(book.PageCount.Int32) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 428, Col: 68} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 441, Col: 68} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var60)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1303,12 +1276,7 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var61 string - templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.JoinStringErrs(book.Genre.String) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 434, Col: 65} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var61)) + templ_7745c5c3_Err = FacetLink("genre", book.Genre.String, prettifyGenre(book.Genre.String)).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1322,12 +1290,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var62 string - templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(book.SeriesCount.Int32) + var templ_7745c5c3_Var54 string + templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(book.SeriesCount.Int32) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 440, Col: 70} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 453, Col: 70} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1341,12 +1309,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var63 string - templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(book.Volume.Int32) + var templ_7745c5c3_Var55 string + templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(book.Volume.Int32) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 446, Col: 70} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 459, Col: 70} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1360,12 +1328,7 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var64 string - templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.JoinStringErrs(book.Imprint.String) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 452, Col: 67} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var64)) + templ_7745c5c3_Err = FacetLink("imprint", book.Imprint.String, book.Imprint.String).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1379,12 +1342,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var65 string - templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(book.CopyrightYear.Int32) + var templ_7745c5c3_Var56 string + templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(book.CopyrightYear.Int32) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 458, Col: 72} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 471, Col: 72} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1398,12 +1361,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var66 string - templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.JoinStringErrs(strings.ReplaceAll(book.MangaType.String, "_", " ")) + var templ_7745c5c3_Var57 string + templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(strings.ReplaceAll(book.MangaType.String, "_", " ")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 464, Col: 118} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 477, Col: 118} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var66)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1417,12 +1380,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var67 string - templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(book.ScanInformation.String) + var templ_7745c5c3_Var58 string + templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(book.ScanInformation.String) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 470, Col: 94} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 483, Col: 94} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1436,12 +1399,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var68 string - templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinStringErrs(altSeries) + var templ_7745c5c3_Var59 string + templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(altSeries) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 476, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 489, Col: 57} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1455,12 +1418,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var69 string - templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(strings.Join(book.Contributors, ", ")) + var templ_7745c5c3_Var60 string + templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.JoinStringErrs(strings.Join(book.Contributors, ", ")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 482, Col: 85} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 495, Col: 85} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var69)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var60)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1473,12 +1436,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var70 string - templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinStringErrs(book.MimeType.String) + var templ_7745c5c3_Var61 string + templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.JoinStringErrs(book.MimeType.String) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 487, Col: 67} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 500, Col: 67} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var70)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var61)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1491,12 +1454,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var71 string - templ_7745c5c3_Var71, templ_7745c5c3_Err = templ.JoinStringErrs(formatFileSize(book.FileSize.Int64)) + var templ_7745c5c3_Var62 string + templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(formatFileSize(book.FileSize.Int64)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 492, Col: 82} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 505, Col: 82} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var71)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1510,12 +1473,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var72 string - templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.JoinStringErrs(book.FileLocation) + var templ_7745c5c3_Var63 string + templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(book.FileLocation) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 498, Col: 113} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 511, Col: 113} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var72)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1538,12 +1501,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var73 templ.SafeURL - templ_7745c5c3_Var73, templ_7745c5c3_Err = templ.JoinURLErrs(getExternalURL("goodreads", book.GoodreadsID.String, book.Isbn, book.Title, book.Author)) + var templ_7745c5c3_Var64 templ.SafeURL + templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.JoinURLErrs(getExternalURL("goodreads", book.GoodreadsID.String, book.Isbn, book.Title, book.Author)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 505, Col: 106} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 518, Col: 106} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var73)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var64)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1556,12 +1519,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var74 templ.SafeURL - templ_7745c5c3_Var74, templ_7745c5c3_Err = templ.JoinURLErrs(getExternalURL("goodreads", "", book.Isbn, book.Title, book.Author)) + var templ_7745c5c3_Var65 templ.SafeURL + templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinURLErrs(getExternalURL("goodreads", "", book.Isbn, book.Title, book.Author)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 507, Col: 85} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 520, Col: 85} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var74)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1575,12 +1538,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var75 templ.SafeURL - templ_7745c5c3_Var75, templ_7745c5c3_Err = templ.JoinURLErrs(getExternalURL("openlibrary", book.OpenlibraryID.String, book.Isbn, book.Title, book.Author)) + var templ_7745c5c3_Var66 templ.SafeURL + templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.JoinURLErrs(getExternalURL("openlibrary", book.OpenlibraryID.String, book.Isbn, book.Title, book.Author)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 510, Col: 110} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 523, Col: 110} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var75)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var66)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1593,12 +1556,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var76 templ.SafeURL - templ_7745c5c3_Var76, templ_7745c5c3_Err = templ.JoinURLErrs(getExternalURL("openlibrary", "", book.Isbn, book.Title, book.Author)) + var templ_7745c5c3_Var67 templ.SafeURL + templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinURLErrs(getExternalURL("openlibrary", "", book.Isbn, book.Title, book.Author)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 512, Col: 87} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 525, Col: 87} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var76)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1612,12 +1575,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var77 templ.SafeURL - templ_7745c5c3_Var77, templ_7745c5c3_Err = templ.JoinURLErrs(getExternalURL("googlebooks", book.GoogleBooksID.String, book.Isbn, book.Title, book.Author)) + var templ_7745c5c3_Var68 templ.SafeURL + templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinURLErrs(getExternalURL("googlebooks", book.GoogleBooksID.String, book.Isbn, book.Title, book.Author)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 515, Col: 110} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 528, Col: 110} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var77)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1630,12 +1593,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var78 templ.SafeURL - templ_7745c5c3_Var78, templ_7745c5c3_Err = templ.JoinURLErrs(getExternalURL("googlebooks", "", book.Isbn, book.Title, book.Author)) + var templ_7745c5c3_Var69 templ.SafeURL + templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinURLErrs(getExternalURL("googlebooks", "", book.Isbn, book.Title, book.Author)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 517, Col: 87} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 530, Col: 87} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var78)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var69)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1649,12 +1612,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var79 templ.SafeURL - templ_7745c5c3_Var79, templ_7745c5c3_Err = templ.JoinURLErrs(getExternalURL("amazon", book.Asin.String, book.Isbn, book.Title, book.Author)) + var templ_7745c5c3_Var70 templ.SafeURL + templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinURLErrs(getExternalURL("amazon", book.Asin.String, book.Isbn, book.Title, book.Author)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 520, Col: 96} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 533, Col: 96} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var79)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var70)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1667,12 +1630,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var80 templ.SafeURL - templ_7745c5c3_Var80, templ_7745c5c3_Err = templ.JoinURLErrs(getExternalURL("amazon", "", book.Isbn, book.Title, book.Author)) + var templ_7745c5c3_Var71 templ.SafeURL + templ_7745c5c3_Var71, templ_7745c5c3_Err = templ.JoinURLErrs(getExternalURL("amazon", "", book.Isbn, book.Title, book.Author)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 522, Col: 82} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 535, Col: 82} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var80)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var71)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1686,12 +1649,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var81 templ.SafeURL - templ_7745c5c3_Var81, templ_7745c5c3_Err = templ.JoinURLErrs(book.WebUrl.String) + var templ_7745c5c3_Var72 templ.SafeURL + templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.JoinURLErrs(book.WebUrl.String) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 525, Col: 36} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 538, Col: 36} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var81)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var72)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1699,12 +1662,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var82 string - templ_7745c5c3_Var82, templ_7745c5c3_Err = templ.JoinStringErrs(getDomainName(book.WebUrl.String)) + var templ_7745c5c3_Var73 string + templ_7745c5c3_Var73, templ_7745c5c3_Err = templ.JoinStringErrs(getDomainName(book.WebUrl.String)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 525, Col: 238} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 538, Col: 238} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var82)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var73)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1732,12 +1695,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var83 templ.SafeURL - templ_7745c5c3_Var83, templ_7745c5c3_Err = templ.JoinURLErrs("/collections/" + uuidToString(col.ID)) + var templ_7745c5c3_Var74 templ.SafeURL + templ_7745c5c3_Var74, templ_7745c5c3_Err = templ.JoinURLErrs("/collections/" + uuidToString(col.ID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 537, Col: 54} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 550, Col: 54} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var83)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var74)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1745,12 +1708,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var84 string - templ_7745c5c3_Var84, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("border-color: " + col.Color.String + "; background-color: var(--bg-primary); text-decoration: none;") + var templ_7745c5c3_Var75 string + templ_7745c5c3_Var75, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("border-color: " + col.Color.String + "; background-color: var(--bg-primary); text-decoration: none;") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 539, Col: 118} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 552, Col: 118} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var84)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var75)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1758,12 +1721,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var85 string - templ_7745c5c3_Var85, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("color: " + col.Color.String) + var templ_7745c5c3_Var76 string + templ_7745c5c3_Var76, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("color: " + col.Color.String) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 541, Col: 51} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 554, Col: 51} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var85)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var76)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1771,12 +1734,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var86 string - templ_7745c5c3_Var86, templ_7745c5c3_Err = templ.JoinStringErrs(col.Icon.String) + var templ_7745c5c3_Var77 string + templ_7745c5c3_Var77, templ_7745c5c3_Err = templ.JoinStringErrs(col.Icon.String) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 541, Col: 71} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 554, Col: 71} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var86)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var77)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1784,12 +1747,12 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombs if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var87 string - templ_7745c5c3_Var87, templ_7745c5c3_Err = templ.JoinStringErrs(col.Name) + var templ_7745c5c3_Var78 string + templ_7745c5c3_Var78, templ_7745c5c3_Err = templ.JoinStringErrs(col.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 542, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `book_detail.templ`, Line: 555, Col: 61} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var87)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var78)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/templates/bookshelf.templ b/templates/bookshelf.templ index e779e2a0..34b41117 100644 --- a/templates/bookshelf.templ +++ b/templates/bookshelf.templ @@ -286,6 +286,28 @@ templ BookShelf(
+ +
+ +
@BooksGrid(books, limit, offset, count, currentLibraryID) if errorMessage != "" { diff --git a/templates/bookshelf_templ.go b/templates/bookshelf_templ.go index 1378deed..65ebcfab 100644 --- a/templates/bookshelf_templ.go +++ b/templates/bookshelf_templ.go @@ -406,7 +406,7 @@ func BookShelf( } } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "
= 2) fetchAuthorValues($el)\">
= 2) fetchTagValues($el)\" @keydown=\"onTagFilterKeydown($event)\" @blur=\"hideTagDropdown()\">
= 2) fetchSeriesValues($el)\">
= 2) fetchNarratorValues($el)\">
= 2) fetchLanguageValues($el)\">
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "
= 2) fetchAuthorValues($el)\">
= 2) fetchTagValues($el)\" @keydown=\"onTagFilterKeydown($event)\" @blur=\"hideTagDropdown()\">
= 2) fetchSeriesValues($el)\">
= 2) fetchNarratorValues($el)\">
= 2) fetchLanguageValues($el)\">
0\" x-cloak>
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -422,7 +422,7 @@ func BookShelf( var templ_7745c5c3_Var20 string templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(errorMessage) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `bookshelf.templ`, Line: 293, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `bookshelf.templ`, Line: 315, Col: 59} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) if templ_7745c5c3_Err != nil { @@ -445,7 +445,7 @@ func BookShelf( var templ_7745c5c3_Var21 string templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("/api/media-items/search?library_id=%s&limit=%d&offset=%d", currentLibraryID, limit, offset-limit)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `bookshelf.templ`, Line: 302, Col: 126} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `bookshelf.templ`, Line: 324, Col: 126} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var21) if templ_7745c5c3_Err != nil { @@ -476,7 +476,7 @@ func BookShelf( var templ_7745c5c3_Var22 string templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(offset/limit + 1) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `bookshelf.templ`, Line: 311, Col: 32} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `bookshelf.templ`, Line: 333, Col: 32} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) if templ_7745c5c3_Err != nil { @@ -489,7 +489,7 @@ func BookShelf( var templ_7745c5c3_Var23 string templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("/api/media-items/search?library_id=%s&limit=%d&offset=%d", currentLibraryID, limit, offset+limit)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `bookshelf.templ`, Line: 316, Col: 126} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `bookshelf.templ`, Line: 338, Col: 126} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var23) if templ_7745c5c3_Err != nil { diff --git a/templates/facet_links.templ b/templates/facet_links.templ new file mode 100644 index 00000000..40956d21 --- /dev/null +++ b/templates/facet_links.templ @@ -0,0 +1,37 @@ +package templates + +import ( + "bookhoard/internal/services" + "net/url" +) + +// Facet navigation helpers (faceted-navigation plan, Batch B). +// +// "Everything filterable should be clickable": every place a facet value +// is DISPLAYED links to the bookshelf pre-filtered by that value. The +// raw (stored) value rides the param — for genre that is the canonical +// lowercase form, while the shown text is prettified — so matching and +// display can never disagree. Series and tags keep their dedicated +// pages (/series/detail, /tags/detail) by design and do NOT use these +// helpers; author links predate them and stay as-is. + +// FacetLink renders a facet value as an inline text link (metadata +// grid cells, the narrators line) to /bookshelf?_filter=. +templ FacetLink(facet string, value string, display string) { + { display } +} + +// FacetBadge is the badge-styled FacetLink (the detail page's +// age-rating and story-arc badges). +templ FacetBadge(facet string, value string, display string) { + { display } +} + +// languageDisplay renders a stored language value for display: +// "en" → "English"; unknown/legacy values pass through verbatim. +func languageDisplay(value string) string { + if name := services.LanguageDisplayName(value); name != "" { + return name + } + return value +} diff --git a/templates/facet_links_templ.go b/templates/facet_links_templ.go new file mode 100644 index 00000000..20db0df0 --- /dev/null +++ b/templates/facet_links_templ.go @@ -0,0 +1,149 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1020 +package templates + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "bookhoard/internal/services" + "net/url" +) + +// Facet navigation helpers (faceted-navigation plan, Batch B). +// +// "Everything filterable should be clickable": every place a facet value +// is DISPLAYED links to the bookshelf pre-filtered by that value. The +// raw (stored) value rides the param — for genre that is the canonical +// lowercase form, while the shown text is prettified — so matching and +// display can never disagree. Series and tags keep their dedicated +// pages (/series/detail, /tags/detail) by design and do NOT use these +// helpers; author links predate them and stay as-is. + +// FacetLink renders a facet value as an inline text link (metadata +// grid cells, the narrators line) to /bookshelf?_filter=. +func FacetLink(facet string, value string, display string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(display) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `facet_links.templ`, Line: 21, Col: 142} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// FacetBadge is the badge-styled FacetLink (the detail page's +// age-rating and story-arc badges). +func FacetBadge(facet string, value string, display string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var4 := templ.GetChildren(ctx) + if templ_7745c5c3_Var4 == nil { + templ_7745c5c3_Var4 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(display) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `facet_links.templ`, Line: 27, Col: 226} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// languageDisplay renders a stored language value for display: +// "en" → "English"; unknown/legacy values pass through verbatim. +func languageDisplay(value string) string { + if name := services.LanguageDisplayName(value); name != "" { + return name + } + return value +} + +var _ = templruntime.GeneratedTemplate diff --git a/templates/utils.go b/templates/utils.go index f362ea04..b0d83304 100644 --- a/templates/utils.go +++ b/templates/utils.go @@ -9,6 +9,7 @@ import ( "net/url" "sort" "strings" + "unicode" "time" "github.com/google/uuid" @@ -255,6 +256,41 @@ func conflictID(c *handlers.ConflictDetailResponse) string { return c.ID } +// genreSmallWords stay lowercase in title-case rendering (except the +// first word of the value). +var genreSmallWords = map[string]bool{ + "a": true, "an": true, "and": true, "of": true, "the": true, + "in": true, "on": true, "for": true, "to": true, "or": true, +} + +// prettifyGenre renders the canonically-lowercase genre value in title +// case at display time (Batch B transform — never in the DB). Only +// values that are ENTIRELY lowercase get transformed; anything else is +// returned verbatim, protecting user-entered mixed case. Small words +// stay down except in the first position; "&" has no case and passes +// through. "science fiction" → "Science Fiction", "sword & sorcery" → +// "Sword & Sorcery". +func prettifyGenre(value string) string { + trimmed := strings.TrimSpace(value) + if trimmed == "" { + return "" + } + if trimmed != strings.ToLower(trimmed) { + return trimmed // mixed case = user-authored; display as written + } + words := strings.Fields(trimmed) + out := make([]string, len(words)) + for i, w := range words { + if i > 0 && genreSmallWords[w] { + out[i] = w + continue + } + runes := []rune(w) + out[i] = string(append([]rune{unicode.ToUpper(runes[0])}, runes[1:]...)) + } + return strings.Join(out, " ") +} + // getAlternateSeries extracts the alternate series name from JSONB data func getAlternateSeries(data []byte) string { if len(data) == 0 { diff --git a/templates/utils_test.go b/templates/utils_test.go index 0bb2e74f..5ab81dd9 100644 --- a/templates/utils_test.go +++ b/templates/utils_test.go @@ -95,3 +95,27 @@ func TestLibraryTypeDisplayName(t *testing.T) { } } } + +func TestPrettifyGenre(t *testing.T) { + cases := []struct{ in, want string }{ + {"science fiction", "Science Fiction"}, + {"sword & sorcery", "Sword & Sorcery"}, + {"action & adventure", "Action & Adventure"}, + {"war & military", "War & Military"}, + {"historical fiction", "Historical Fiction"}, + {"children's", "Children's"}, + {"middle grade", "Middle Grade"}, + {"fiction", "Fiction"}, + // Small words stay down except in first position. + {"towers of midnight", "Towers of Midnight"}, + // Mixed case = user-authored; verbatim. + {"Science Fiction", "Science Fiction"}, + {"HardSF", "HardSF"}, + {"", ""}, + } + for _, c := range cases { + if got := prettifyGenre(c.in); got != c.want { + t.Errorf("prettifyGenre(%q) = %q, want %q", c.in, got, c.want) + } + } +} diff --git a/web/src/bookshelf.ts b/web/src/bookshelf.ts index cda32f9e..91863c3d 100644 --- a/web/src/bookshelf.ts +++ b/web/src/bookshelf.ts @@ -87,6 +87,9 @@ Alpine.data("bookshelf", () => ({ tagSuggestions: [] as TagSuggestion[], showTagDropdown: false, tagHighlightIndex: -1, + // Active-filter chips (Batch B): Alpine renders them from this state; + // refreshed by refreshFilterChips after every grid refetch. + activeFilters: [] as FilterChip[], // Standalone function references (don't access component state) clearFilters, @@ -96,6 +99,32 @@ Alpine.data("bookshelf", () => ({ showSaveFilterModal() { this.showSaveModal = true; }, + // Remove one active-filter chip: clear that facet's field (hidden + // inputs are dropped outright) and re-apply the form. Chips refresh + // via the shared htmx:afterRequest hook. + removeFilterChip(param: string): void { + const filterForm = document.getElementById( + "filter-form", + ) as HTMLFormElement; + if (!filterForm) return; + if (param === "has_cover") { + filterForm.querySelector('[name="has_cover"]')?.remove(); + this.hasCoverState = null; + window.htmx.trigger(filterForm, "submit"); + return; + } + const input = filterForm.querySelector( + `[name="${param}"]`, + ) as HTMLInputElement | null; + if (input) { + if (input.type === "hidden") { + input.remove(); + } else { + input.value = ""; + } + } + window.htmx.trigger(filterForm, "submit"); + }, // Helper method to calculate alignment calculateAlignment() { const button = document.querySelector("[data-load-filter-btn]"); @@ -459,6 +488,9 @@ Alpine.data("bookshelf", () => ({ // A2c §5.3: deep-linkable filters. Reads known filter params from the URL // into the form inputs; returns true when at least one was applied. A // comma-joined value is a multi-select (the server splits it per element). +// Facet names mirror services/facet_registry.go; params without a visible +// drawer input (Batch A facets, bool facets) land as hidden inputs like +// loadFilter's, so chips and clearing work for every deep link. function applyURLFilters(filterForm: HTMLFormElement | null): boolean { if (!filterForm) return false; const params = new URLSearchParams(window.location.search); @@ -470,6 +502,17 @@ function applyURLFilters(filterForm: HTMLFormElement | null): boolean { "tags_filter", "narrator_filter", "language_filter", + "story_arc_filter", + "imprint_filter", + "edition_filter", + "age_rating_filter", + "format_group_filter", + "manga_type_filter", + "reading_direction_filter", + "explicit_filter", + "abridged_filter", + "year_min", + "year_max", ]; let applied = false; for (const name of filterNames) { @@ -481,16 +524,152 @@ function applyURLFilters(filterForm: HTMLFormElement | null): boolean { if (input) { input.value = value; applied = true; + } else if (name !== "has_cover") { + // No visible drawer input (Batch C's panel will manage these + // facets): hold the value in a hidden input so the form submit + // carries it and a chip can offer removal. + const hidden = document.createElement("input"); + hidden.type = "hidden"; + hidden.name = name; + hidden.value = value; + filterForm.appendChild(hidden); + applied = true; } } return applied; } +// Active-filter chips (Batch B): a pure function of the filter form's +// client state — Alpine renders them from `activeFilters`, templ owns +// the markup. Removing a chip clears that facet and re-applies the form. +interface FilterChip { + param: string; + label: string; +} + +// prettifyGenreLabel ports templates.prettifyGenre (Batch B transform): +// only entirely-lowercase values are title-cased; small words stay down +// except in first position; anything else displays verbatim. +function prettifyGenreLabel(value: string): string { + const trimmed = value.trim(); + if (!trimmed || trimmed !== trimmed.toLowerCase()) return trimmed; + const small = new Set([ + "a", "an", "and", "of", "the", "in", "on", "for", "to", "or", + ]); + return trimmed + .split(/\s+/) + .map((w, i) => + i > 0 && small.has(w) ? w : w.charAt(0).toUpperCase() + w.slice(1), + ) + .join(" "); +} + +// facetChipLabel builds one chip's shown text. +function facetChipLabel(name: string, value: string): string { + if (name === "has_cover") { + return value === "true" ? "Has cover" : "No cover"; + } + const names: Record = { + author_filter: "Author", + publisher_filter: "Publisher", + narrator_filter: "Narrator", + tags_filter: "Tags", + series_filter: "Series", + genre_filter: "Genre", + language_filter: "Language", + year_min: "From year", + year_max: "To year", + story_arc_filter: "Story arc", + imprint_filter: "Imprint", + edition_filter: "Edition", + age_rating_filter: "Age rating", + format_group_filter: "Format", + manga_type_filter: "Manga type", + reading_direction_filter: "Reading direction", + explicit_filter: "Explicit", + abridged_filter: "Abridged", + }; + const label = names[name] ?? name; + const shown = + name === "genre_filter" ? prettifyGenreLabel(value) : value; + return `${label}: ${shown}`; +} + +// refreshFilterChips rebuilds `activeFilters` from the form and syncs +// the shareable URL (history.replaceState) — the single sync point for +// form applies, URL deep-links, sort changes, and clears. +function refreshFilterChips(): void { + const filterForm = document.getElementById( + "filter-form", + ) as HTMLFormElement; + const bookshelfElem = document.querySelector('[x-data*="bookshelf"]'); + if (!filterForm || !bookshelfElem || !(bookshelfElem as any)._x_dataStack) { + return; + } + const data = (bookshelfElem as any)._x_dataStack[0]; + if (typeof data.activeFilters === "undefined") return; + + const chips: FilterChip[] = []; + filterForm + .querySelectorAll( + '[name$="_filter"], [name="year_min"], [name="year_max"]', + ) + .forEach((input) => { + if (input.value) { + chips.push({ + param: input.name, + label: facetChipLabel(input.name, input.value), + }); + } + }); + const hasCoverInput = filterForm.querySelector( + '[name="has_cover"]', + ) as HTMLInputElement | null; + if (hasCoverInput && hasCoverInput.value) { + chips.push({ param: "has_cover", label: facetChipLabel("has_cover", hasCoverInput.value) }); + } + + data.activeFilters = chips; + + // URL stays in sync with the applied set — the filtered view remains + // shareable (plan: chips bar is URL-synced). + const url = new URL(window.location.href); + const keep = new Set([ + "author_filter", "series_filter", "genre_filter", "publisher_filter", + "tags_filter", "narrator_filter", "language_filter", "story_arc_filter", + "imprint_filter", "edition_filter", "age_rating_filter", + "format_group_filter", "manga_type_filter", "reading_direction_filter", + "explicit_filter", "abridged_filter", "year_min", "year_max", + "has_cover", "library_id", + ]); + const next = new URLSearchParams(); + url.searchParams.forEach((value, key) => { + if (keep.has(key)) next.set(key, value); + }); + if (chips.length === 0 && !next.get("library_id")) { + window.history.replaceState(null, "", window.location.pathname); + return; + } + const qs = next.toString(); + window.history.replaceState( + null, + "", + qs ? `${window.location.pathname}?${qs}` : window.location.pathname, + ); +} + function initBookshelf(): void { const filterForm = document.getElementById( "filter-form", ) as HTMLFormElement; + // Active-filter chips refresh after every grid refetch — drawer + // applies, sort changes, and pagination all include the form's + // filters, so one bubbled htmx:afterRequest hook is the sync point. + filterForm.addEventListener("htmx:afterRequest", () => + refreshFilterChips(), + ); + // A2c §5.3: deep-linkable filters — /bookshelf?author_filter=Sapkowski // pre-fills the form and applies it to the first grid load. Author // links on the detail page (and anything else) can share filtered