Improve Algolia search concurrency

This commit is contained in:
Mimi 2026-08-04 14:55:10 +08:00
parent 4352696dba
commit 5c7574fd3f

View File

@ -19,83 +19,92 @@ document.addEventListener('DOMContentLoaded', () => {
return result; return result;
}; };
let isSearching = false; let latestSearchId = 0;
let pendingQuery = null;
const searchAlgolia = async (searchText, page = 0) => { const searchAlgolia = async (searchText, page, searchId) => {
if (isSearching) { if (searchId !== latestSearchId) return;
pendingQuery = { searchText, page }; container.setAttribute('aria-busy', 'true');
return;
}
isSearching = true;
const startTime = Date.now(); const startTime = Date.now();
const result = await client.search({ try {
requests: [{ const result = await client.search({
indexName, requests: [{
page, indexName,
query : searchText, page,
hitsPerPage : hits.per_page || 10, query : searchText,
attributesToRetrieve : ['permalink'], hitsPerPage : hits.per_page || 10,
attributesToHighlight: ['title', 'excerpt', 'excerptStrip', 'contentStripTruncate'], attributesToRetrieve : ['permalink'],
highlightPreTag : '<mark class="search-keyword">', attributesToHighlight: ['title', 'excerpt', 'excerptStrip', 'contentStripTruncate'],
highlightPostTag : '</mark>' highlightPreTag : '<mark class="search-keyword">',
}] highlightPostTag : '</mark>'
}); }]
const data = result.results[0]; });
if (data.nbHits === 0) { if (searchId !== latestSearchId) return;
container.innerHTML = '<div class="search-result-icon"><i class="far fa-frown fa-5x"></i></div>';
} else {
const stats = CONFIG.i18n.hits_time
.replace('${hits}', data.nbHits)
.replace('${time}', Date.now() - startTime);
let pagination = '';
if (data.nbPages > 1) {
pagination += '<nav class="pagination algolia-pagination">';
for (let i = 0; i < data.nbPages; i++) {
if (i === page) {
pagination += `<span class="page-number current">${i + 1}</span>`;
} else {
pagination += `<a class="page-number" href="#" data-index=${i}>${i + 1}</a>`;
}
}
pagination += '</nav>';
}
container.innerHTML = `<div class="search-stats"> const data = result.results[0];
if (data.nbHits === 0) {
container.innerHTML = '<div class="search-result-icon"><i class="far fa-frown fa-5x"></i></div>';
} else {
const stats = CONFIG.i18n.hits_time
.replace('${hits}', data.nbHits)
.replace('${time}', Date.now() - startTime);
let pagination = '';
if (data.nbPages > 1) {
pagination += '<nav class="pagination algolia-pagination">';
for (let i = 0; i < data.nbPages; i++) {
if (i === page) {
pagination += `<span class="page-number current">${i + 1}</span>`;
} else {
pagination += `<a class="page-number" href="#" data-index=${i}>${i + 1}</a>`;
}
}
pagination += '</nav>';
}
container.innerHTML = `<div class="search-stats">
<span>${stats}</span> <span>${stats}</span>
<img src="${CONFIG.images}/logo-algolia-nebula-blue-full.svg" alt="Algolia"> <img src="${CONFIG.images}/logo-algolia-nebula-blue-full.svg" alt="Algolia">
</div> </div>
<hr> <hr>
<ul class="search-result-list">${data.hits.map(formatHits).join('')}</ul> <ul class="search-result-list">${data.hits.map(formatHits).join('')}</ul>
${pagination}`; ${pagination}`;
if (typeof pjax === 'object') pjax.refresh(container); if (typeof pjax === 'object') pjax.refresh(container);
container.querySelectorAll('.page-number').forEach(element => { container.querySelectorAll('.page-number').forEach(element => {
element.addEventListener('click', async event => { element.addEventListener('click', async event => {
event.preventDefault(); event.preventDefault();
await searchAlgolia(searchText, Number(element.dataset.index)); if (input.value.trim() !== searchText) return;
const searchId = ++latestSearchId;
await searchAlgolia(searchText, Number(element.dataset.index), searchId);
});
}); });
}); }
} } catch (error) {
isSearching = false; if (searchId !== latestSearchId) return;
if (pendingQuery !== null && (pendingQuery.searchText !== searchText || pendingQuery.page !== page)) { console.warn('Algolia search failed:', error);
const { searchText, page } = pendingQuery; container.innerHTML = '<div class="search-result-icon"><i class="far fa-frown fa-5x"></i></div>';
pendingQuery = null; } finally {
searchAlgolia(searchText, page); if (searchId === latestSearchId) container.removeAttribute('aria-busy');
} }
}; };
const inputEventFunction = async () => { const inputEventFunction = async searchId => {
if (searchId !== latestSearchId) return;
const searchText = input.value.trim(); const searchText = input.value.trim();
if (searchText === '') { if (searchText === '') return;
container.innerHTML = '<div class="search-result-icon"><i class="fab fa-algolia fa-5x"></i></div>';
return;
}
// Algolia client will automatically cache the data for same queries // Algolia client will automatically cache the data for same queries
await searchAlgolia(searchText, 0); await searchAlgolia(searchText, 0, searchId);
}; };
const debouncedSearch = NexT.utils.debounce(inputEventFunction, 500); const debouncedSearch = NexT.utils.debounce(inputEventFunction, 500);
input.addEventListener('input', debouncedSearch); input.addEventListener('input', () => {
const searchId = ++latestSearchId;
if (input.value.trim() === '') {
container.removeAttribute('aria-busy');
container.innerHTML = '<div class="search-result-icon"><i class="fab fa-algolia fa-5x"></i></div>';
} else {
container.setAttribute('aria-busy', 'true');
}
debouncedSearch(searchId);
});
// Handle and trigger popup window // Handle and trigger popup window
document.querySelectorAll('.popup-trigger').forEach(element => { document.querySelectorAll('.popup-trigger').forEach(element => {