package templates
import (
"fmt"
"html"
"regexp"
"bookhoard/internal/handlers"
)
// CompletedProgressThreshold is the viewer_percentage at which a card
// shows the completed check instead of a partial-progress bar ā the
// SAME 0.999 cut the filter_query.go status arms and the series-page
// status filter use.
const CompletedProgressThreshold = 0.999
// progressFillCSS renders the partial-progress bar width (gotcha #8:
// quote-free style values only ā templ double-escapes quotes into
// literal text the CSS parser never sees).
func progressFillCSS(pct float64) string {
return fmt.Sprintf("width: %.1f%%; background-color: var(--accent);", pct*100)
}
// highlightQueryHTML escapes text FIRST and only then wraps
// case-insensitive query matches in ā the returned HTML is
// safe by construction (escaped payload, marker tags we wrote), which
// is what lets the search partial emit it via templ.Raw.
func highlightQueryHTML(text, query string) string {
if text == "" || query == "" {
return html.EscapeString(text)
}
pattern := regexp.MustCompile(regexp.QuoteMeta(html.EscapeString(query)))
markOpen := ``
return pattern.ReplaceAllString(html.EscapeString(text), markOpen+"$0")
}
// searchCoverURL falls back to the placeholder the way BookCard does ā
// no inline onerror (CSP: hash-mode refuses native handler attributes).
func searchCoverURL(item handlers.BookInfo) string {
if item.CoverImagePath != "" {
return item.CoverImagePath
}
return "/static/placeholder-book.svg"
}
// SearchResults is the header-search dropdown partial: compact result
// rows swapped into #search-results by the htmx request the header
// input fires (layout=dropdown on /api/media-items/search). Content is
// server-rendered (rendering-ownership doctrine); web/src/search.ts
// only owns open/close/keyboard state.
templ SearchResults(items []handlers.BookInfo, query string) {
{ resultCountLabel(len(items), query) }
if len(items) == 0 {
š
No results found
Try different keywords
} else {
for _, item := range items {
@SearchResultRow(item, query)
}
}
// SearchProgressOverlay is the cover overlay shared by the dropdown
// rows and BookCard: a completed check at/above the 0.999 threshold,
// a partial fill bar along the cover bottom while mid-read, nothing
// when the viewer never opened the book.
templ SearchProgressOverlay(item handlers.BookInfo) {
if item.ViewerPercentage != nil {
if *item.ViewerPercentage >= CompletedProgressThreshold {
@Icon("check", "h-3 w-3")
} else if *item.ViewerPercentage > 0 {
}
}
}
func resultCountLabel(n int, query string) string {
if n == 1 {
return fmt.Sprintf("1 result for ā%sā", query)
}
return fmt.Sprintf("%d results for ā%sā", n, query)
}