feat(phase 3b filtering): full tree-page filter bar (search + chips + counts + HTMX swap)
Tree page (/) gains every navigation dimension m asked for: - Debounced search input matching title/slug/aliases/content_md/paths case-insensitively (?q=…) - Tag chip row (?tag=a,b — AND within tags, as before) - Management chip row with ?mgmt=mai,self,external,unmanaged (OR within management; "unmanaged" is the synthetic empty-array case) - Status chip row with ?status=active,done,archived (default = active; archived rows only surface when the separate show-archived toggle is on) - Has-link chip row ?has=caldav-list,gitea-repo - Each chip carries the count it would yield if toggled — honest user cue, computed via per-dimension recomputation in pure Go (cheap at m's scale) - URL is the source of truth — every filter goes through the query string, so any view is bookmarkable; HTMX swaps the tree-section in place with hx-push-url=true on every chip click and on search keyup - Empty-state copy with a clear-all link Implementation: - web/tree_filter.go new: TreeFilter struct + ParseTreeFilter + QueryString/URL + Toggle* helpers + Matches + applyTreeFilter (replacement for buildForest) + computeChipCounts. - web/tree_filter_test.go: parse defaults + every dimension's match + URL round-trip + ancestor-keep semantics + chip counting. - Linkages: linkKindsByItem on Server fans across the two has-link ref_types in one pass and feeds the filter. - tree.tmpl reduced to a one-liner that calls tree-section; new tree_section partial powers both the initial page render and HTMX fragment swaps (matches the pattern from phases 2.a/b/d). docs/design.md §4: tree-filter contract — URL keys, AND/OR rules, count semantics, archived ergonomics.
This commit is contained in:
@@ -106,3 +106,24 @@ table.classify input, table.classify select { width: 100%; }
|
||||
.issues li.issue-row .milestone { font-size: 0.72em; padding: 1px 6px; border-radius: 4px; background: #fff; border: 1px solid var(--border); color: var(--warn); }
|
||||
.issues li.issue-row .assignee { font-size: 0.78em; color: var(--muted); }
|
||||
.issues ul.closed .title { color: var(--muted); }
|
||||
|
||||
/* Tree-page filter bar (phase 3b). */
|
||||
.tree-section { display: block; }
|
||||
#tree-filterbar { padding: 12px 0; border-bottom: 1px dotted var(--border); margin-bottom: 12px; }
|
||||
#tree-filterbar .search { margin: 0 0 8px; display: flex; }
|
||||
#tree-filterbar .search input[type="search"] {
|
||||
width: 100%; font: inherit; padding: 6px 10px;
|
||||
border: 1px solid var(--border); background: #fff; border-radius: 4px;
|
||||
}
|
||||
#tree-filterbar .chip-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin: 4px 0; }
|
||||
#tree-filterbar .chip-row .muted { width: 4em; flex-shrink: 0; }
|
||||
.mgmt-chip, .status-chip, .has-chip {
|
||||
display: inline-block; font-size: 0.78em; padding: 1px 8px; border-radius: 999px;
|
||||
background: #fff; border: 1px solid var(--border); color: var(--muted); text-decoration: none;
|
||||
}
|
||||
.mgmt-chip:hover, .status-chip:hover, .has-chip:hover { color: var(--fg); border-color: var(--accent); }
|
||||
.chip-on { background: var(--accent); color: #fff; border-color: var(--accent); }
|
||||
.chip-on:hover { color: #fff; filter: brightness(0.92); }
|
||||
#tree-filterbar small { opacity: 0.75; margin-left: 2px; }
|
||||
.tree-section .empty { padding: 24px; color: var(--muted); }
|
||||
.tree-section .clear { color: var(--bad); }
|
||||
|
||||
Reference in New Issue
Block a user