Phase 4: triage actions (delete, move, mark; bulk select) (AUD-70)

Bulk select in the triage list, with delete/move/mark and a recoverable
delete (FR-6, FR-9).

- migration v4: messages.folder + messages.mailbox; folder/category views
- internal/mailops: app folder -> Gmail label moves over IMAP (IPv4), locate
  by X-GM-MSGID, create labels lazily, move/save/reply/restore, flag marks
- mailfetch: CREATE, UID MOVE (COPY+EXPUNGE fallback), STORE flags, X-GM-MSGID search
- server: POST /actions/bulk with CSRF, UI delete confirmation, second
  confirmation for messages classified Spam (never auto-deleted), per-message
  and summary action-log rows (FR-13)
- inbox: select-all toolbar, checkboxes, folder nav, Restore on non-inbox rows

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-10-11 21:44:52 +11:00
parent 735934dab2
commit 8c0416750e
18 changed files with 2023 additions and 71 deletions

View File

@@ -49,13 +49,15 @@
<a class="nav-edit" href="/categories">Edit</a>
</div>
<div class="nav-group-items">
<a class="nav-item nav-item-active" href="#">
<a class="nav-item nav-item-active" href="#"
hx-get="/partials/inbox?folder=all" hx-target="#inbox-list" hx-swap="innerHTML">
<span class="dot" style="background: var(--primary)"></span>
<span>All mail</span>
<span class="count">{{.Total}}</span>
</a>
{{range .Categories}}
<a class="nav-item" href="#">
<a class="nav-item" href="#"
hx-get="/partials/inbox?{{.Param}}" hx-target="#inbox-list" hx-swap="innerHTML">
<span class="dot" style="background: {{.Color}}; {{if not .Count}}opacity:.35;{{end}}"></span>
<span>{{.Name}}</span>
<span class="count">{{.Count}}</span>
@@ -67,10 +69,11 @@
<div class="nav-group-title">Folders</div>
<div class="nav-group-items">
{{range .Folders}}
<a class="nav-item" href="#">
<a class="nav-item" href="#"
hx-get="/partials/inbox?{{.Param}}" hx-target="#inbox-list" hx-swap="innerHTML">
<span class="dot" style="background: {{.Color}}"></span>
<span>{{.Name}}</span>
<span class="count">0</span>
<span class="count">{{.Count}}</span>
</a>
{{end}}
</div>
@@ -119,5 +122,31 @@
</main>
<script src="/static/htmx.min.js" defer></script>
<script>
// Bulk-select state for the Phase 4 triage toolbar. The message list is
// swapped by HTMX, so the handlers live on the document (delegation).
function eaSelectedIds() {
return document.querySelectorAll('#bulk-items input[name="ids"]:checked');
}
function eaUpdateSelected() {
var el = document.getElementById('selected-count');
if (el) el.textContent = eaSelectedIds().length + ' selected';
}
document.addEventListener('change', function (e) {
if (!e.target) return;
if (e.target.id === 'select-all') {
var on = e.target.checked;
document.querySelectorAll('#bulk-items input[name="ids"]').forEach(function (cb) { cb.checked = on; });
eaUpdateSelected();
} else if (e.target.name === 'ids') {
eaUpdateSelected();
}
});
document.body.addEventListener('htmx:afterSwap', function () {
var sa = document.getElementById('select-all');
if (sa) sa.checked = false;
eaUpdateSelected();
});
</script>
</body>
</html>