(function () { "use strict"; // Suchbegriff direkt aus PHP übernehmen (POST hat Vorrang vor GET) var autoSearchTerm = ""; // Sprache direkt aus PHP übernehmen var isEnglish = false; // Prüfen, ob zum normalen Anchor gesprungen werden soll var jumpToAnchor = false && window.location.hash.length > 1; // Alle unterstützten Suchformulare finden var forms = document.querySelectorAll( "#header-suche, #header-suche-eng, #header-suche-desktop" ); // Automatische Suche starten, wenn ein Suchbegriff vorhanden ist // und die aktuelle Seite nicht suche.php ist var shouldStartAutomaticSearch = !/(^|\/)suche\.php$/i.test(window.location.pathname) && autoSearchTerm.trim() !== ""; if (!forms.length) { return; } // Elemente, deren Inhalte nicht durchsucht werden sollen var excludedTags = [ "SCRIPT", "STYLE", "NOSCRIPT", "TEXTAREA", "INPUT", "SELECT", "BUTTON", "OPTION" ]; // Bereits vorhandene Hervorhebungen entfernen function removeHighlights() { var marks = document.querySelectorAll( "mark.header-suche-highlight, mark.header-suche-other-highlight" ); marks.forEach(function (mark) { var parent = mark.parentNode; if (parent) { parent.replaceChild( document.createTextNode(mark.textContent), mark ); parent.normalize(); } }); } // Inhaltsbereich zwischen CONTENT START und CONTENT ENDE ermitteln function getContentArea() { var walker = document.createTreeWalker( document.body, NodeFilter.SHOW_COMMENT, null ); var startComment = null; var endComment = null; var comment; while ((comment = walker.nextNode())) { var value = comment.nodeValue.trim(); if (value === "CONTENT START") { startComment = comment; } if (value === "CONTENT ENDE" && startComment) { endComment = comment; break; } } if (!startComment || !endComment) { return null; } var range = document.createRange(); range.setStartAfter(startComment); range.setEndBefore(endComment); return range; } // Prüfen, ob ein Textknoten durchsucht werden darf function isAllowedNode(node) { var parent = node.parentElement; while (parent) { // Bestimmte HTML-Elemente grundsätzlich ausschließen if (excludedTags.indexOf(parent.tagName) !== -1) { return false; } // Bereits hervorgehobene Texte ausschließen if ( parent.classList && ( parent.classList.contains("header-suche-highlight") || parent.classList.contains("header-suche-other-highlight") ) ) { return false; } // Unsichtbare Elemente ausschließen if (parent.hidden) { return false; } var style = window.getComputedStyle(parent); if ( style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse" || style.opacity === "0" || style.contentVisibility === "hidden" ) { return false; } parent = parent.parentElement; } // Prüfen, ob der Text tatsächlich eine sichtbare Fläche besitzt var range = document.createRange(); range.selectNodeContents(node); var rects = range.getClientRects(); for (var i = 0; i < rects.length; i++) { if (rects[i].width > 0 && rects[i].height > 0) { return true; } } return false; } // Prüfen, ob ein Textknoten vollständig im CONTENT-Bereich liegt function isInsideContentArea(node, contentRange) { try { var startPosition = contentRange.comparePoint(node, 0); var endPosition = contentRange.comparePoint( node, node.nodeValue.length ); return startPosition >= 0 && endPosition <= 0; } catch (e) { return false; } } // Alle Vorkommen des Suchbegriffs im CONTENT-Bereich finden // Weiche Trennzeichen (­) werden dabei ignoriert function findMatches(term) { var matches = []; if (!term) { return matches; } // Weiche Trennzeichen auch im Suchbegriff entfernen var searchTerm = term.replace(/\u00AD/g, "").toLowerCase(); if (!searchTerm) { return matches; } var contentRange = getContentArea(); if (!contentRange) { return matches; } var walker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT, { acceptNode: function (node) { if ( !node.nodeValue || !isAllowedNode(node) || !isInsideContentArea(node, contentRange) ) { return NodeFilter.FILTER_REJECT; } // Weiche Trennzeichen für die Suche entfernen var normalizedText = node.nodeValue.replace( /\u00AD/g, "" ).toLowerCase(); if (normalizedText.indexOf(searchTerm) !== -1) { return NodeFilter.FILTER_ACCEPT; } return NodeFilter.FILTER_REJECT; } } ); var node; while ((node = walker.nextNode())) { var text = node.nodeValue; // Weiche Trennzeichen entfernen und ursprüngliche // Zeichenpositionen für die Hervorhebung merken var normalizedText = ""; var originalPositions = []; for (var i = 0; i < text.length; i++) { if (text.charAt(i) === "\u00AD") { continue; } normalizedText += text.charAt(i); originalPositions.push(i); } var lowerText = normalizedText.toLowerCase(); var position = 0; while ( (position = lowerText.indexOf( searchTerm, position )) !== -1 ) { matches.push({ node: node, start: originalPositions[position], end: originalPositions[ position + searchTerm.length - 1 ] + 1 }); position += searchTerm.length; } } return matches; } // Erstes Vorkommnis nach dem angegebenen Anchor ermitteln function findFirstMatchAfterAnchor(matches) { var anchorId; try { anchorId = decodeURIComponent( window.location.hash.substring(1) ); } catch (e) { anchorId = window.location.hash.substring(1); } var anchor = document.getElementById(anchorId); if (!anchor) { return null; } // Position direkt hinter dem Anchor ermitteln var anchorRange = document.createRange(); anchorRange.selectNode(anchor); anchorRange.collapse(false); for (var i = 0; i < matches.length; i++) { var matchRange = document.createRange(); matchRange.setStart( matches[i].node, matches[i].start ); matchRange.collapse(true); // Liegt der Treffer hinter dem Anchor? if ( anchorRange.compareBoundaryPoints( Range.START_TO_START, matchRange ) <= 0 ) { return { anchor: anchor, index: i }; } } // Anchor gefunden, aber kein Treffer dahinter return { anchor: anchor, index: -1 }; } // Alle Vorkommen hervorheben // Das aktuelle Vorkommen wird gelb, alle anderen orange markiert function highlightMatches(matches, index, deferScroll, scrollElement) { if (!matches.length || index < -1 || index >= matches.length) { return false; } var groups = []; // Fundstellen nach Textknoten gruppieren matches.forEach(function (match, matchIndex) { var group = null; for (var i = 0; i < groups.length; i++) { if (groups[i].node === match.node) { group = groups[i]; break; } } if (!group) { group = { node: match.node, matches: [] }; groups.push(group); } group.matches.push({ start: match.start, end: match.end, index: matchIndex }); }); var selectedMark = null; // Alle Fundstellen innerhalb der Textknoten markieren groups.forEach(function (group) { var node = group.node; if (!node.parentNode) { return; } var text = node.nodeValue; var fragment = document.createDocumentFragment(); var position = 0; group.matches.forEach(function (match) { // Text vor dem Treffer übernehmen if (match.start > position) { fragment.appendChild( document.createTextNode( text.substring(position, match.start) ) ); } var mark = document.createElement("mark"); if (match.index === index) { mark.className = "header-suche-highlight"; mark.style.backgroundColor = "#ffff00"; selectedMark = mark; } else { mark.className = "header-suche-other-highlight"; mark.style.backgroundColor = "#ff9900"; } mark.textContent = text.substring( match.start, match.end ); mark.style.color = "#000000"; mark.style.padding = "3px 0px 2px 0px"; // Innerhalb der Klasse .header den Textschatten entfernen if (node.parentElement.closest(".header")) { mark.style.setProperty( "text-shadow", "none", "important" ); } fragment.appendChild(mark); position = match.end; }); // Verbleibenden Text übernehmen if (position < text.length) { fragment.appendChild( document.createTextNode( text.substring(position) ) ); } node.parentNode.replaceChild(fragment, node); }); if (!selectedMark && !scrollElement) { return false; } // Einheitliche Scrollposition für automatische und manuelle Suche function scrollToMatch() { // Bei jump=no zum normalen Anchor springen, // jedoch 50 Pixel oberhalb des eigentlichen Anchors landen if (scrollElement) { if (!scrollElement.isConnected) { return; } var rect = scrollElement.getBoundingClientRect(); var scrollPosition = window.pageYOffset + rect.top - 50; window.scrollTo({ top: scrollPosition, behavior: "smooth" }); return; } if (!selectedMark || !selectedMark.isConnected) { return; } var rect = selectedMark.getBoundingClientRect(); var scrollPosition = window.pageYOffset + rect.top - (window.innerHeight - rect.height) / 2; window.scrollTo({ top: scrollPosition, behavior: "smooth" }); } if (deferScroll) { function waitForLayout() { requestAnimationFrame(function () { requestAnimationFrame(function () { scrollToMatch(); }); }); } // Erst nach vollständigem Laden der Seite scrollen if (document.readyState === "complete") { waitForLayout(); } else { window.addEventListener( "load", waitForLayout, { once: true } ); } } else { scrollToMatch(); } return true; } // Dialogfenster bei keinen weiteren Ergebnissen function showNoMoreResults(term, state) { // Bereits geöffnetes Dialogfenster vermeiden if (document.getElementById("header-suche-dialog")) { return; } // Hintergrund var overlay = document.createElement("div"); overlay.id = "header-suche-dialog"; overlay.style.position = "fixed"; overlay.style.top = "0"; overlay.style.left = "0"; overlay.style.width = "100%"; overlay.style.height = "100%"; overlay.style.backgroundColor = "rgba(52, 52, 52, 0.70)"; overlay.style.zIndex = "999999"; overlay.style.display = "flex"; overlay.style.alignItems = "center"; overlay.style.justifyContent = "center"; overlay.style.padding = "15px"; overlay.style.boxSizing = "border-box"; // Dialogfenster var dialog = document.createElement("div"); dialog.style.position = "relative"; dialog.style.backgroundColor = "#FFFFFF"; dialog.style.color = "#000000"; dialog.style.width = "100%"; dialog.style.maxWidth = "420px"; dialog.style.padding = "25px"; dialog.style.boxSizing = "border-box"; dialog.style.borderRadius = "8px"; dialog.style.boxShadow = "0 5px 25px rgba(0,0,0,0.3)"; dialog.style.textAlign = "center"; dialog.style.fontFamily = "inherit"; // Schließen-Symbol oben rechts var closeButton = document.createElement("button"); closeButton.type = "button"; closeButton.innerHTML = "×"; closeButton.title = isEnglish ? "Close" : "Schließen"; closeButton.setAttribute( "aria-label", isEnglish ? "Close" : "Schließen" ); closeButton.style.position = "absolute"; closeButton.style.top = "10px"; closeButton.style.right = "10px"; closeButton.style.width = "30px"; closeButton.style.height = "28px"; closeButton.style.padding = "0"; closeButton.style.backgroundColor = "#000040"; closeButton.style.color = "#ffffff"; closeButton.style.border = "none"; closeButton.style.borderRadius = "4px"; closeButton.style.fontSize = "22px"; closeButton.style.lineHeight = "30px"; closeButton.style.cursor = "pointer"; // Hintergrundfarbe bei Mouseover closeButton.addEventListener("mouseenter", function () { closeButton.style.backgroundColor = "#6d081a"; }); closeButton.addEventListener("mouseleave", function () { closeButton.style.backgroundColor = "#000040"; }); // Überschrift var heading = document.createElement("div"); heading.textContent = isEnglish ? "All contents scanned" : "Alle Inhalte gescannt"; heading.style.fontSize = "18px"; heading.style.fontWeight = "bold"; heading.style.marginBottom = "15px"; // Beschreibung var message = document.createElement("div"); if (isEnglish) { message.textContent = state.matches.length === 1 ? "That was the only match on this page. Do you want to search all other pages or images instead?" : "That were all " + state.matches.length + " matches on this page. Do you want to search all other pages or images instead?"; } else { message.textContent = state.matches.length === 1 ? "Das war das einzige Vorkommnis auf dieser Seite. Möchtest Du stattdessen auf allen anderen Seiten oder nach Bildern suchen?" : "Das waren alle " + state.matches.length + " Vorkommnisse auf dieser Seite. Möchtest Du stattdessen auf allen anderen Seiten oder nach Bildern suchen?"; } message.style.fontSize = "15px"; message.style.lineHeight = "1.5"; message.style.marginBottom = "25px"; // Zusätzliches DIV mit KITT oberhalb der Schaltflächen var diary = document.createElement("div"); diary.className = "message-kitt"; diary.style.width = "180px"; diary.style.height = "150px"; diary.style.margin = "-20px auto 20px auto"; diary.style.flexShrink = "0"; // Schaltflächen-Container var buttons = document.createElement("div"); buttons.style.display = "flex"; buttons.style.flexWrap = "wrap"; buttons.style.justifyContent = "center"; buttons.style.gap = "10px"; // Schaltfläche erstellen function createButton(text, backgroundColor) { var button = document.createElement("button"); button.type = "button"; button.textContent = text; button.style.backgroundColor = backgroundColor; button.style.color = "#ffffff"; button.style.border = "none"; button.style.borderRadius = "4px"; button.style.padding = "11px 16px"; button.style.fontSize = "14px"; button.style.cursor = "pointer"; button.style.height = "44px"; button.style.minHeight = "44px"; button.style.boxSizing = "border-box"; button.style.display = "flex"; button.style.alignItems = "center"; button.style.justifyContent = "center"; button.style.flex = "1 1 auto"; // Hintergrundfarbe bei Mouseover button.addEventListener("mouseenter", function () { button.style.backgroundColor = "#6d081a"; }); button.addEventListener("mouseleave", function () { button.style.backgroundColor = backgroundColor; }); return button; } // Dialog schließen function closeDialog() { document.removeEventListener( "keydown", handleEscape ); if (overlay.parentNode) { overlay.parentNode.removeChild(overlay); } } // ESC-Taste function handleEscape(event) { if (event.key === "Escape") { closeWithoutChangingPosition(); } } // Fenster schließen, ohne das aktuelle Highlight zu verändern function closeWithoutChangingPosition() { closeDialog(); var mainSearchButton = document.getElementById("searchbutton"); if (mainSearchButton) { mainSearchButton.focus(); } } // Alle Unterseiten durchsuchen var searchButton = createButton( isEnglish ? "All pages" : "Alle Seiten", "#000040" ); searchButton.addEventListener("click", function () { closeDialog(); window.location.href = "suche.php?term=" + encodeURIComponent('' + term + ''); }); // Bilder durchsuchen var imageButton = createButton( isEnglish ? "Images" : "Bilder", "#000040" ); imageButton.addEventListener("click", function () { closeDialog(); window.location.href = "suche.php?mode=images&term=" + encodeURIComponent('' + term + ''); }); // Abbrechen var cancelButton = createButton( isEnglish ? "Back to the first match" : "Zurück zum ersten Vorkommen", "#777777" ); // Beide Suchbuttons gleich breit searchButton.style.flex = "1 1 calc(50% - 5px)"; imageButton.style.flex = "1 1 calc(50% - 5px)"; // Abbrechen in eine eigene Zeile cancelButton.style.flex = "1 1 100%"; function cancelSearch() { closeDialog(); // Wieder zum ersten Ergebnis springen state.currentIndex = 0; removeHighlights(); state.matches = findMatches(term); if (state.matches.length > 0) { highlightMatches(state.matches, 0); } // Fokus wieder auf den Suchbutton setzen var mainSearchButton = document.getElementById("searchbutton"); if (mainSearchButton) { mainSearchButton.focus(); } } cancelButton.addEventListener("click", cancelSearch); // Klick auf das X: Fenster schließen, Position beibehalten // und Suchleiste ausblenden closeButton.addEventListener("click", function () { closeWithoutChangingPosition(); var suchleiste = document.getElementById("suchleiste"); if (suchleiste) { suchleiste.style.display = "none"; } }); // Elemente zusammensetzen buttons.appendChild(searchButton); buttons.appendChild(imageButton); buttons.appendChild(cancelButton); dialog.appendChild(closeButton); dialog.appendChild(heading); dialog.appendChild(message); dialog.appendChild(diary); dialog.appendChild(buttons); overlay.appendChild(dialog); // Klick auf den Hintergrund schließt den Dialog overlay.addEventListener("click", function (event) { if (event.target === overlay) { cancelSearch(); } }); document.addEventListener("keydown", handleEscape); document.body.appendChild(overlay); // Fokus auf "Zurück zum ersten Vorkommen" setzen cancelButton.focus(); } // Jedes Formular separat initialisieren forms.forEach(function (form) { var input = form.querySelector('input[name="term"]'); var mode = form.querySelector('select[name="mode"]'); if (!input || !mode) { return; } // Eigener Suchstatus für jedes Formular var state = { currentTerm: "", currentIndex: -1, matches: [] }; // Highlight bei Klick oder Tippen außerhalb der Suche entfernen document.addEventListener("click", function (event) { // Klicks innerhalb eines Suchformulars oder des Dialogfensters ignorieren if ( event.target.closest("#header-suche, #header-suche-eng, #header-suche-desktop, #header-suche-dialog") ) { return; } removeHighlights(); // Suchstatus zurücksetzen state.currentTerm = ""; state.currentIndex = -1; state.matches = []; }); // Suche neu starten function startSearch(term, deferScroll, useAnchor) { removeHighlights(); state.currentTerm = term; state.currentIndex = -1; state.matches = findMatches(term); if (state.matches.length > 0) { var scrollElement = null; var highlightIndex = 0; state.currentIndex = 0; // Sonderfall: Zum normalen Anchor springen if (useAnchor) { var anchorInfo = findFirstMatchAfterAnchor( state.matches ); if (anchorInfo) { scrollElement = anchorInfo.anchor; if (anchorInfo.index >= 0) { state.currentIndex = anchorInfo.index; highlightIndex = anchorInfo.index; } else { // Kein Treffer hinter dem Anchor: // Beim nächsten Klick den Dialog anzeigen state.currentIndex = state.matches.length - 1; highlightIndex = -1; } } } highlightMatches( state.matches, highlightIndex, deferScroll, scrollElement ); } } // Automatische Suche über PHP übergebenen GET- oder POST-Parameter if (shouldStartAutomaticSearch) { mode.value = "here"; input.value = autoSearchTerm.trim(); // Alle Treffer markieren und zum Anchor bzw. ersten Treffer springen startSearch(input.value, true, jumpToAnchor); // Fokus auf den Suchbutton setzen, damit Enter zum nächsten Treffer springt var mainSearchButton = document.getElementById("searchbutton"); if (mainSearchButton) { mainSearchButton.focus(); } } // Suche beim Tippen starten input.addEventListener("input", function () { if (mode.value !== "here") { return; } var term = input.value.trim(); if (term !== state.currentTerm) { startSearch(term); } }); // Formular absenden form.addEventListener("submit", function (event) { // Normale Suche für Inhalte und Bilder if (mode.value !== "here") { return; } // Suche auf dieser Seite event.preventDefault(); var term = input.value.trim(); if (!term) { return; } // Falls der Begriff noch nicht gesucht wurde if (term !== state.currentTerm) { startSearch(term); return; } // Falls keine Ergebnisse vorhanden sind if (state.matches.length === 0) { showNoMoreResults(term, state); return; } // Zum nächsten Vorkommen springen state.currentIndex++; // Hervorhebungen entfernen und Fundstellen neu ermitteln removeHighlights(); state.matches = findMatches(term); if (state.currentIndex < state.matches.length) { highlightMatches( state.matches, state.currentIndex ); } else { showNoMoreResults(term, state); } }); }); })();