1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// autumn-admin-plugin client-side helpers.
// Served from /{prefix}/static/admin.js so it works under CSP `script-src 'self'`.
(function () {
// form.requestSubmit() shipped later than <dialog> support in some
// browsers (e.g. Safari 15.4-15.6 has <dialog> but not requestSubmit,
// which arrived in Safari 16) and may be absent in older headless test
// runners, where calling it directly throws a TypeError. Fall back to
// the older form.submit() (which the browser accepts everywhere <dialog>
// does) rather than letting the resubmit fail silently.
function submitForm(form) {
if (typeof form.requestSubmit === "function") {
form.requestSubmit();
} else {
// form.submit() bypasses the 'submit' event entirely (per spec), so
// the bulk-action submit handler's `bulkConfirmed` cleanup below
// never runs for this path — clear it here instead. Otherwise a
// bfcache-restored page (after navigating away via this fallback)
// would carry a stale "confirmed" flag into the next, unrelated
// bulk-action attempt and skip its confirmation dialog.
delete form.dataset.bulkConfirmed;
form.submit();
}
}
// Select-all checkbox toggles every .row-check in the current table.
document.addEventListener("click", function (e) {
var t = e.target;
if (t && t.id === "select-all") {
document.querySelectorAll(".row-check").forEach(function (c) {
c.checked = t.checked;
});
}
});
// Bulk-action submit: require at least one selected row, and — if the
// selected action is marked as requiring confirmation (data-confirm="1"
// on the option) — show the server-rendered #admin-bulk-confirm <dialog>
// (autumn_web::widgets::modal) rather than a native browser confirm popup.
// preventDefault() always runs once confirmation is required, regardless
// of <dialog>.showModal support, so a destructive submit is never let
// through unconfirmed: browsers without it fall back to window.confirm().
// Once confirmed, the submit is re-issued and allowed through (see the
// click handler below), which also clears the one-shot flag below.
document.addEventListener("submit", function (e) {
var form = e.target;
if (
!form ||
!form.matches ||
!form.matches('form[action$="/actions"]')
) {
return;
}
var checked = form.querySelectorAll(
'.row-check:checked',
);
if (checked.length === 0) {
e.preventDefault();
window.alert("Select at least one row first.");
return;
}
if (form.dataset.bulkConfirmed === "1") {
delete form.dataset.bulkConfirmed;
return;
}
var sel = form.querySelector('select[name="action"]');
if (!sel) return;
var opt = sel.options[sel.selectedIndex];
if (!opt || opt.dataset.confirm !== "1") return;
e.preventDefault();
var message =
"Apply '" + opt.text + "' to " + checked.length + " record(s)?";
var dialog = document.getElementById("admin-bulk-confirm");
if (!dialog || !dialog.showModal) {
if (window.confirm(message)) {
form.dataset.bulkConfirmed = "1";
// Deferred: requestSubmit() called synchronously from within this
// same form's still-dispatching submit handler is a no-op per the
// HTML spec's reentrancy guard ("if form's firing submit event is
// true, then return"). Scheduling it after the current dispatch
// finishes lets the resubmit actually happen.
setTimeout(function () {
submitForm(form);
}, 0);
}
return;
}
var detail = dialog.querySelector("[data-bulk-confirm-detail]");
if (detail) detail.textContent = message;
dialog.showModal();
});
// Bulk-action confirm dialog: clicking [data-bulk-confirm] re-submits the
// bulk-action form. Closing the dialog itself is handled by the button's
// own command="close"/data-modal-close attributes (see templates.rs),
// routed through the same shared mechanism modal_close_button uses — no
// bespoke close call needed here.
document.addEventListener("click", function (e) {
var confirmBtn = e.target.closest("[data-bulk-confirm]");
if (!confirmBtn) return;
var form = document.querySelector('form[action$="/actions"]');
if (!form) return;
form.dataset.bulkConfirmed = "1";
submitForm(form);
});
// CSV import form: multipart/form-data bypasses form-field CSRF scanning, so
// send the token as a header (CsrfLayer checks headers before reading the body).
// Reads the token and optional custom header name from the existing csrf meta tag,
// consistent with how the HTMX CSRF companion script works.
document.addEventListener("submit", function (e) {
var form = e.target;
if (!form || !form.matches || !form.matches("#autumn-csv-import-form")) return;
e.preventDefault();
var meta = document.querySelector('meta[name="csrf-token"]');
var header = (meta && meta.getAttribute("data-header")) || "X-CSRF-Token";
var token = (meta && meta.getAttribute("content")) || "";
var headers = token ? { [header]: token } : {};
fetch(form.action, { method: "POST", headers: headers, body: new FormData(form) })
.then(function (r) { return r.text(); })
.then(function (h) { document.open(); document.write(h); document.close(); });
});
// Cosmetic client-side strip of blank password inputs so they aren't sent.
// The real safety net is server-side in strip_meta_fields() using the
// declared AdminFieldKind::Password metadata; this just avoids shipping
// empty values over the wire.
document.addEventListener(
"submit",
function (e) {
var form = e.target;
if (!form || !form.matches || !form.matches("form")) return;
form
.querySelectorAll('input[type="password"]')
.forEach(function (i) {
if (i.value === "") i.removeAttribute("name");
});
},
true,
);
})();