Files
Wegwichtel/public/js/legal-ui.js
T
Florian Zumpe 17e4421e28
Sonarqube Scanner / Build and analyze (push) Failing after 29s
fix js structure
2026-08-30 16:23:44 +02:00

440 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function (ns) {
'use strict';
const pageTitles = Object.freeze({
privacy: 'Datenschutzerklärung',
imprint: 'Impressum',
technology: 'Cookies und Technik'
});
const menuEntries = Object.freeze([
['privacy', 'Datenschutzerklärung'],
['imprint', 'Impressum'],
['technology', 'Cookies und verwendete JavaScripts']
]);
let elements = null;
let dropdownOpen = false;
let documentEventsBound = false;
function ready(callback) {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', callback, { once: true });
return;
}
callback();
}
function byId(id) {
return typeof document.getElementById === 'function' ? document.getElementById(id) : null;
}
function qs(selector, root = document) {
return root && typeof root.querySelector === 'function' ? root.querySelector(selector) : null;
}
function qsa(selector, root = document) {
if (root && typeof root.querySelectorAll === 'function') return Array.from(root.querySelectorAll(selector));
if (root !== document && typeof document.querySelectorAll === 'function') return Array.from(document.querySelectorAll(selector));
return [];
}
function createElement(tagName, attributes = {}, text = '') {
if (typeof document.createElement !== 'function') return null;
const element = document.createElement(tagName);
Object.entries(attributes).forEach(([name, value]) => {
if (value === null || value === undefined) return;
if (name === 'className') {
element.className = value;
return;
}
if (name === 'dataset') {
Object.entries(value).forEach(([key, dataValue]) => {
element.dataset[key] = dataValue;
});
return;
}
element.setAttribute(name, value);
});
if (text) element.textContent = text;
return element;
}
function setAttribute(element, name, value) {
if (!element || typeof element.setAttribute !== 'function') return;
element.setAttribute(name, value);
}
function toggleClass(element, className, enabled) {
if (!element || !element.classList) return;
if (typeof element.classList.toggle === 'function') {
element.classList.toggle(className, enabled);
return;
}
if (enabled && typeof element.classList.add === 'function') element.classList.add(className);
if (!enabled && typeof element.classList.remove === 'function') element.classList.remove(className);
}
function append(parent, child) {
if (!parent || !child || typeof parent.appendChild !== 'function') return false;
parent.appendChild(child);
return true;
}
function prependToBody(element) {
if (!document.body || !element) return false;
if (typeof document.body.prepend === 'function') {
document.body.prepend(element);
return true;
}
if (typeof document.body.insertBefore === 'function') {
document.body.insertBefore(element, document.body.firstChild ?? null);
return true;
}
return append(document.body, element);
}
function ensureInfoControl() {
let control = qs('.info-control');
let infoButton = byId('info-button') || qs('.info-button');
let dropdown = byId('info-dropdown') || qs('.info-dropdown');
if (!control && typeof document.createElement === 'function') {
control = createElement('div', { className: 'info-control' });
prependToBody(control);
}
if (!infoButton && control) {
infoButton = createElement('button', {
id: 'info-button',
className: 'info-button',
type: 'button',
'aria-label': 'Informationsmenü öffnen',
title: 'Informationen'
});
const icon = createElement('span', { 'aria-hidden': 'true' }, 'i');
append(infoButton, icon);
append(control, infoButton);
}
if (!dropdown && control) {
dropdown = createElement('nav', {
id: 'info-dropdown',
className: 'info-dropdown',
'aria-label': 'Rechtliche und technische Informationen'
});
append(control, dropdown);
}
if (infoButton) {
setAttribute(infoButton, 'type', 'button');
setAttribute(infoButton, 'aria-haspopup', 'true');
setAttribute(infoButton, 'aria-expanded', 'false');
setAttribute(infoButton, 'aria-controls', 'info-dropdown');
if (typeof infoButton.getAttribute !== 'function' || !infoButton.getAttribute('aria-label')) {
setAttribute(infoButton, 'aria-label', 'Informationsmenü öffnen');
}
if (!String(infoButton.textContent ?? '').trim() && typeof document.createElement === 'function') {
append(infoButton, createElement('span', { 'aria-hidden': 'true' }, 'i'));
}
}
if (dropdown) {
setAttribute(dropdown, 'aria-label', 'Rechtliche und technische Informationen');
dropdown.hidden = true;
menuEntries.forEach(([target, label]) => {
let button = qs(`[data-info-target="${target}"]`, dropdown)
|| qsa('[data-info-target]').find(item => item.dataset?.infoTarget === target);
if (!button && typeof document.createElement === 'function') {
button = createElement('button', {
type: 'button',
dataset: { infoTarget: target }
}, label);
append(dropdown, button);
}
if (!button) return;
setAttribute(button, 'type', 'button');
button.dataset.infoTarget = target;
if (!String(button.textContent ?? '').trim()) button.textContent = label;
});
}
return { control, infoButton, dropdown };
}
function ensureInfoPage(content, name) {
let page = qs(`[data-info-page="${name}"]`, content) || qs(`[data-info-page="${name}"]`);
if (!page && content) {
page = createElement('article', {
className: 'info-page',
dataset: { infoPage: name }
});
append(page, createElement('p', {}, `${pageTitles[name]} ist noch nicht hinterlegt.`));
append(content, page);
}
if (page) {
page.dataset.infoPage = name;
if (name !== 'privacy') page.hidden = true;
}
return page;
}
function ensureInfoDialog() {
let dialog = byId('info-dialog') || qs('#info-dialog');
let title = byId('info-dialog-title') || qs('#info-dialog-title', dialog);
let closeButton = byId('info-close') || qs('#info-close', dialog);
let content = qs('.info-dialog-content', dialog) || qs('.info-dialog-content');
if (!dialog && typeof document.createElement === 'function') {
dialog = createElement('dialog', {
id: 'info-dialog',
className: 'info-dialog',
'aria-labelledby': 'info-dialog-title'
});
append(document.body, dialog);
}
if (dialog && (!title || !closeButton || !content) && typeof document.createElement === 'function') {
let shell = qs('.info-dialog-shell', dialog);
if (!shell) {
shell = createElement('div', { className: 'info-dialog-shell' });
append(dialog, shell);
}
let header = qs('.info-dialog-header', shell);
if (!header) {
header = createElement('header', { className: 'info-dialog-header' });
append(shell, header);
}
if (!title) {
title = createElement('h2', { id: 'info-dialog-title' }, pageTitles.privacy);
append(header, title);
}
if (!closeButton) {
closeButton = createElement('button', {
id: 'info-close',
type: 'button',
'aria-label': 'Informationsfenster schließen',
title: 'Schließen'
}, '×');
append(header, closeButton);
}
if (!content) {
content = createElement('div', { className: 'info-dialog-content' });
append(shell, content);
}
}
if (dialog) {
setAttribute(dialog, 'aria-labelledby', 'info-dialog-title');
}
if (closeButton) {
setAttribute(closeButton, 'type', 'button');
setAttribute(closeButton, 'aria-label', 'Informationsfenster schließen');
if (!String(closeButton.textContent ?? '').trim()) closeButton.textContent = '×';
}
if (content) {
Object.keys(pageTitles).forEach(name => ensureInfoPage(content, name));
}
return { dialog, title, closeButton, content };
}
function ensureInfoUi() {
return {
...ensureInfoControl(),
...ensureInfoDialog()
};
}
function isActivation(event) {
if (!event) return false;
if (event.type === 'pointerup') {
if (event.isPrimary === false) return false;
if (event.pointerType === 'mouse' && event.button !== 0) return false;
return true;
}
if (event.type === 'keydown') return event.key === 'Enter' || event.key === ' ';
return false;
}
function activate(element, handler) {
if (!element || typeof element.addEventListener !== 'function') return;
if (element.dataset) {
const key = handler.name ? `wegwichtelLegalUiBound${handler.name}` : 'wegwichtelLegalUiBound';
if (element.dataset[key] === 'true') return;
element.dataset[key] = 'true';
}
element.addEventListener('pointerup', event => {
if (!isActivation(event)) return;
if (typeof event.preventDefault === 'function') event.preventDefault();
if (typeof event.stopPropagation === 'function') event.stopPropagation();
handler(event);
});
element.addEventListener('keydown', event => {
if (!isActivation(event)) return;
if (typeof event.preventDefault === 'function') event.preventDefault();
if (typeof event.stopPropagation === 'function') event.stopPropagation();
handler(event);
});
}
function setDropdownOpen(open) {
if (!elements?.dropdown || !elements?.infoButton) return;
dropdownOpen = Boolean(open);
elements.dropdown.hidden = !dropdownOpen;
toggleClass(elements.dropdown, 'is-open', dropdownOpen);
setAttribute(elements.infoButton, 'aria-expanded', dropdownOpen ? 'true' : 'false');
}
function toggleDropdown() {
setDropdownOpen(!dropdownOpen);
}
function elementContains(element, target) {
if (!element || !target) return false;
if (element === target) return true;
return typeof element.contains === 'function' && element.contains(target);
}
function isInsideInfoControl(target) {
return elementContains(elements?.infoButton, target) || elementContains(elements?.dropdown, target);
}
function isProgressStorageEnabled() {
if (!ns.PrivacyStorage || typeof ns.PrivacyStorage.isProgressStorageEnabled !== 'function') return false;
return ns.PrivacyStorage.isProgressStorageEnabled();
}
function setProgressStorageEnabled(enabled) {
if (!ns.PrivacyStorage || typeof ns.PrivacyStorage.setProgressStorageEnabled !== 'function') return false;
return ns.PrivacyStorage.setProgressStorageEnabled(enabled);
}
function syncStorageToggles() {
const enabled = isProgressStorageEnabled();
qsa('.progress-storage-toggle').forEach(toggle => {
toggle.checked = enabled;
});
qsa('.cookie-toggle').forEach(toggle => {
toggle.checked = false;
});
}
function showPage(name) {
const pageName = Object.prototype.hasOwnProperty.call(pageTitles, name) ? name : 'privacy';
const page = qs(`[data-info-page="${pageName}"]`, elements?.content) || qs(`[data-info-page="${pageName}"]`);
if (!page) return;
if (elements?.title) elements.title.textContent = pageTitles[pageName] ?? 'Informationen';
qsa('[data-info-page]').forEach(item => {
item.hidden = item !== page;
});
if (elements?.content) elements.content.scrollTop = 0;
}
function focusSafely(element) {
if (!element || typeof element.focus !== 'function') return;
try {
element.focus({ preventScroll: true });
} catch {
element.focus();
}
}
function openDialog(name = 'privacy') {
if (!elements?.dialog) return;
const pageName = Object.prototype.hasOwnProperty.call(pageTitles, name) ? name : 'privacy';
setDropdownOpen(false);
showPage(pageName);
syncStorageToggles();
if (!elements.dialog.open) {
if (typeof elements.dialog.showModal === 'function') elements.dialog.showModal();
else setAttribute(elements.dialog, 'open', '');
}
focusSafely(elements.closeButton ?? elements.dialog);
}
function closeDialog() {
if (elements?.dialog) {
if (typeof elements.dialog.close === 'function') elements.dialog.close();
else if (typeof elements.dialog.removeAttribute === 'function') elements.dialog.removeAttribute('open');
}
setDropdownOpen(false);
focusSafely(elements?.infoButton);
}
function bindStorageToggles() {
qsa('.progress-storage-toggle').forEach(toggle => {
if (toggle.dataset?.wegwichtelStorageBound === 'true') return;
if (toggle.dataset) toggle.dataset.wegwichtelStorageBound = 'true';
toggle.addEventListener('change', event => {
const enabled = setProgressStorageEnabled(event.currentTarget.checked);
syncStorageToggles();
document.dispatchEvent(new CustomEvent('wegwichtel:progress-storage-changed', {
detail: { enabled }
}));
});
});
}
function bindDialog() {
activate(elements?.infoButton, toggleDropdown);
activate(elements?.closeButton, closeDialog);
qsa('[data-info-target]', elements?.dropdown).forEach(button => {
activate(button, () => openDialog(button.dataset?.infoTarget));
});
if (!documentEventsBound) {
documentEventsBound = true;
document.addEventListener('pointerup', event => {
if (!dropdownOpen || isInsideInfoControl(event.target)) return;
setDropdownOpen(false);
});
document.addEventListener('keydown', event => {
if (!dropdownOpen || event.key !== 'Escape' || elements?.dialog?.open) return;
if (typeof event.preventDefault === 'function') event.preventDefault();
setDropdownOpen(false);
focusSafely(elements?.infoButton);
});
}
if (elements?.dialog && elements.dialog.dataset?.wegwichtelLegalUiDialogBound !== 'true') {
if (elements.dialog.dataset) elements.dialog.dataset.wegwichtelLegalUiDialogBound = 'true';
elements.dialog.addEventListener('pointerup', event => {
if (event.target !== elements.dialog || !isActivation(event)) return;
if (typeof event.preventDefault === 'function') event.preventDefault();
if (typeof event.stopPropagation === 'function') event.stopPropagation();
closeDialog();
});
elements.dialog.addEventListener('cancel', event => {
if (typeof event.preventDefault === 'function') event.preventDefault();
closeDialog();
});
}
}
function init() {
elements = ensureInfoUi();
bindDialog();
bindStorageToggles();
syncStorageToggles();
ns.LegalUi = Object.freeze({
open: openDialog,
close: closeDialog,
toggleMenu: toggleDropdown,
closeMenu: () => setDropdownOpen(false),
syncStorageToggles
});
}
ready(init);
}(globalThis.Wegwichtel = globalThis.Wegwichtel || {}));