Quelltext des aktuellen Standes
/**
* Ereignisverteiler für die Tresor-Seiten.
*
* Auf `/vault…` gilt eine Content-Security-Policy ohne 'unsafe-inline': Ein in
* die Seite geratener HTML-Schnipsel wird dort nicht zu ausgeführtem Code. Der
* Preis dafür ist, dass `onclick="…"` und Verwandte nicht mehr laufen – ein
* Nonce deckt Ereignisattribute nicht ab. Die Vorlagen beschreiben ihr Verhalten
* deshalb über data-Attribute, die hier einmal zentral verdrahtet werden.
*
* Aufgelöst wird beim Klick immer das **innerste** Element mit einem bekannten
* Attribut. Ein Kopier-Knopf in einer anklickbaren Zeile gewinnt damit gegen die
* Zeile, ohne dass irgendwo `stopPropagation()` nötig wäre.
*
* Vokabular (Klick):
* data-act="fn" Funktion aufrufen: fn(...args, el, event); `this` ist el.
* Gibt sie false zurück, wird die Vorgabeaktion unterdrückt.
* data-args='[1,"x"]' Argumente davor (JSON-Array)
* data-confirm="Frage" erst nach Rückfrage; auch an <form> für das Absenden
* data-open="id" Element mit dieser Kennung öffnen (Klasse „open")
* data-close="id" Element mit dieser Kennung schließen
* data-close-self nur beim Klick auf das Element selbst schließen (Dialoghintergrund)
* data-toggle-parent="cls" Klasse am Elternelement umschalten
* data-drop-class="cls" Klasse am Element selbst entfernen
* data-remove-closest="s" nächstgelegenes passendes Element entfernen
* data-copy="text" Text kopieren – über die Seitenfunktion vaultCopyText(text, el)
* data-href="/pfad" dorthin wechseln
*
* Weitere Ereignisse:
* data-act-change="fn" beim change-Ereignis
* data-act-input="fn" beim input-Ereignis
* data-act-submit="fn" beim Absenden eines Formulars
* data-favicon-fallback Bild: schlägt das Laden fehl, das folgende Element einblenden
*/
(function () {
'use strict';
var SELEKTOR = '[data-act],[data-open],[data-close],[data-close-self],[data-toggle-parent],'
+ '[data-drop-class],[data-remove-closest],[data-copy],[data-href],[data-stop]';
/** Argumente eines Elements; eine fehlerhafte Angabe gilt als „keine". */
function args(el) {
var roh = el.getAttribute('data-args');
if (!roh) { return []; }
try {
var wert = JSON.parse(roh);
return Array.isArray(wert) ? wert : [wert];
} catch (e) {
return [];
}
}
/**
* Ruft die benannte Funktion auf. Sie muss global stehen – die Vorlagen
* definieren sie in ihrem eigenen Skriptblock.
* @returns {*} Rückgabewert, damit `false` die Vorgabeaktion unterdrücken kann
*/
function rufe(name, el, ereignis) {
var fn = name && window[name];
return typeof fn === 'function'
? fn.apply(el, args(el).concat([el, ereignis]))
: undefined;
}
function oeffne(id) {
var ziel = id && document.getElementById(id);
if (ziel) { ziel.classList.add('open'); }
}
function schliesse(id) {
var ziel = id && document.getElementById(id);
if (ziel) { ziel.classList.remove('open'); }
}
document.addEventListener('click', function (e) {
var el = e.target.closest(SELEKTOR);
if (!el) { return; }
// Reiner Halt: verhindert, dass ein umschließendes Element (etwa eine
// anklickbare Zeile) auf diesen Klick reagiert.
if (el.hasAttribute('data-stop') && !el.hasAttribute('data-act')) { return; }
// Dialoghintergrund: nur schließen, wenn der Klick wirklich ihm galt und
// nicht dem Inhalt darin.
if (el.hasAttribute('data-close-self')) {
if (e.target === el) { el.classList.remove('open'); }
return;
}
var frage = el.getAttribute('data-confirm');
if (frage && !window.confirm(frage)) {
e.preventDefault();
return;
}
if (el.hasAttribute('data-open')) {
oeffne(el.getAttribute('data-open'));
e.preventDefault();
return;
}
if (el.hasAttribute('data-close')) {
schliesse(el.getAttribute('data-close'));
e.preventDefault();
return;
}
if (el.hasAttribute('data-toggle-parent')) {
if (el.parentNode && el.parentNode.classList) {
el.parentNode.classList.toggle(el.getAttribute('data-toggle-parent'));
}
return;
}
if (el.hasAttribute('data-drop-class')) {
el.classList.remove(el.getAttribute('data-drop-class'));
return;
}
if (el.hasAttribute('data-remove-closest')) {
var weg = el.closest(el.getAttribute('data-remove-closest'));
if (weg) { weg.remove(); }
return;
}
if (el.hasAttribute('data-copy')) {
if (typeof window.vaultCopyText === 'function') {
window.vaultCopyText(el.getAttribute('data-copy'), el);
}
return;
}
if (el.hasAttribute('data-act')) {
if (rufe(el.getAttribute('data-act'), el, e) === false) {
e.preventDefault();
}
return;
}
if (el.hasAttribute('data-href')) {
window.location = el.getAttribute('data-href');
}
});
document.addEventListener('change', function (e) {
var el = e.target.closest('[data-act-change]');
if (el) { rufe(el.getAttribute('data-act-change'), el, e); }
});
document.addEventListener('input', function (e) {
var el = e.target.closest('[data-act-input]');
if (el) { rufe(el.getAttribute('data-act-input'), el, e); }
});
document.addEventListener('submit', function (e) {
var form = e.target;
if (!form || !form.getAttribute) { return; }
var frage = form.getAttribute('data-confirm');
if (frage && !window.confirm(frage)) {
e.preventDefault();
return;
}
if (rufe(form.getAttribute('data-act-submit'), form, e) === false) {
e.preventDefault();
}
});
/**
* Favicon-Ersatz: Lädt das Bild nicht, wird das unmittelbar folgende
* Element eingeblendet. Ein Fehler kann schon vor dem Laden dieses Skripts
* eingetreten sein – deshalb wird der Zustand zusätzlich einmal geprüft.
*/
function faviconErsatz(img) {
var ph = img.nextElementSibling;
img.style.display = 'none';
if (!ph) { return; }
ph.hidden = false;
if (img.parentNode && img.parentNode.style) {
img.parentNode.style.background = ph.getAttribute('data-bg') || '';
}
}
function faviconsVerdrahten(wurzel) {
(wurzel || document).querySelectorAll('img[data-favicon-fallback]').forEach(function (img) {
if (img.dataset.faviconWired === '1') { return; }
img.dataset.faviconWired = '1';
img.addEventListener('error', function () { faviconErsatz(img); });
if (img.complete && img.naturalWidth === 0) { faviconErsatz(img); }
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () { faviconsVerdrahten(); });
} else {
faviconsVerdrahten();
}
// Für Vorlagen, die dieselben Dialoge auch aus eigenem Code steuern oder
// Inhalte nachladen.
window.vaultOpen = oeffne;
window.vaultClose = schliesse;
window.vaultFavicons = faviconsVerdrahten;
})();