The cluster and node pages swap their live part every few seconds, and the forms inside it went with it: what was typed, an open choice, the focus. Filling one in raced the clock. The swap now waits while a field in the live part has the focus or a form in it has been typed into and not sent; sending it, or leaving the page, ends that. The mirror page's reload during a download goes through the same check instead of http-equiv. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
68 lines
2.8 KiB
JavaScript
68 lines
2.8 KiB
JavaScript
// What the pages did in inline handlers, which the Content-Security-Policy
|
|
// does not run: a form sent when a choice changes, a confirmation before a
|
|
// removal, a field filled from the option chosen, and the live parts of the
|
|
// cluster and node pages refreshed only while the tab is in view.
|
|
(function () {
|
|
document.addEventListener('change', function (e) {
|
|
var t = e.target;
|
|
if (t.matches && t.matches('[data-autosubmit]') && t.form) {
|
|
t.form.submit();
|
|
}
|
|
if (t.matches && t.matches('[data-suggest-into]') && t.form) {
|
|
var f = t.form.elements[t.getAttribute('data-suggest-into')];
|
|
var o = t.selectedOptions[0];
|
|
if (f && o) { f.value = o.dataset.suggest || ''; }
|
|
}
|
|
});
|
|
document.addEventListener('submit', function (e) {
|
|
var msg = e.target.getAttribute && e.target.getAttribute('data-confirm');
|
|
if (msg && !window.confirm(msg)) { e.preventDefault(); }
|
|
});
|
|
// A form being filled in is not refreshed away. The live parts of the
|
|
// cluster and node pages are swapped whole every few seconds, and a form
|
|
// inside one lost what was typed into it, its open choice, its focus:
|
|
// filling one in raced the clock. While a field in the refreshing part
|
|
// has the focus, or a form in it has been typed into and not sent, it
|
|
// waits; sending the form, or leaving the page, ends that.
|
|
var edited = [];
|
|
document.addEventListener('input', function (e) {
|
|
var f = e.target.form || (e.target.closest && e.target.closest('form'));
|
|
if (f && edited.indexOf(f) < 0) { edited.push(f); }
|
|
});
|
|
document.addEventListener('submit', function (e) {
|
|
if (!e.defaultPrevented) { edited = []; }
|
|
});
|
|
function busy(root) {
|
|
var a = document.activeElement;
|
|
if (a && a !== document.body && root.contains(a) &&
|
|
a.matches('input, select, textarea, [contenteditable]')) {
|
|
return true;
|
|
}
|
|
for (var i = 0; i < edited.length; i++) {
|
|
if (root.contains(edited[i]) && document.contains(edited[i])) { return true; }
|
|
}
|
|
return false;
|
|
}
|
|
// htmx's trigger filters are evaluated JavaScript; this is the same
|
|
// "only while visible" without them, and "not while being filled in".
|
|
document.addEventListener('htmx:beforeRequest', function (e) {
|
|
var elt = e.detail.elt;
|
|
if (elt.hasAttribute('data-visible-only') &&
|
|
(document.visibilityState !== 'visible' || busy(elt))) {
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
// A page that reloads itself (<meta name="llmgw-refresh" content="N">)
|
|
// does it on the same terms, rather than by http-equiv, which nothing
|
|
// can hold off.
|
|
var r = document.querySelector('meta[name="llmgw-refresh"]');
|
|
var every = r ? parseInt(r.getAttribute('content'), 10) : 0;
|
|
if (every > 0) {
|
|
setInterval(function () {
|
|
if (document.visibilityState === 'visible' && !busy(document.body)) {
|
|
window.location.reload();
|
|
}
|
|
}, every * 1000);
|
|
}
|
|
})();
|