UNPKG

hexo-theme-next

Version:

Elegant and powerful theme for Hexo.

137 lines (125 loc) 5 kB
/* global CONFIG, NexT, pjax */ document.addEventListener('DOMContentLoaded', () => { const { indexName, appID, apiKey, hits } = CONFIG.algolia; const client = window['algoliasearch/lite'].liteClient(appID, apiKey); const input = document.querySelector('.search-input'); const container = document.querySelector('.search-result-container'); const formatHits = data => { const { title, excerpt, excerptStrip, contentStripTruncate } = data._highlightResult; let result = `<li><a href="${data.permalink}" class="search-result-title">${title.value}</a>`; const content = excerpt?.value || excerptStrip?.value || contentStripTruncate?.value; if (content) { const div = document.createElement('div'); div.innerHTML = content; result += `<a href="${data.permalink}"><p class="search-result">${div.textContent.substring(0, 100)}...</p></a></li>`; } return result; }; let isSearching = false; let pendingQuery = null; const searchAlgolia = async (searchText, page = 0) => { if (isSearching) { pendingQuery = { searchText, page }; return; } isSearching = true; const startTime = Date.now(); const result = await client.search({ requests: [{ indexName, page, query : searchText, hitsPerPage : hits.per_page || 10, attributesToRetrieve : ['permalink'], attributesToHighlight: ['title', 'excerpt', 'excerptStrip', 'contentStripTruncate'], highlightPreTag : '<mark class="search-keyword">', highlightPostTag : '</mark>' }] }); 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> <img src="${CONFIG.images}/logo-algolia-nebula-blue-full.svg" alt="Algolia"> </div> <hr> <ul class="search-result-list">${data.hits.map(formatHits).join('')}</ul> ${pagination}`; if (typeof pjax === 'object') pjax.refresh(container); container.querySelectorAll('.page-number').forEach(element => { element.addEventListener('click', async event => { event.preventDefault(); await searchAlgolia(searchText, Number(element.dataset.index)); }); }); } isSearching = false; if (pendingQuery !== null && (pendingQuery.searchText !== searchText || pendingQuery.page !== page)) { const { searchText, page } = pendingQuery; pendingQuery = null; searchAlgolia(searchText, page); } }; const inputEventFunction = async () => { const searchText = input.value.trim(); if (searchText === '') { 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 await searchAlgolia(searchText, 0); }; const debouncedSearch = NexT.utils.debounce(inputEventFunction, 500); input.addEventListener('input', debouncedSearch); // Handle and trigger popup window document.querySelectorAll('.popup-trigger').forEach(element => { element.addEventListener('click', () => { NexT.utils.setGutter(); document.body.classList.add('search-active'); // Wait for search-popup animation to complete setTimeout(() => input.focus(), 500); }); }); // Monitor main search box const onPopupClose = () => { NexT.utils.setGutter('0'); document.body.classList.remove('search-active'); }; document.querySelector('.search-pop-overlay').addEventListener('click', event => { if (event.target === document.querySelector('.search-pop-overlay')) { onPopupClose(); } }); document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose); document.addEventListener('pjax:success', onPopupClose); window.addEventListener('keydown', event => { if ((event.ctrlKey || event.metaKey) && event.key === 'k') { event.preventDefault(); NexT.utils.setGutter(); document.body.classList.add('search-active'); setTimeout(() => input.focus(), 500); } }); window.addEventListener('keyup', event => { if (event.key === 'Escape') { onPopupClose(); } }); });