No results found
Loading...
`;
list.appendChild(li);
});
setVisibility(section, true);
}
function clearSections() {
sectionsEl.querySelectorAll('[data-section]').forEach((section) => {
setVisibility(section, false);
const list = section.querySelector('[data-role="hits"]');
if (list) list.innerHTML = '';
});
}
function renderEmpty(show) {
setVisibility(emptyEl, show);
}
function renderError(message) {
if (!errorEl) return;
if (message) {
errorEl.textContent = message;
setVisibility(errorEl, true);
} else {
setVisibility(errorEl, false);
}
}
function setLoading(show) {
setVisibility(loadingEl, show);
}
function ensureAlgoliaClient() {
return new Promise((resolve, reject) => {
if (window.algoliasearch) {
return resolve();
}
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/
[email protected]/dist/algoliasearch-lite.umd.js';
script.async = true;
script.onload = () => resolve();
script.onerror = () => reject(new Error('Failed to load Algolia client'));
document.head.appendChild(script);
});
}
async function fetchConfig() {
if (algoliaConfig) return algoliaConfig;
try {
const res = await fetch(configUrl, { credentials: 'same-origin' });
if (!res.ok) throw new Error('Config request failed');
const data = await res.json();
algoliaConfig = data;
return data;
} catch (err) {
renderError('Could not load search settings');
throw err;
}
}
async function ensureClientReady() {
const cfg = await fetchConfig();
if (!cfg || !cfg.app_id || !cfg.search_key) {
throw new Error('Algolia credentials missing');
}
await ensureAlgoliaClient();
client = window.algoliasearch(cfg.app_id, cfg.search_key);
return { cfg, client };
}
async function runSearch(query) {
const trimmed = (query || '').trim();
if (!trimmed) {
clearSections();
renderEmpty(false);
ensurePanel(false);
setStatus('Type to search');
return;
}
setLoading(true);
renderError('');
renderEmpty(false);
ensurePanel(true);
try {
const { cfg } = await ensureClientReady();
const queries = [];
const mergeQueries = [];
sectionConfig.forEach((section) => {
const indexName = cfg.indexes && cfg.indexes[section.key];
if (!indexName) return;
if (section.mergeKeys && section.mergeKeys.length) {
queries.push({
key: section.key,
indexName,
query: trimmed,
params: { hitsPerPage: section.limit, clickAnalytics: false },
mergeKeys: section.mergeKeys,
});
section.mergeKeys.forEach((mergeKey) => {
const mergeIndexName = cfg.indexes && cfg.indexes[mergeKey];
if (mergeIndexName && !mergeQueries.find((mq) => mq.key === mergeKey)) {
mergeQueries.push({
key: mergeKey,
indexName: mergeIndexName,
query: trimmed,
params: { hitsPerPage: 3, clickAnalytics: false },
mergeInto: section.key,
});
}
});
} else {
queries.push({
key: section.key,
indexName,
query: trimmed,
params: { hitsPerPage: section.limit, clickAnalytics: false },
});
}
});
const allQueries = [...queries, ...mergeQueries];
if (!allQueries.length) {
throw new Error('No indexes configured');
}
const response = await client.multipleQueries(
allQueries.map((q) => ({
indexName: q.indexName,
query: q.query,
params: q.params,
}))
);
clearSections();
let totalHits = 0;
const mergedResults = {};
allQueries.forEach((q, idx) => {
const result = response.results[idx];
if (!result) return;
const hits = result.hits || [];
totalHits += result.nbHits || 0;
if (q.mergeInto) {
if (!mergedResults[q.mergeInto]) {
mergedResults[q.mergeInto] = [];
}
mergedResults[q.mergeInto].push(...hits);
} else {
if (!mergedResults[q.key]) {
mergedResults[q.key] = [];
}
mergedResults[q.key].push(...hits);
}
});
Object.keys(mergedResults).forEach((sectionKey) => {
renderHits(sectionKey, mergedResults[sectionKey]);
});
renderEmpty(totalHits === 0);
if (!totalHits) {
setStatus('No results');
}
} catch (err) {
console.error('Algolia search failed', err);
renderError('Search is temporarily unavailable.');
} finally {
setLoading(false);
}
}
const debouncedSearch = debounce(runSearch, 200);
if (input) {
input.addEventListener('input', (e) => debouncedSearch(e.target.value));
input.addEventListener('focus', () => {
if (input.value.trim()) {
ensurePanel(true);
}
});
}
if (cancelBtn) {
cancelBtn.addEventListener('click', () => {
input.value = '';
clearSections();
renderEmpty(false);
renderError('');
setStatus('Type to search');
ensurePanel(false);
root.dispatchEvent(new CustomEvent('algolia-close', { bubbles: true }));
});
}
document.addEventListener('click', (event) => {
if (!root.contains(event.target)) {
ensurePanel(false);
}
});
})();