Files
projax/web/templates/timeline_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

156 lines
8.4 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 "timeline-section"}}
<section id="timeline-section" class="timeline">
<section class="tagbar" id="timeline-filterbar">
<form id="timeline-filter" class="search"
hx-get="/timeline"
hx-target="#timeline-section"
hx-swap="outerHTML"
hx-trigger="change from:select"
hx-push-url="true">
<label>tag&nbsp;
<select name="tag" multiple size="3">
{{range $.Filter.Tags}}<option value="{{.}}" selected>{{.}}</option>{{end}}
</select>
</label>
<label>mgmt&nbsp;
<select name="mgmt" multiple size="4">
{{$selM := .Filter.Management}}
<option value="mai" {{if contains $selM "mai"}}selected{{end}}>mai</option>
<option value="self" {{if contains $selM "self"}}selected{{end}}>self</option>
<option value="external" {{if contains $selM "external"}}selected{{end}}>external</option>
</select>
</label>
<label>has&nbsp;
<select name="has" multiple size="2">
{{$selH := .Filter.HasLinks}}
<option value="caldav-list" {{if contains $selH "caldav-list"}}selected{{end}}>caldav</option>
<option value="gitea-repo" {{if contains $selH "gitea-repo"}}selected{{end}}>gitea</option>
</select>
</label>
<label>kind&nbsp;
<select name="kind" multiple size="4">
{{$selK := .P.Kinds}}
<option value="todo" {{if contains $selK "todo"}}selected{{end}}>todo</option>
<option value="event" {{if contains $selK "event"}}selected{{end}}>event</option>
<option value="doc" {{if contains $selK "doc"}}selected{{end}}>doc</option>
<option value="creation" {{if contains $selK "creation"}}selected{{end}}>creation</option>
</select>
</label>
<label>order&nbsp;
<select name="order">
<option value="desc" {{if eq .P.Order "desc"}}selected{{end}}>newest first</option>
<option value="asc" {{if eq .P.Order "asc"}}selected{{end}}>oldest first</option>
</select>
</label>
{{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}}
{{if .Filter.Active}}<a class="clear" href="/timeline">clear filters</a>{{end}}
</form>
{{template "view-project-chip" .}}
<p class="counts muted">
<small>{{.P.TotalRows}} rows · {{.P.From.Format "2006-01-02"}} → {{.P.ToInclusive.Format "2006-01-02"}}</small>
{{if .P.Cached}}<small title="Served from 90s in-memory cache · built {{.P.BuiltAt.Format "15:04:05"}}">· cached</small>{{else}}<small>· fresh</small>{{end}}
</p>
</section>
{{if .P.Days}}
<ol class="spine" data-order="{{.P.Order}}">
{{range .P.Days}}
<li class="spine-day{{if .Sticky}} sticky-{{.Sticky}}{{end}}" data-date="{{.DateKey}}">
<header class="day-header">
{{if .Sticky}}<span class="sticky-pill">{{.Sticky}}</span>{{end}}
<h2><a class="muted" href="/timeline?from={{.DateKey}}&amp;to={{.DateKey}}">{{.Label}}</a> <small class="muted">({{len .Rows}})</small></h2>
</header>
<ul class="day-rows">
{{range .Rows}}
{{if eq .Kind "event"}}
<li class="row row-event{{if .FarFuture}} far-future{{end}}" data-uid="{{.Event.UID}}">
<span class="time">{{if .StartLabel}}{{.StartLabel}}{{else}}&nbsp;{{end}}</span>
<span class="kind-badge kind-event" title="event">event</span>
<a class="proj" href="/i/{{.ItemPath}}">{{.ItemPath}}</a>
<span class="summary">{{.Event.Summary}}</span>
{{if .Event.Location}}<span class="loc muted">· {{.Event.Location}}</span>{{end}}
{{if .DurationHint}}<span class="duration muted">{{.DurationHint}}</span>{{end}}
{{if .Event.Recurring}}<span class="recurring" title="recurring — only literal DTSTART shown">↻</span>{{end}}
</li>
{{else if eq .Kind "todo"}}
{{$todoCal := .CalendarURL}}
{{$uid := .Todo.UID}}
{{$done := or (eq .Todo.Status "COMPLETED") (eq .Todo.Status "CANCELLED")}}
<li class="row row-todo{{if .FarFuture}} far-future{{end}}{{if $done}} done{{end}}" data-uid="{{$uid}}">
<span class="time">
{{if .Todo.Due}}{{if eq (.Todo.Due.Hour) 0}}{{else}}{{.Todo.Due.Format "15:04"}}{{end}}{{end}}
</span>
<form class="todo-complete inline"
hx-post="/dashboard/task/done"
hx-target="#timeline-section"
hx-swap="outerHTML"
hx-include="this">
<input type="hidden" name="calendar_url" value="{{$todoCal}}">
<input type="hidden" name="uid" value="{{$uid}}">
<button type="submit" class="check" title="{{if $done}}Reopen{{else}}Mark complete{{end}}" aria-label="Toggle">{{if $done}}☑{{else}}☐{{end}}</button>
</form>
<span class="kind-badge kind-todo" title="task">todo</span>
<a class="proj" href="/i/{{.ItemPath}}">{{.ItemPath}}</a>
<form class="todo-edit inline"
hx-post="/dashboard/task/edit"
hx-target="#timeline-section"
hx-swap="outerHTML">
<input type="hidden" name="calendar_url" value="{{$todoCal}}">
<input type="hidden" name="uid" value="{{$uid}}">
<input type="text" name="summary" value="{{.Todo.Summary}}" required>
<input type="date" name="due" value="{{if .Todo.Due}}{{.Todo.Due.Format "2006-01-02"}}{{end}}">
<button type="submit" title="Save edits">Save</button>
</form>
<form class="todo-delete inline"
hx-post="/dashboard/task/delete"
hx-target="#timeline-section"
hx-swap="outerHTML"
hx-confirm="Delete this task? This cannot be undone.">
<input type="hidden" name="calendar_url" value="{{$todoCal}}">
<input type="hidden" name="uid" value="{{$uid}}">
<button type="submit" class="x" title="Delete" aria-label="Delete">×</button>
</form>
</li>
{{else if eq .Kind "doc"}}
<li class="row row-doc{{if .FarFuture}} far-future{{end}}" data-link="{{.Link.ID}}">
<span class="time">&nbsp;</span>
<span class="kind-badge kind-doc" title="document">doc</span>
<span class="per">{{.PER}}</span>
<span class="ref-type ref-type-{{.Link.RefType}}">{{.Link.RefType}}</span>
{{if .Link.Note}}<span class="note">{{deref .Link.Note}}</span>{{end}}
<a class="ref-id" href="{{.Link.RefID}}" target="_blank" rel="noopener">{{.Link.RefID}}</a>
<a class="proj muted" href="/i/{{.ItemPath}}">{{.ItemPath}}</a>
<form class="link-delete inline"
hx-post="/i/{{.ItemPath}}/links/remove"
hx-target="#timeline-section"
hx-swap="outerHTML"
hx-confirm="Remove this dated link from {{.ItemPath}}?">
<input type="hidden" name="link_id" value="{{.Link.ID}}">
<button type="submit" class="x" title="Remove link" aria-label="Remove">×</button>
</form>
</li>
{{else if eq .Kind "creation"}}
<li class="row row-creation{{if .FarFuture}} far-future{{end}}">
<span class="time">&nbsp;</span>
<span class="kind-badge kind-creation" title="item added">added</span>
<span class="creation-marker muted">added <a class="proj" href="/i/{{.ItemPath}}">{{.ItemPath}}</a> to projax</span>
</li>
{{end}}
{{end}}
</ul>
</li>
{{end}}
</ol>
{{else}}
<p class="empty muted">Nothing on this timeline yet.</p>
{{end}}
</section>
{{end}}