fix(filters): preserve every value from <select multiple> filter strips
Symptom (m-reported): /calendar filters don't work.
Root cause: ParseTreeFilter and calendar's ?kind parser both used
`r.URL.Query().Get(key)` to read tag/mgmt/has/status/kind. `Get()`
returns ONLY the first value when a URL has the same key repeated, and
the HTMX filter-strip forms (calendar_section.tmpl, timeline_section,
dashboard_section, graph, bulk) all use `<select multiple name="tag">`
which the browser serialises as `?tag=foo&tag=bar` — repeated params,
not the comma-joined `?tag=foo,bar` the tree page emits from its hidden
input. Every second-and-beyond chip silently dropped on every filter
submission across every page with a multi-select strip; m happened to
catch it on /calendar.
Fix (single helper, four call-site swaps):
- web/server.go parseValues(q, key): collects q[key] (the full slice of
values), joins on comma, runs parseCSV. Accepts both URL shapes:
?tag=foo,bar → ["foo", "bar"]
?tag=foo&tag=bar → ["foo", "bar"]
?tag=foo,bar&tag=baz → ["foo", "bar", "baz"]
- web/tree_filter.go ParseTreeFilter: tag / mgmt / status / has all
switch from `parseCSV(q.Get(...))` to `parseValues(q, ...)`. q / show-
archived / public stay on `q.Get` — they're single-value by design.
- web/calendar.go parseCalendarQuery: ?kind handling drops the bespoke
q.Get + strings.Split + dedup-map and uses `parseValues(..., "kind")`
for the same reason. Behaviour preserved for legacy comma-joined
`?kind=event,doc` AND new repeated-param submission.
Regression test:
- TestCalendarFilterMultiValueTagsFromForm seeds three items — one with
both test tags (A+B), one with only A, one with only B — drops a
dated link on each, then probes `/calendar?tag=A&tag=B`. Before the
fix the A-only note leaked through (the parser kept just tag=A);
after, only the A+B item appears per the AND-across-tags contract.
Full web suite green. Pre-existing db/TestBackfillTagsFromArea failure
unchanged (independent of this change).
Same fix transparently repairs /timeline, /dashboard, /graph, /bulk —
they all consume ParseTreeFilter and shared the bug.
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"log/slog"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -733,6 +734,23 @@ func parseTimelineExcludeList(raw []string) []string {
|
||||
// parseCSV splits a comma/space-delimited chip input into a deduplicated,
|
||||
// trimmed lowercase string slice. Empty input → []string{} (nil avoided so
|
||||
// JSON/SQL writes get an explicit empty array).
|
||||
// parseValues collects every value for `key` from a url.Values map and
|
||||
// splits each on the same comma/whitespace separators parseCSV accepts.
|
||||
// Handles both filter-strip styles:
|
||||
// - `?tag=foo,bar` (tree page hidden-input chip pattern)
|
||||
// - `?tag=foo&tag=bar` (HTMX multi-select form submission)
|
||||
//
|
||||
// Mixed shapes work too (`?tag=foo,bar&tag=baz` → [foo bar baz]).
|
||||
// Without this, `q.Get(key)` returned only the first value, so the
|
||||
// second tag/mgmt/has selection from any <select multiple> filter strip
|
||||
// silently dropped.
|
||||
func parseValues(q url.Values, key string) []string {
|
||||
if vs, ok := q[key]; ok {
|
||||
return parseCSV(strings.Join(vs, ","))
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func parseCSV(raw string) []string {
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
return []string{}
|
||||
|
||||
Reference in New Issue
Block a user