PageSourceSearch

https://www.spie-rodias.de/wp-content/themes/robur/js/search.js?ver=2.43.3

js spie-rodias.de collected 2026-09-28 05:37:04 UTC 7,540 bytes, 287 lines download raw bytes

1// Schließt das Such-Modal und setzt den Suchzustand zurück.
2// Global, damit auch custom.js (Quersteuerung via #nav-toggle) darauf zugreifen kann.
3function closeSearch() {
4	const modal = document.getElementById('search-modal');
5
6	if (!modal) {
7		return;
8	}
9
10	modal.classList.remove('active');
11	document.body.classList.remove('search-open');
12
13	jQuery('#show-popup').show();
14}
15
16jQuery(document).ready(function ($) {
17	$('.menu-search').click(function () {
18		document.getElementById('search-modal').classList.add('active');
19		$('#show-popup, #contact-popup, .eael-ext-scroll-to-top-wrap').hide();
20
21		document.body.classList.add('search-open');
22	});
23
24	$('#search-close').click(function () {
25		closeSearch();
26	});
27
28	let backdropMouseDown = false;
29
30	$('#search-modal').on('mousedown', function (evt) {
31		backdropMouseDown = evt.target === this;
32	});
33
34	$('#search-modal').on('click', function (evt) {
35		if (evt.target === this && backdropMouseDown) {
36			closeSearch();
37		}
38		backdropMouseDown = false;
39	});
40});
41
42(function () {
43	'use strict';
44
45	// #################################################################
46	// DICTIONARY
47	// #################################################################
48
49	const dictionaries = {
50		de: {
51			no_results: 'Keine Ergebnisse gefunden.',
52			search_error: 'Die Suche ist momentan nicht verfügbar.',
53		},
54		en: {
55			no_results: 'No results found.',
56			search_error: 'Search is currently unavailable.',
57		},
58		nl: {
59			no_results: 'Geen resultaten gevonden.',
60			search_error: 'De zoekfunctie is momenteel niet beschikbaar.',
61		},
62	};
63
64	function resolveSearchLanguage(lang) {
65		const code = (lang || 'en').slice(0, 2).toLowerCase();
66
67		if (code === 'de' || code === 'nl') {
68			return code;
69		}
70
71		return 'en';
72	}
73
74	// Spracherkennung via html lang Attribut
75	const languageCode = resolveSearchLanguage(document.documentElement.lang);
76	const dictionary = dictionaries[languageCode];
77
78	// #################################################################
79	// VARIABLEN
80	// #################################################################
81
82	let debounceTimer = null;
83	let trackingTimer = null;
84	let issuedRequestId = 0;
85	let resultsWrapper = null;
86	let results = null;
87	let resultsText = null;
88
89	// #################################################################
90	// FUNKTIONEN
91	// #################################################################
92
93	function debounce(func, delay) {
94		return function (...args) {
95			clearTimeout(debounceTimer);
96			debounceTimer = setTimeout(() => func.apply(this, args), delay);
97		};
98	}
99
100	// Schickt den Suchbegriff als Matomo-Event. Guard, da _paq durch
101	// Cookie-Consent/Adblocker fehlen kann.
102	function trackSearch(query) {
103		if (typeof window._paq === 'undefined') {
104			return;
105		}
106
107		window._paq.push(['trackEvent', 'search', 'query', query]);
108	}
109
110	// Eigener Debounce fürs Tracking (unabhängig vom Such-Debounce), damit nur
111	// die fertig getippte Query gezaehlt wird und keine Teil-Eingaben.
112	function scheduleTracking(query) {
113		clearTimeout(trackingTimer);
114		trackingTimer = setTimeout(() => trackSearch(query), 1500);
115	}
116
117	function createExcerpt(html, maxWords = 15) {
118		// Umweg der Text-Interpretation über den Browser -> Wir erhalten den Text ohne HTML
119		const temp = document.createElement('div');
120		temp.innerHTML = html;
121		const text = temp.textContent || temp.innerText || '';
122
123		const words = text.trim().split(/\s+/);
124
125		if (words.length <= maxWords) {
126			return text.trim();
127		}
128
129		return words.slice(0, maxWords).join(' ') + ' [...]';
130	}
131
132	async function renderResults(items) {
133		if (!resultsWrapper) {
134			return;
135		}
136
137		if (!items || items.length === 0) {
138			resultsText.innerHTML = '<strong>' + dictionary.no_results + '</strong>';
139			results.innerHTML = '';
140			return;
141		}
142
143		const itemHtml = await Promise.all(
144			items.map(async (item, index) => {
145				const metadata = item.metadata;
146				const type = metadata.type;
147				const title = metadata.title;
148				const url = metadata.url;
149
150				return `<div class="search-result type-${type}" data-href="${url}" style="--delay: ${index * 80}ms">
151							<p class="title"><strong>${title}</strong></p>
152							<p class="excerpt">${createExcerpt(item.text)}</p>
153						</div>`;
154			}),
155		);
156
157		resultsText.innerHTML = '';
158		results.innerHTML = itemHtml.join('');
159	}
160
161	function renderError(message) {
162		if (!resultsWrapper) {
163			return;
164		}
165
166		resultsText.innerHTML = '<strong>' + dictionary.search_error + '</strong>';
167
168		results.innerHTML = message ? `<p class="search-error">${message}</p>` : '';
169	}
170
171	function resetResults() {
172		if (!resultsWrapper) {
173			return;
174		}
175
176		issuedRequestId++; // entwertet alle noch laufenden fetches
177		resultsWrapper.classList.remove('loading');
178		resultsText.innerHTML = '';
179		results.innerHTML = '';
180	}
181
182	function performSearch(query) {
183		resultsWrapper.classList.add('loading');
184		const requestId = ++issuedRequestId;
185
186		const formData = new FormData();
187
188		formData.append('action', 'search_query');
189		formData.append('nonce', searchConfig.nonce);
190		formData.append('query', query);
191		formData.append('language', languageCode);
192
193		fetch(searchConfig.ajaxUrl, {
194			method: 'POST',
195			body: formData,
196		})
197			.then((response) => response.json())
198			.then((data) => {
199				// Nur die aktuellste Anfrage zählt; alles andere ist veraltet oder entwertet.
200				if (requestId !== issuedRequestId) {
201					return;
202				}
203
204				if (data.success) {
205					renderResults(data.data.results);
206				} else {
207					renderError(data?.data?.message);
208					console.error('Fehler:', data.data.message);
209				}
210			})
211			.catch((error) => {
212				if (requestId === issuedRequestId) {
213					renderError();
214				}
215				console.error('Verbindungsfehler:', error);
216			})
217			.finally(() => {
218				// Nur der aktuellste Request darf den Loading-State finalisieren.
219				if (requestId === issuedRequestId) {
220					resultsWrapper.classList.remove('loading');
221				}
222			});
223	}
224
225	const debouncedSearch = debounce(performSearch, 400);
226
227	// #################################################################
228	// EVENT LISTENER
229	// #################################################################
230
231	document.addEventListener('DOMContentLoaded', function () {
232		const search = document.getElementById('search-text');
233		const submitBtn = document.getElementById('search-submit');
234		resultsWrapper = document.querySelector('.search-results');
235		results = document.getElementById('search-results');
236		resultsText = document.querySelector('.search-results > p');
237
238		if (!search || !resultsWrapper) {
239			return;
240		}
241
242		if (submitBtn) {
243			submitBtn.addEventListener('click', function () {
244				executeSearch();
245			});
246		}
247
248		// Click-Handler für Suchergebnisse mit data-href (Event Delegation)
249		results.addEventListener('click', function (e) {
250			const result = e.target.closest('.search-result[data-href]');
251			if (result) {
252				window.location.href = result.dataset.href;
253			}
254		});
255
256		search.addEventListener('input', function () {
257			const query = this.value.trim();
258
259			if (query.length >= 3) {
260				debouncedSearch(query);
261				scheduleTracking(query);
262			} else {
263				clearTimeout(debounceTimer);
264				clearTimeout(trackingTimer);
265				resetResults();
266			}
267		});
268
269		search.addEventListener('keydown', function (e) {
270			if (e.key === 'Enter') {
271				e.preventDefault();
272				executeSearch();
273			}
274		});
275
276		function executeSearch() {
277			clearTimeout(debounceTimer);
278			clearTimeout(trackingTimer);
279			const query = search.value.trim();
280
281			if (query.length >= 3) {
282				performSearch(query);
283				trackSearch(query);
284			}
285		}
286	});
287})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.