Files
projax/web/templates/tree_section.tmpl
mAi 13923aadb6 feat(views): Phase 5i slice A — project filter dim + descendants toggle
m's Q5 pick (2026-05-26): project scope on every Views-supporting page,
with descendants exposed as an explicit on/off chip toggle rather than
always-on. Slice A ships the smallest standalone piece of the Views
system; slices B–E (view_type URL param, kanban, saved-views schema,
defaults) follow on the same branch.

TreeFilter grows two fields:
- ProjectPath: scoped item's primary path; "" = no filter.
- IncludeDescendants: default true; flipped via ?project_descendants=0.

Matching extends to path-prefix across `it.Paths` when ProjectPath is
set; equality-only when IncludeDescendants is off. Multi-parent items
pass when ANY of their paths qualifies.

Picker is a shared partial (templates/project_chip.tmpl) that every
Views-supporting filter strip includes (tree, dashboard, timeline,
calendar). Two states: <select> picker when no project is set; active
chip with × clear + descendants on/off chip when scoped. Hidden
inputs added to each form so non-picker chip clicks preserve the
project state. Graph and admin tools are NOT Views consumers (per
design.md / docs/plans/views-system.md §5) and stay untouched.

Test-source edits (per the 5c sharpened rule):
- dashboard_test.go, public_listing_test.go, timeline_test.go: row
  membership assertions tightened from `Contains(body, slug)` to
  `Contains(body, href="/i/path")`. The picker now renders every
  item's primary path inside a <select>, so coarse slug substring
  matches falsely passed across filtered-out picker options. Behaviour
  preserved (filtered rows still don't render); the impl-detail
  assertion moved to the row link.

New tests: TestProjectFilterIncludesDescendants,
TestProjectFilterDescendantsOff, TestParseTreeFilterProjectFields,
TestTreeFilterProjectRoundTrip, TestSetProjectAndToggleHelpers,
TestProjectFilterScopesTreeToDescendants (end-to-end via /).
2026-05-26 13:27:37 +02:00

105 lines
4.7 KiB
Cheetah
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{{define "tree-section"}}
<section id="tree-section" class="tree-section">
<p class="counts">
<strong>{{.Matched}}</strong> / <strong>{{.Total}}</strong> items match
{{if .OrphanN}} · <strong>{{.OrphanN}}</strong> unclassified mai-managed roots <a href="/admin/classify">→ classify</a>{{end}}
{{if .Filter.Active}} · <a class="clear" href="/">clear filters</a>{{end}}
</p>
<section class="tagbar" id="tree-filterbar">
<form class="search" hx-get="/" hx-target="#tree-section" hx-swap="outerHTML"
hx-trigger="keyup changed delay:200ms from:input[name=q], change from:input[type=hidden]"
hx-push-url="true">
<input type="search" name="q" value="{{.Filter.Q}}" placeholder="search title, slug, content…" autocomplete="off">
{{if .Filter.Tags}}<input type="hidden" name="tag" value="{{join "," .Filter.Tags}}">{{end}}
{{if .Filter.Management}}<input type="hidden" name="mgmt" value="{{join "," .Filter.Management}}">{{end}}
{{if ne (join "," .Filter.Status) "active"}}<input type="hidden" name="status" value="{{join "," .Filter.Status}}">{{end}}
{{if .Filter.HasLinks}}<input type="hidden" name="has" value="{{join "," .Filter.HasLinks}}">{{end}}
{{if .Filter.ShowArchived}}<input type="hidden" name="show-archived" value="1">{{end}}
{{if .Filter.ProjectPath}}<input type="hidden" name="project" value="{{.Filter.ProjectPath}}">{{end}}
{{if and .Filter.ProjectPath (not .Filter.IncludeDescendants)}}<input type="hidden" name="project_descendants" value="0">{{end}}
</form>
{{template "view-project-chip" .}}
{{if .AllTags}}
<div class="chip-row">
<span class="muted">tag:</span>
{{range .Counts.Tags}}
<a class="tag {{if .Active}}tag-on{{end}}"
href="{{.URL}}"
hx-get="{{.URL}}" hx-target="#tree-section" hx-swap="outerHTML" hx-push-url="true">{{.Label}} <small>({{.Count}})</small></a>
{{end}}
</div>
{{end}}
<div class="chip-row">
<span class="muted">mgmt:</span>
{{range .Counts.Management}}
<a class="mgmt-chip {{if .Active}}chip-on{{end}}"
href="{{.URL}}"
hx-get="{{.URL}}" hx-target="#tree-section" hx-swap="outerHTML" hx-push-url="true">{{.Label}} <small>({{.Count}})</small></a>
{{end}}
</div>
<div class="chip-row">
<span class="muted">status:</span>
{{range .Counts.Status}}
<a class="status-chip {{if .Active}}chip-on{{end}}"
href="{{.URL}}"
hx-get="{{.URL}}" hx-target="#tree-section" hx-swap="outerHTML" hx-push-url="true">{{.Label}} <small>({{.Count}})</small></a>
{{end}}
<a class="status-chip {{if .Filter.ShowArchived}}chip-on{{end}}"
href="{{.Counts.ShowArchived.URL}}"
hx-get="{{.Counts.ShowArchived.URL}}" hx-target="#tree-section" hx-swap="outerHTML" hx-push-url="true"
title="When off, archived rows hide even when status=archived is selected">show archived <small>({{.Counts.ShowArchived.Count}})</small></a>
</div>
<div class="chip-row">
<span class="muted">has:</span>
{{range .Counts.Has}}
<a class="has-chip {{if .Active}}chip-on{{end}}"
href="{{.URL}}"
hx-get="{{.URL}}" hx-target="#tree-section" hx-swap="outerHTML" hx-push-url="true">{{.Label}} <small>({{.Count}})</small></a>
{{end}}
</div>
</section>
<section class="tree">
<ul class="forest">
{{range .Roots}}
<li class="node root">
<a href="/i/{{.Item.PrimaryPath}}">{{.Item.Title}}</a>
<span class="slug">{{.Item.PrimaryPath}}</span>
{{range .Item.Management}}<span class="mgmt mgmt-{{.}}">{{.}}</span>{{end}}
{{range .Item.Tags}}<span class="tag">{{.}}</span>{{end}}
<a class="add" href="/new?parent={{.Item.PrimaryPath}}">+</a>
{{template "children" .}}
</li>
{{else}}
<li class="empty"><em>No items match. Try fewer filters or <a href="/">clear all</a>.</em></li>
{{end}}
</ul>
</section>
</section>
{{end}}
{{define "children"}}
{{if .Children}}
<ul>
{{range .Children}}
<li class="node project">
<a href="/i/{{.Item.PrimaryPath}}">{{.Item.Title}}</a>
<span class="slug">{{.Item.PrimaryPath}}</span>
<span class="status status-{{.Item.Status}}">{{.Item.Status}}</span>
{{if gt (len .Item.Paths) 1}}<span class="muted multi-parent" title="appears under multiple parents">×{{len .Item.Paths}}</span>{{end}}
{{range .Item.Management}}<span class="mgmt mgmt-{{.}}">{{.}}</span>{{end}}
{{range .Item.Tags}}<span class="tag">{{.}}</span>{{end}}
<a class="add" href="/new?parent={{.Item.PrimaryPath}}">+</a>
{{template "children" .}}
</li>
{{end}}
</ul>
{{end}}
{{end}}