(() => { const modal = document.getElementById('globalSearchModal'); const input = document.getElementById('globalSearchInput'); const results = document.getElementById('globalSearchResults'); if (!modal || !input || !results) return; const labels = modal.dataset; let groups = {}; try { groups = JSON.parse(labels.groups || '{}'); } catch {} let timer; let request; const escapeText = value => { const node = document.createElement('span'); node.textContent = String(value ?? ''); return node.innerHTML; }; const notice = (message, danger = false) => '
' + escapeText(message) + '
'; document.addEventListener('keydown', event => { if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'k') { event.preventDefault(); bootstrap.Modal.getOrCreateInstance(modal).show(); } }); modal.addEventListener('shown.bs.modal', () => input.focus()); input.addEventListener('input', () => { clearTimeout(timer); const query = input.value.trim(); if (query.length < 2) { results.innerHTML = notice(labels.hint); return; } timer = setTimeout(async () => { request?.abort(); request = new AbortController(); results.innerHTML = notice(labels.loading); try { const response = await fetch('/search.json?q=' + encodeURIComponent(query), { signal: request.signal, headers: {Accept: 'application/json'} }); if (!response.ok) throw new Error('Search failed'); const data = await response.json(); const entries = Object.entries(data.groups || {}); if (!entries.length) { results.innerHTML = notice(labels.none); return; } results.innerHTML = entries.map(([group, items]) => '
' + escapeText(groups[group] || group) + '
' + items.map(item => '' + escapeText(item.title) + '
' + escapeText(item.detail) + '
').join('') + '
' ).join('') + '' + escapeText(labels.allResults) + ''; } catch (error) { if (error.name !== 'AbortError') results.innerHTML = notice(labels.error, true); } }, 220); }); })();