fix js structure
Sonarqube Scanner / Build and analyze (push) Failing after 29s

This commit is contained in:
Florian Zumpe
2026-08-30 16:23:44 +02:00
parent 3f390f6ec4
commit 17e4421e28
+264 -182
View File
@@ -1,18 +1,22 @@
(function (ns) { (function (ns) {
'use strict'; 'use strict';
const dialog = document.getElementById('info-dialog');
const title = document.getElementById('info-dialog-title');
const menu = document.getElementById('info-menu');
const backButton = document.getElementById('info-back');
const closeButton = document.getElementById('info-close');
const infoButton = document.getElementById('info-button');
const pageTitles = Object.freeze({ const pageTitles = Object.freeze({
privacy: 'Datenschutzerklärung', privacy: 'Datenschutzerklärung',
imprint: 'Impressum', imprint: 'Impressum',
technology: 'Cookies und Technik' 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) { function ready(callback) {
if (document.readyState === 'loading') { if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', callback, { once: true }); document.addEventListener('DOMContentLoaded', callback, { once: true });
@@ -21,7 +25,22 @@
callback(); 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 = '') { function createElement(tagName, attributes = {}, text = '') {
if (typeof document.createElement !== 'function') return null;
const element = document.createElement(tagName); const element = document.createElement(tagName);
Object.entries(attributes).forEach(([name, value]) => { Object.entries(attributes).forEach(([name, value]) => {
if (value === null || value === undefined) return; if (value === null || value === undefined) return;
@@ -41,20 +60,52 @@
return element; return element;
} }
function ensureInfoControl() { function setAttribute(element, name, value) {
let control = document.querySelector('.info-control'); if (!element || typeof element.setAttribute !== 'function') return;
let button = document.getElementById('info-button') element.setAttribute(name, value);
?? document.querySelector('[aria-controls="info-dropdown"], .info-button'); }
let dropdown = document.getElementById('info-dropdown')
?? document.querySelector('.info-dropdown');
if (!control) { 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' }); control = createElement('div', { className: 'info-control' });
document.body.insertBefore(control, document.body.firstChild); prependToBody(control);
} }
if (!button) { if (!infoButton && control) {
button = createElement('button', { infoButton = createElement('button', {
id: 'info-button', id: 'info-button',
className: 'info-button', className: 'info-button',
type: 'button', type: 'button',
@@ -62,217 +113,226 @@
title: 'Informationen' title: 'Informationen'
}); });
const icon = createElement('span', { 'aria-hidden': 'true' }, 'i'); const icon = createElement('span', { 'aria-hidden': 'true' }, 'i');
button.appendChild(icon); append(infoButton, icon);
append(control, infoButton);
} }
if (button.parentElement !== control) { if (!dropdown && control) {
control.insertBefore(button, control.firstChild);
}
button.id = 'info-button';
button.classList.add('info-button');
button.setAttribute('type', 'button');
button.setAttribute('aria-haspopup', 'true');
button.setAttribute('aria-expanded', 'false');
button.setAttribute('aria-controls', 'info-dropdown');
if (!button.getAttribute('aria-label')) button.setAttribute('aria-label', 'Informationsmenü öffnen');
if (!button.textContent.trim()) {
const icon = createElement('span', { 'aria-hidden': 'true' }, 'i');
button.appendChild(icon);
}
if (!dropdown) {
dropdown = createElement('nav', { dropdown = createElement('nav', {
id: 'info-dropdown', id: 'info-dropdown',
className: 'info-dropdown', className: 'info-dropdown',
'aria-label': 'Rechtliche und technische Informationen' 'aria-label': 'Rechtliche und technische Informationen'
}); });
append(control, dropdown);
} }
if (dropdown.parentElement !== control) { if (infoButton) {
control.appendChild(dropdown); setAttribute(infoButton, 'type', 'button');
} setAttribute(infoButton, 'aria-haspopup', 'true');
setAttribute(infoButton, 'aria-expanded', 'false');
dropdown.id = 'info-dropdown'; setAttribute(infoButton, 'aria-controls', 'info-dropdown');
dropdown.classList.add('info-dropdown'); if (typeof infoButton.getAttribute !== 'function' || !infoButton.getAttribute('aria-label')) {
dropdown.setAttribute('aria-label', dropdown.getAttribute('aria-label') ?? 'Rechtliche und technische Informationen'); setAttribute(infoButton, 'aria-label', 'Informationsmenü öffnen');
dropdown.hidden = true;
menuEntries.forEach(([target, label]) => {
let menuButton = dropdown.querySelector(`[data-info-target="${target}"]`);
if (!menuButton) {
menuButton = createElement('button', {
type: 'button',
dataset: { infoTarget: target }
}, label);
dropdown.appendChild(menuButton);
} }
menuButton.setAttribute('type', 'button'); if (!String(infoButton.textContent ?? '').trim() && typeof document.createElement === 'function') {
menuButton.dataset.infoTarget = target; append(infoButton, createElement('span', { 'aria-hidden': 'true' }, 'i'));
if (!menuButton.textContent.trim()) menuButton.textContent = label; }
}); }
return { control, button, dropdown }; if (dropdown) {
} setAttribute(dropdown, 'aria-label', 'Rechtliche und technische Informationen');
dropdown.hidden = true;
function moveExistingPagesInto(content, dialog) { menuEntries.forEach(([target, label]) => {
dialog.querySelectorAll('[data-info-page]').forEach(page => { let button = qs(`[data-info-target="${target}"]`, dropdown)
if (page.parentElement !== content) content.appendChild(page); || 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) { function ensureInfoPage(content, name) {
let page = content.querySelector(`[data-info-page="${name}"]`) let page = qs(`[data-info-page="${name}"]`, content) || qs(`[data-info-page="${name}"]`);
?? document.querySelector(`[data-info-page="${name}"]`); if (!page && content) {
if (!page) {
page = createElement('article', { page = createElement('article', {
className: 'info-page', className: 'info-page',
dataset: { infoPage: name } dataset: { infoPage: name }
}); });
page.appendChild(createElement('p', {}, `${pageTitles[name]} ist noch nicht hinterlegt.`)); append(page, createElement('p', {}, `${pageTitles[name]} ist noch nicht hinterlegt.`));
content.appendChild(page); append(content, page);
}
if (page) {
page.dataset.infoPage = name;
if (name !== 'privacy') page.hidden = true;
} }
page.classList.add('info-page');
page.dataset.infoPage = name;
page.hidden = true;
if (page.parentElement !== content) content.appendChild(page);
return page; return page;
} }
function ensureInfoDialog() { function ensureInfoDialog() {
let dialog = document.getElementById('info-dialog'); let dialog = byId('info-dialog') || qs('#info-dialog');
if (!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', { dialog = createElement('dialog', {
id: 'info-dialog', id: 'info-dialog',
className: 'info-dialog', className: 'info-dialog',
'aria-labelledby': 'info-dialog-title' 'aria-labelledby': 'info-dialog-title'
}); });
document.body.appendChild(dialog); append(document.body, dialog);
} }
dialog.id = 'info-dialog'; if (dialog && (!title || !closeButton || !content) && typeof document.createElement === 'function') {
dialog.classList.add('info-dialog'); let shell = qs('.info-dialog-shell', dialog);
dialog.setAttribute('aria-labelledby', 'info-dialog-title'); if (!shell) {
shell = createElement('div', { className: 'info-dialog-shell' });
append(dialog, shell);
}
let shell = dialog.querySelector('.info-dialog-shell'); let header = qs('.info-dialog-header', shell);
if (!shell) { if (!header) {
shell = createElement('div', { className: 'info-dialog-shell' }); header = createElement('header', { className: 'info-dialog-header' });
while (dialog.firstChild) shell.appendChild(dialog.firstChild); append(shell, header);
dialog.appendChild(shell); }
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);
}
} }
let header = shell.querySelector('.info-dialog-header'); if (dialog) {
if (!header) { setAttribute(dialog, 'aria-labelledby', 'info-dialog-title');
header = createElement('header', { className: 'info-dialog-header' }); }
shell.insertBefore(header, shell.firstChild); if (closeButton) {
setAttribute(closeButton, 'type', 'button');
setAttribute(closeButton, 'aria-label', 'Informationsfenster schließen');
if (!String(closeButton.textContent ?? '').trim()) closeButton.textContent = '×';
} }
let title = document.getElementById('info-dialog-title') if (content) {
?? header.querySelector('h1, h2, .info-dialog-title'); Object.keys(pageTitles).forEach(name => ensureInfoPage(content, name));
if (!title) {
title = createElement('h2', { id: 'info-dialog-title' }, 'Informationen');
header.insertBefore(title, header.firstChild);
} }
title.id = 'info-dialog-title';
if (!title.textContent.trim()) title.textContent = 'Informationen';
let closeButton = document.getElementById('info-close')
?? header.querySelector('[data-info-close], .info-close, .info-dialog-close, button[aria-label="Dialog schließen"], button[aria-label="Schließen"]');
if (!closeButton) {
closeButton = createElement('button', {
id: 'info-close',
type: 'button',
'aria-label': 'Informationsfenster schließen',
title: 'Schließen'
}, '×');
header.appendChild(closeButton);
}
closeButton.id = 'info-close';
closeButton.setAttribute('type', 'button');
closeButton.setAttribute('aria-label', closeButton.getAttribute('aria-label') ?? 'Informationsfenster schließen');
closeButton.setAttribute('title', closeButton.getAttribute('title') ?? 'Schließen');
if (!closeButton.textContent.trim()) closeButton.textContent = '×';
if (closeButton.parentElement !== header) header.appendChild(closeButton);
let content = shell.querySelector('.info-dialog-content');
if (!content) {
content = createElement('div', { className: 'info-dialog-content' });
shell.appendChild(content);
}
moveExistingPagesInto(content, dialog);
Object.keys(pageTitles).forEach(name => ensureInfoPage(content, name));
return { dialog, title, closeButton, content }; return { dialog, title, closeButton, content };
} }
function ensureInfoUi() { function ensureInfoUi() {
const control = ensureInfoControl();
const dialog = ensureInfoDialog();
return { return {
control: control.control, ...ensureInfoControl(),
infoButton: control.button, ...ensureInfoDialog()
dropdown: control.dropdown,
dialog: dialog.dialog,
title: dialog.title,
closeButton: dialog.closeButton,
content: dialog.content
}; };
} }
function isActivation(event) { 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 === ' '; if (event.type === 'keydown') return event.key === 'Enter' || event.key === ' ';
if (event.type !== 'pointerup') return false; return false;
if (event.isPrimary === false) return false;
return event.pointerType !== 'mouse' || event.button === 0;
} }
function activate(element, handler) { 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 => { element.addEventListener('pointerup', event => {
if (!isActivation(event)) return; if (!isActivation(event)) return;
event.preventDefault(); if (typeof event.preventDefault === 'function') event.preventDefault();
event.stopPropagation(); if (typeof event.stopPropagation === 'function') event.stopPropagation();
handler(event); handler(event);
}); });
element.addEventListener('keydown', event => { element.addEventListener('keydown', event => {
if (!isActivation(event)) return; if (!isActivation(event)) return;
event.preventDefault(); if (typeof event.preventDefault === 'function') event.preventDefault();
event.stopPropagation(); if (typeof event.stopPropagation === 'function') event.stopPropagation();
handler(event); handler(event);
}); });
} }
function syncStorageToggles() { function setDropdownOpen(open) {
const enabled = isProgressStorageEnabled(); if (!elements?.dropdown || !elements?.infoButton) return;
document.querySelectorAll('.progress-storage-toggle').forEach(toggle => { dropdownOpen = Boolean(open);
toggle.checked = enabled; elements.dropdown.hidden = !dropdownOpen;
}); toggleClass(elements.dropdown, 'is-open', dropdownOpen);
document.querySelectorAll('.cookie-toggle').forEach(toggle => { setAttribute(elements.infoButton, 'aria-expanded', dropdownOpen ? 'true' : 'false');
toggle.checked = false;
});
} }
function showMenu() { function toggleDropdown() {
title.textContent = 'Informationen'; setDropdownOpen(!dropdownOpen);
menu.hidden = false; }
backButton.hidden = true;
document.querySelectorAll('[data-info-page]').forEach(page => { function elementContains(element, target) {
page.hidden = true; 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) { function showPage(name) {
const pageName = Object.prototype.hasOwnProperty.call(pageTitles, name) ? name : 'privacy'; const pageName = Object.prototype.hasOwnProperty.call(pageTitles, name) ? name : 'privacy';
const page = elements?.content?.querySelector(`[data-info-page="${pageName}"]`) const page = qs(`[data-info-page="${pageName}"]`, elements?.content) || qs(`[data-info-page="${pageName}"]`);
?? document.querySelector(`[data-info-page="${pageName}"]`);
if (!page) return; if (!page) return;
title.textContent = pageTitles[name] ?? 'Informationen'; if (elements?.title) elements.title.textContent = pageTitles[pageName] ?? 'Informationen';
menu.hidden = true; qsa('[data-info-page]').forEach(item => {
backButton.hidden = false;
document.querySelectorAll('[data-info-page]').forEach(item => {
item.hidden = item !== page; item.hidden = item !== page;
}); });
if (elements?.content) elements.content.scrollTop = 0; if (elements?.content) elements.content.scrollTop = 0;
@@ -287,25 +347,32 @@
} }
} }
function openDialog() { function openDialog(name = 'privacy') {
if (dialog.open) return; if (!elements?.dialog) return;
showMenu(); const pageName = Object.prototype.hasOwnProperty.call(pageTitles, name) ? name : 'privacy';
setDropdownOpen(false);
showPage(pageName);
syncStorageToggles(); syncStorageToggles();
if (typeof dialog.showModal === 'function') dialog.showModal(); if (!elements.dialog.open) {
else dialog.setAttribute('open', ''); if (typeof elements.dialog.showModal === 'function') elements.dialog.showModal();
closeButton.focus(); else setAttribute(elements.dialog, 'open', '');
}
focusSafely(elements.closeButton ?? elements.dialog);
} }
function closeDialog() { function closeDialog() {
if (typeof dialog.close === 'function') dialog.close(); if (elements?.dialog) {
else dialog.removeAttribute('open'); if (typeof elements.dialog.close === 'function') elements.dialog.close();
infoButton.focus(); else if (typeof elements.dialog.removeAttribute === 'function') elements.dialog.removeAttribute('open');
}
setDropdownOpen(false);
focusSafely(elements?.infoButton);
} }
function bindStorageToggles() { function bindStorageToggles() {
document.querySelectorAll('.progress-storage-toggle').forEach(toggle => { qsa('.progress-storage-toggle').forEach(toggle => {
if (toggle.dataset.wegwichtelStorageBound === 'true') return; if (toggle.dataset?.wegwichtelStorageBound === 'true') return;
toggle.dataset.wegwichtelStorageBound = 'true'; if (toggle.dataset) toggle.dataset.wegwichtelStorageBound = 'true';
toggle.addEventListener('change', event => { toggle.addEventListener('change', event => {
const enabled = setProgressStorageEnabled(event.currentTarget.checked); const enabled = setProgressStorageEnabled(event.currentTarget.checked);
syncStorageToggles(); syncStorageToggles();
@@ -316,23 +383,38 @@
}); });
} }
activate(infoButton, openDialog); function bindDialog() {
activate(closeButton, closeDialog); activate(elements?.infoButton, toggleDropdown);
activate(backButton, showMenu); activate(elements?.closeButton, closeDialog);
document.querySelectorAll('[data-info-target]').forEach(button => {
activate(button, () => showPage(button.dataset.infoTarget));
});
if (elements.dialog.dataset.wegwichtelLegalUiDialogBound !== 'true') { qsa('[data-info-target]', elements?.dropdown).forEach(button => {
elements.dialog.dataset.wegwichtelLegalUiDialogBound = 'true'; 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 => { elements.dialog.addEventListener('pointerup', event => {
if (event.target !== elements.dialog || !isActivation(event)) return; if (event.target !== elements.dialog || !isActivation(event)) return;
event.preventDefault(); if (typeof event.preventDefault === 'function') event.preventDefault();
event.stopPropagation(); if (typeof event.stopPropagation === 'function') event.stopPropagation();
closeDialog(); closeDialog();
}); });
elements.dialog.addEventListener('cancel', event => { elements.dialog.addEventListener('cancel', event => {
event.preventDefault(); if (typeof event.preventDefault === 'function') event.preventDefault();
closeDialog(); closeDialog();
}); });
} }
@@ -353,5 +435,5 @@
}); });
} }
ns.LegalUi = Object.freeze({ open: openDialog, close: closeDialog, syncStorageToggles }); ready(init);
}(globalThis.Wegwichtel ??= {})); }(globalThis.Wegwichtel = globalThis.Wegwichtel || {}));