Files
KantSites/public/assets/confirm.js
T
kantarciandClaude Fable 5.2 b08862b139 Back up unsaved editor work locally, structure the settings, fix archive accessibility
- The editor keeps unsaved changes in the browser and offers them
  after a crash, a closed tab or an expired session. No timed save on
  the server: every save is a full revision.
- The save status stays visible in narrow windows.
- Settings get a sticky section bar, two folded groups and a save bar
  that stays in view.
- The admin post list fetches terms and images once, not per post.
- Archive post titles are second-level headings, the archive head
  keeps full text contrast, the skip link sits above a sticky header,
  a linked image without alternative text names its link.
- The editor check starts through the dev router like the others.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 13:01:23 +01:00

73 lines
3.6 KiB
JavaScript

/**
* Rückfrage im Stil der Verwaltung - statt confirm() des Browsers, und statt gar keiner.
*
* Ein Formular meldet sich mit data-confirm-title an (dazu optional data-confirm-text,
* data-confirm-label und data-confirm-variant="danger"). Sein Absenden wird angehalten, bis der
* Dialog bestätigt ist. Ohne JavaScript bleibt es ein gewöhnliches Formular.
*
* Skripte rufen kantConfirm({ title, message, confirmLabel, variant, onConfirm }) auf.
*/
(() => {
let dialog = null;
let pending = null;
let opener = null;
const build = () => {
dialog = document.createElement('dialog');
dialog.className = 'confirm-dialog';
dialog.setAttribute('aria-labelledby', 'confirm-dialog-title');
dialog.setAttribute('aria-describedby', 'confirm-dialog-text');
const title = document.createElement('h2'); title.id = 'confirm-dialog-title';
const text = document.createElement('p'); text.id = 'confirm-dialog-text';
const actions = document.createElement('div'); actions.className = 'dialog-actions';
const cancel = document.createElement('button'); cancel.type = 'button'; cancel.className = 'secondary'; cancel.textContent = 'Abbrechen'; cancel.dataset.confirmCancel = '';
const ok = document.createElement('button'); ok.type = 'button'; ok.dataset.confirmOk = '';
actions.append(cancel, ok);
dialog.append(title, text, actions);
cancel.addEventListener('click', () => dialog.close());
ok.addEventListener('click', () => { const run = pending; pending = null; dialog.close(); run?.(); });
// Escape und der Klick neben den Dialog schließen ohne Folgen.
dialog.addEventListener('click', event => { if (event.target === dialog) dialog.close(); });
dialog.addEventListener('close', () => { pending = null; opener?.focus?.({ preventScroll: true }); opener = null; });
document.body.append(dialog);
};
const ask = ({ title, message = '', confirmLabel = 'Bestätigen', variant = 'primary', onConfirm }) => {
if (!dialog) build();
if (dialog.open) dialog.close();
opener = document.activeElement;
dialog.querySelector('h2').textContent = title;
const text = dialog.querySelector('p'); text.textContent = message; text.hidden = '' === message;
const ok = dialog.querySelector('[data-confirm-ok]');
ok.textContent = confirmLabel;
ok.className = 'danger' === variant ? 'primary danger' : 'primary';
pending = onConfirm;
dialog.showModal();
// Der Fokus liegt auf „Abbrechen“: Die Eingabetaste darf nichts Endgültiges auslösen.
dialog.querySelector('[data-confirm-cancel]').focus();
};
window.kantConfirm = ask;
// Ein ungültiges Feld in einem zugeklappten Bereich kann der Browser nicht anspringen - das
// Absenden bliebe ohne jede Meldung stehen. Der Bereich klappt deshalb auf.
document.addEventListener('invalid', event => { event.target.closest?.('details:not([open])')?.setAttribute('open', ''); }, true);
document.addEventListener('submit', event => {
const form = event.target;
if (!(form instanceof HTMLFormElement) || !form.dataset.confirmTitle || form.dataset.confirmed) return;
event.preventDefault();
const submitter = event.submitter;
ask({
title: form.dataset.confirmTitle,
message: form.dataset.confirmText || '',
confirmLabel: form.dataset.confirmLabel || submitter?.textContent.trim() || 'Bestätigen',
variant: form.dataset.confirmVariant || 'primary',
onConfirm: () => {
form.dataset.confirmed = '1';
// requestSubmit nimmt den Knopf mit, falls er einen Namen und Wert trägt.
submitter && submitter.form === form ? form.requestSubmit(submitter) : form.requestSubmit();
},
});
});
})();