Files
KantSites/public/assets/consent.js
T
kantarciandClaude Opus 5.5 6fa303f901 Save consent without reloading the page
consent.js posts the consent form in the background, the server answers
204 with the cookie, and the script swaps in the embeds and the legal line
from a fresh copy of the page. Embed hosts are in frame-src from the start,
the content address still only after consent. Without JavaScript the form
keeps working with a reload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:56:16 +02:00

92 lines
3.9 KiB
JavaScript

// Einwilligung ohne Neuladen (ADR 115).
//
// Ohne dieses Skript schicken der Hinweis und der Knopf am eingebetteten Inhalt ein Formular ab,
// der Server setzt das Cookie und leitet auf dieselbe Seite zurück - die Seite lädt neu. Mit dem
// Skript geht dasselbe Formular im Hintergrund an denselben Endpunkt; die Kopfzeile
// X-Kant-Consent sagt dem Server, dass keine Weiterleitung nötig ist. Danach holt das Skript die
// Seite noch einmal und übernimmt daraus nur, was sich durch die Entscheidung ändert: die
// eingebetteten Inhalte und die Zeile „Rechtliches“. Die Adresse eines Inhalts steht so weiterhin
// erst nach der Einwilligung im Dokument.
//
// Geht dabei etwas schief, schickt das Skript das Formular ganz normal ab: lieber einmal neu
// laden als eine Entscheidung, die nicht ankommt.
(() => {
const forms = document.querySelectorAll('.consent-bar form, .embed-blocked form');
if (forms.length === 0) {
return;
}
const replaceFrom = async () => {
const response = await fetch(location.href, { credentials: 'same-origin', headers: { Accept: 'text/html' } });
if (!response.ok) {
throw new Error('Seite nicht geladen');
}
const fresh = new DOMParser().parseFromString(await response.text(), 'text/html');
for (const blocked of document.querySelectorAll('.embed-blocked[id]')) {
const next = fresh.getElementById(blocked.id);
if (next && !next.classList.contains('embed-blocked')) {
blocked.replaceWith(document.importNode(next, true));
}
}
for (const legal of document.querySelectorAll('.block-legal[id]')) {
const next = fresh.getElementById(legal.id);
if (next) {
legal.replaceWith(document.importNode(next, true));
}
}
};
for (const form of forms) {
form.addEventListener('submit', async (event) => {
event.preventDefault();
const button = event.submitter;
const body = new FormData(form, button);
const placeholder = form.closest('.embed-blocked');
for (const control of form.querySelectorAll('button')) {
control.disabled = true;
}
try {
const saved = await fetch(form.action, { method: 'POST', body, credentials: 'same-origin', headers: { 'X-Kant-Consent': '1' } });
if (!saved.ok) {
throw new Error('Entscheidung nicht gespeichert');
}
// Wer etwas erlaubt hat, bekommt die Inhalte jetzt; wer abgelehnt hat, braucht keinen
// zweiten Abruf - es ändert sich nichts außer dem Hinweis.
if (body.get('choice') !== 'none' && document.querySelector('.embed-blocked')) {
await replaceFrom();
}
document.querySelector('.consent-bar')?.remove();
if (placeholder) {
// Der Knopf ist mit dem Platzhalter verschwunden; der Fokus geht an den Inhalt, der an
// seiner Stelle steht.
const frame = document.getElementById(placeholder.id)?.querySelector('iframe');
frame?.focus();
} else {
// Entschieden im Hinweis: der Fokus beginnt wieder am Seitenanfang, wie nach dem
// Neuladen - der nächste Tab führt zu „Zum Inhalt springen“.
document.getElementById('seitenanfang')?.focus({ preventScroll: true });
}
} catch {
// Rückfall: das Formular normal absenden, samt gedrücktem Knopf.
for (const control of form.querySelectorAll('button')) {
control.disabled = false;
}
withChoice(form, button).submit();
}
});
}
// form.submit() schickt den gedrückten Knopf nicht mit; sein Wert wandert als verstecktes Feld
// ins Formular, damit der Rückfall dieselbe Entscheidung sendet.
function withChoice(form, button) {
if (button?.name) {
const field = document.createElement('input');
field.type = 'hidden';
field.name = button.name;
field.value = button.value;
form.append(field);
}
return form;
}
})();