Files
KantSites/public/assets/confirm.js
T
kantarciandClaude Fable 5.2 ead47cc924 Keep old pages readable, detect stale settings writes, make backups honest
- Own CSS is parsed on write only. A page whose stored CSS no longer
  passes a stricter rule answers 200 and opens in the editor; that one
  block loses its CSS. A document read several times in one request is
  validated once.
- Settings and theme refuse a write from an outdated form.
- A backup fails when a media file does not arrive completely and
  leaves no half directory behind; rotation counts intact backups only.
- The archive update takes its backup behind the maintenance page.
- A package import puts all media in place before removing the old
  ones, reports honestly when that fails and keeps a maintenance state
  that was already on.
- Restoring replaces the SQLite file in one move and points at pending
  migrations.
- Six irreversible actions ask first, in a dialog of the admin's own.
- Ctrl+S in the editor takes the field being typed in along; Delete
  no longer removes a block while a dialog is open.

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

69 lines
3.3 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;
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();
},
});
});
})();