feat(filters+ui): per-element author/publisher filters, playlist toggles, Up Next API, reorder UI, dashboard badges
Phases 4 + 5 of the metadata overhaul (plan §4, §5) plus §3 handler wiring. Filters (§4.1): author_filter/publisher_filter now match per-element against the authors[]/publishers[] arrays — a co-authored book matches any one of its authors — with comma-joined params acting as OR-within-facet multi-select (string_to_array + cross unnest). Legacy single-value params unchanged on the wire; series/genre/language filters stay on their varchar columns; narrator filter already per-element. Collections API (§3.3-3.5): collection detail carries viewer + hide_finished into the listing query and exposes up_next (visible- library-enforced reading-queue head); PUT /api/collections/:id/items/order (owner-only, full-order rewrite); UpdateCollectionRequest gains hide_finished/show_up_next ([] string last-value-wins for the hidden-false-sibling checkbox pattern) merged server-side OVER stored view_settings so imported_from_series provenance survives edits; EditCollectionModal renders the toggles from view_settings. Dashboard (§5.5): sections JSON carries is_playlist/complete (dashboard.ts is the AGENTS-documented JSON-render exception; first paint stays templ). Fully-read playlists render '✓ Playlist complete' instead of an empty rail; imported playlists get a 📖 Playlist badge. Frontend: book detail shows subtitle + full authors + Explicit badge; edit modal gains subtitle, comma-separated authors/publishers (client- split to arrays exactly like narrators), step=any series number, explicit checkbox; collection book cards get playlist move ↑/↓ buttons persisting the full order via the reorder endpoint (templ partial re-render on failure — DOM order is client-local, API order is truth). AGENTS.md gotcha #3 amended per plan §3.7: user collections are dashboard sections by default; the show_on_dashboard premise is retired. Verified: full affected integration sweep green (pins widened for up_next/is_playlist/complete — changelog entries in docs/contract_pins.md); unit suite green; go build + templ + vite green.
This commit is contained in:
1 parent
1dbb0ce406
commit
f94e7472b4
20 files changed
+1711
-1339
No files matched your search
@@ -52,6 +52,12 @@ type UpdateCollectionRequest struct {
|
||||
Icon string `form:"icon" json:"icon"`
|
||||
AutoAssignRules []services.Rule `form:"auto_assign_rules" json:"auto_assign_rules"`
|
||||
ViewSettings map[string]interface{} `form:"view_settings" json:"view_settings"`
|
||||
// A2c §3.3: playlist toggles — merged into view_settings server-side
|
||||
// (nil = untouched). []string because the form posts a hidden "false"
|
||||
// sibling next to the checkbox; the LAST value wins. JSON clients may
|
||||
// send a single-element array (or omit — untouched).
|
||||
HideFinished []string `form:"hide_finished" json:"hide_finished"`
|
||||
ShowUpNext []string `form:"show_up_next" json:"show_up_next"`
|
||||
}
|
||||
|
||||
type AddBooksRequest struct {
|
||||
@@ -404,6 +410,23 @@ func (h *CollectionHandler) UpdateCollection(c *echo.Context) error {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
// A2c §3.3: playlist toggles ride the edit form as top-level fields and
|
||||
// merge into view_settings server-side (the form can't bind a map).
|
||||
// The form posts a hidden "false" sibling before the checkbox, so the
|
||||
// LAST value is the checkbox state.
|
||||
if v := optionalFlag(req.HideFinished); v != nil {
|
||||
if req.ViewSettings == nil {
|
||||
req.ViewSettings = map[string]interface{}{}
|
||||
}
|
||||
req.ViewSettings["hide_finished"] = *v
|
||||
}
|
||||
if v := optionalFlag(req.ShowUpNext); v != nil {
|
||||
if req.ViewSettings == nil {
|
||||
req.ViewSettings = map[string]interface{}{}
|
||||
}
|
||||
req.ViewSettings["show_up_next"] = *v
|
||||
}
|
||||
|
||||
collection, err := h.collectionService.UpdateCollection(
|
||||
c.Request().Context(),
|
||||
collectionID,
|
||||
@@ -1336,3 +1359,13 @@ func (h *CollectionHandler) ReorderItems(c *echo.Context) error {
|
||||
}
|
||||
return c.JSON(http.StatusOK, map[string]string{"status": "reordered"})
|
||||
}
|
||||
|
||||
// optionalFlag interprets a multi-valued form flag: last value wins
|
||||
// (hidden-false sibling precedes the checkbox), absent → nil (untouched).
|
||||
func optionalFlag(values []string) *bool {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
v := strings.EqualFold(values[len(values)-1], "true")
|
||||
return &v
|
||||
}
|
||||
Reference in new issue
Block a user