const input = document.querySelector('#side-search'); const resultsEl = document.querySelector('[data-global-search-results]'); if (!input || !resultsEl) { if (!input) console.warn('[GlobalSearch] #side-search input not found'); if (!resultsEl) console.warn('[GlobalSearch] [data-global-search-results] container not found'); } else { const panel = document.querySelector('#aside-panel-search'); const toggleButton = document.querySelector('[data-search-details-toggle]'); const toggleIcon = document.querySelector('[data-search-details-icon]'); const appBase = window.APP_BASE || document.baseURI; const endpoint = new URL('admin/search/data', appBase); const resultsPage = new URL('search', appBase); const minLength = 2; let pending = null; const itemsByKey = new Map(); resultsEl.querySelectorAll('[data-search-key]').forEach((item) => { const key = item.getAttribute('data-search-key'); if (key) itemsByKey.set(key, item); }); if (itemsByKey.size === 0) { console.warn('[GlobalSearch] No [data-search-key] elements found in results container'); } const setLoadingState = (value) => { resultsEl.querySelectorAll('[data-search-count]').forEach((el) => { el.textContent = value; }); }; const render = (items, query) => { items.forEach((item) => { const li = itemsByKey.get(item.key); if (!li) { console.warn('[GlobalSearch] No element found for key:', item.key); return; } const countEl = li.querySelector('[data-search-count]'); const link = li.querySelector('a'); const previewEl = li.querySelector('[data-search-preview]'); const base = item.key === 'pages' && item.url ? item.url : (li.getAttribute('data-search-base') || item.url); const url = new URL(base, appBase); if (item.key !== 'pages') { url.searchParams.set('search', query); } if (countEl) countEl.textContent = item.count.toString(); if (link) { link.href = item.count > 0 ? url.toString() : base; link.setAttribute('aria-disabled', item.count > 0 ? 'false' : 'true'); } if (previewEl) { previewEl.innerHTML = ''; if (Array.isArray(item.items) && item.items.length > 0) { item.items.forEach((previewItem, idx) => { if (!previewItem || !previewItem.url) { console.warn('[GlobalSearch] Preview item missing url at index', idx, 'for key:', item.key, previewItem); return; } const previewLi = document.createElement('li'); const previewLink = document.createElement('a'); previewLink.href = previewItem.url; previewLink.textContent = previewItem.label || ''; previewLi.appendChild(previewLink); previewEl.appendChild(previewLi); }); } } if (item.count > 0) { li.hidden = false; } else { li.hidden = true; } }); }; const clear = () => { setLoadingState('0'); resultsEl.querySelectorAll('a').forEach((link) => { link.setAttribute('aria-disabled', 'true'); const base = link.closest('[data-search-base]')?.getAttribute('data-search-base'); link.setAttribute('href', base || '#'); }); resultsEl.querySelectorAll('[data-search-preview]').forEach((list) => { list.innerHTML = ''; }); resultsEl.querySelectorAll('[data-search-key]').forEach((li) => { li.hidden = false; }); }; const fetchCounts = async (value) => { const url = new URL(endpoint.toString()); url.searchParams.set('q', value); setLoadingState('…'); const response = await fetch(url.toString(), { headers: { 'Accept': 'application/json' } }); if (!response.ok) { console.warn('[GlobalSearch] Fetch failed with status:', response.status, response.statusText); clear(); return; } const data = await response.json(); if (Array.isArray(data)) { render(data, value); } else { console.warn('[GlobalSearch] Invalid response format, expected array but got:', typeof data, data); clear(); } }; const onInput = () => { const value = input.value.trim(); if (value.length < minLength) { clear(); return; } if (pending) window.clearTimeout(pending); pending = window.setTimeout(() => { fetchCounts(value).catch((err) => { console.warn('[GlobalSearch] Fetch error:', err); clear(); }); }, 300); }; input.addEventListener('input', onInput); const submitSearch = () => { const value = input.value.trim(); if (value.length < minLength) return; resultsPage.searchParams.set('search', value); window.location.href = resultsPage.toString(); }; input.addEventListener('keydown', (event) => { if (event.key !== 'Enter') return; event.preventDefault(); submitSearch(); }); const form = input.closest('form'); if (form) { form.addEventListener('submit', (event) => { event.preventDefault(); submitSearch(); }); } if (toggleButton && panel) { toggleButton.addEventListener('click', () => { panel.classList.toggle('search-details-hidden'); if (toggleIcon) { toggleIcon.classList.toggle('bi-dash-square'); toggleIcon.classList.toggle('bi-plus-square'); } }); } else if (toggleButton || panel) { if (!toggleButton) console.warn('[GlobalSearch] [data-search-details-toggle] button not found'); if (!panel) console.warn('[GlobalSearch] #aside-panel-search panel not found'); } // Seed from current URL search param if present const currentUrl = new URL(window.location.href); const preset = currentUrl.searchParams.get('search'); if (preset && !input.value) { input.value = preset; onInput(); } }