caldav package:
- Todo carries URL, ETag, Raw so ListTodos rows can be PUT/DELETEd in place
- BuildVTodoICS for new VTODOs, ApplyVTodoEdit for in-place edits that
preserve unknown properties (DESCRIPTION, CATEGORIES, X-*)
- PutTodo/DeleteTodo with If-Match optimistic concurrency
- ErrPreconditionFailed/ErrNotFound for 412/404
- RFC 5545 fold-at-75 + CRLF + text escape, hand-rolled UUID v4
- httptest round-trip (create -> list -> complete -> delete) plus 412 path
web:
- POST /i/{path}/caldav/todo/{complete,reopen,edit,delete,todo-create}
- Re-fetches the live ETag before each PUT/DELETE so ordinary use never
trips 412; on actual 412 the section reloads with a banner
- Calendar URL must already be linked to the item (anti-forgery guard)
- tasks_section partial drives both the initial page render and HTMX
swaps; detail.tmpl reduces to a one-liner template call
docs/design.md §5: rewrite for full read/write semantics + ETag concurrency.
103 lines
4.6 KiB
Cheetah
103 lines
4.6 KiB
Cheetah
{{define "tasks-section"}}
|
||
<section class="tasks" id="tasks-section">
|
||
<h2>Tasks</h2>
|
||
{{if .Banner}}<p class="banner warn" role="alert">{{.Banner}}</p>{{end}}
|
||
{{if .Tasks}}
|
||
{{$root := .}}
|
||
{{range .Tasks}}
|
||
{{$calURL := .CalendarURL}}
|
||
<div class="cal-block" data-cal="{{$calURL}}">
|
||
<h3>{{.DisplayName}}</h3>
|
||
{{if .Error}}<p class="banner warn">{{.Error}}</p>{{end}}
|
||
|
||
<form class="todo-create"
|
||
hx-post="/i/{{$root.Item.PrimaryPath}}/caldav/todo/todo-create"
|
||
hx-target="#tasks-section"
|
||
hx-swap="outerHTML">
|
||
<input type="hidden" name="calendar_url" value="{{$calURL}}">
|
||
<input type="text" name="summary" placeholder="Add a task…" required>
|
||
<input type="date" name="due" title="due date (optional)">
|
||
<button type="submit">Add</button>
|
||
</form>
|
||
|
||
{{if .Open}}
|
||
<ul class="todo open">
|
||
{{range .Open}}
|
||
<li class="todo-row" data-uid="{{.UID}}">
|
||
<form class="todo-complete inline"
|
||
hx-post="/i/{{$root.Item.PrimaryPath}}/caldav/todo/complete"
|
||
hx-target="#tasks-section"
|
||
hx-swap="outerHTML">
|
||
<input type="hidden" name="calendar_url" value="{{$calURL}}">
|
||
<input type="hidden" name="uid" value="{{.UID}}">
|
||
<button type="submit" class="check" title="Mark complete" aria-label="Mark complete">☐</button>
|
||
</form>
|
||
|
||
<form class="todo-edit inline"
|
||
hx-post="/i/{{$root.Item.PrimaryPath}}/caldav/todo/edit"
|
||
hx-target="#tasks-section"
|
||
hx-swap="outerHTML">
|
||
<input type="hidden" name="calendar_url" value="{{$calURL}}">
|
||
<input type="hidden" name="uid" value="{{.UID}}">
|
||
<input type="text" name="summary" value="{{.Summary}}" required>
|
||
<input type="date" name="due" value="{{if .Due}}{{.Due.Format "2006-01-02"}}{{end}}">
|
||
<button type="submit" title="Save edits">Save</button>
|
||
</form>
|
||
|
||
<form class="todo-delete inline"
|
||
hx-post="/i/{{$root.Item.PrimaryPath}}/caldav/todo/delete"
|
||
hx-target="#tasks-section"
|
||
hx-swap="outerHTML"
|
||
hx-confirm="Delete this task? This cannot be undone.">
|
||
<input type="hidden" name="calendar_url" value="{{$calURL}}">
|
||
<input type="hidden" name="uid" value="{{.UID}}">
|
||
<button type="submit" class="x" title="Delete" aria-label="Delete">×</button>
|
||
</form>
|
||
</li>
|
||
{{end}}
|
||
</ul>
|
||
{{else}}
|
||
<p class="muted">No open tasks.</p>
|
||
{{end}}
|
||
|
||
{{if .DoneRecent}}
|
||
<details>
|
||
<summary class="muted">{{len .DoneRecent}} completed in last 30 days</summary>
|
||
<ul class="todo done">
|
||
{{range .DoneRecent}}
|
||
<li class="todo-row" data-uid="{{.UID}}">
|
||
<form class="todo-reopen inline"
|
||
hx-post="/i/{{$root.Item.PrimaryPath}}/caldav/todo/reopen"
|
||
hx-target="#tasks-section"
|
||
hx-swap="outerHTML"
|
||
title="Reopen">
|
||
<input type="hidden" name="calendar_url" value="{{$calURL}}">
|
||
<input type="hidden" name="uid" value="{{.UID}}">
|
||
<button type="submit" class="check" aria-label="Reopen">☑</button>
|
||
</form>
|
||
<span class="summary">{{.Summary}}</span>
|
||
<form class="todo-delete inline"
|
||
hx-post="/i/{{$root.Item.PrimaryPath}}/caldav/todo/delete"
|
||
hx-target="#tasks-section"
|
||
hx-swap="outerHTML"
|
||
hx-confirm="Delete this task? This cannot be undone.">
|
||
<input type="hidden" name="calendar_url" value="{{$calURL}}">
|
||
<input type="hidden" name="uid" value="{{.UID}}">
|
||
<button type="submit" class="x" title="Delete" aria-label="Delete">×</button>
|
||
</form>
|
||
</li>
|
||
{{end}}
|
||
</ul>
|
||
</details>
|
||
{{end}}
|
||
</div>
|
||
{{end}}
|
||
{{else}}
|
||
<p class="muted">No CalDAV list linked.</p>
|
||
<form method="post" action="/i/{{.Item.PrimaryPath}}/caldav/create" class="inline">
|
||
<button type="submit">Create CalDAV list</button>
|
||
</form>
|
||
{{end}}
|
||
</section>
|
||
{{end}}
|