package templates import ( "fmt" "bookhoard/internal/database" "bookhoard/internal/handlers" ) // pluralizeBooks renders "book"/"books" for count displays. func pluralizeBooks(n int) string { if n == 1 { return "book" } return "books" } // firstPlayableEpisodeURL returns the reader URL that starts the // collection sequence from the first downloaded episode (plan item 2.3 // "Play collection as sequence" — the queue param makes the reader // build the collection chain), or "" when nothing can play. func firstPlayableEpisodeURL(collectionID string, books []handlers.BookInfo) string { for _, b := range books { if b.IsEpisode && !b.Hidden && b.DownloadState == "downloaded" { return "/readers/" + b.MediaItemID + "?queue=collection:" + collectionID } } return "" } // PlayCollectionButton renders the header's sequence-start button when // the collection holds at least one downloaded episode — alongside the // collection's CURATED listening order select (plan §10 R2: the chain // order lives on the collection, honored by the chain producer; the // player shows no order control for collection sources). templ PlayCollectionButton(collectionID string, chainOrder string, books []handlers.BookInfo) { if url := firstPlayableEpisodeURL(collectionID, books); url != "" {
} } // EpisodePickerGrid renders the episode scope of the collection picker // (plan item 2.3): YOUR subscriptions' episodes (RLS scopes), compact // rows with the shared bookPicker selection store. Checksum ✓ overlay // mirrors BookPickerGrid so selection survives htmx swaps. templ EpisodePickerGrid(eps []database.SearchEpisodesForPickerRow) { for _, ep := range eps {{ ep.Title }
{ ep.FeedTitle }
No episodes from your subscriptions match.
} } // CollectionBooksGrid renders the content of #books-container: the // collection's book cards, or the empty state. Shared by the collection // detail page and the /collections/:id/books-grid partial route (the // library switcher refreshes the grid through it — rendering ownership: // templ renders, JS only swaps). templ CollectionBooksGrid(collection CollectionData, books []handlers.BookInfo) { if len(books) == 0 {Not subscribed (or not visible to you) — the feed's episodes leave the queue until you follow it again.
🎧 { book.FeedTitle }
if book.DownloadState == "downloaded" { ▶ Play } else { Not downloaded }by { book.Author }
}Organize your books into custom collections
Create collections to organize your books
{ col.Description }
{ fmt.Sprintf("%d %s", col.BookCount, pluralizeBooks(col.BookCount)) } if col.IsSmart { · rules-matched live }
{ collection.Description }
if collection.IsSmart {Membership is rules-matched live — always current, nothing to refresh.
}{ book.Title }