From 1ca10776ff8fc84c240e8ce74a464eee98d40f58 Mon Sep 17 00:00:00 2001 From: Mimi <1119186082@qq.com> Date: Tue, 7 May 2024 16:43:11 +0800 Subject: [PATCH] Refactor Algolia search: drop instantsearch.js (#795) --- _vendors.yml | 5 - layout/_partials/search/algolia-search.njk | 14 -- layout/_partials/search/index.njk | 23 ++- layout/_partials/search/localsearch.njk | 18 -- layout/_third-party/search/algolia-search.njk | 1 - .../components/third-party/search.styl | 83 +++------ .../images/logo-algolia-nebula-blue-full.svg | 2 +- .../js/third-party/search/algolia-search.js | 176 +++++++++--------- source/js/third-party/search/local-search.js | 5 +- source/js/utils.js | 9 + 10 files changed, 141 insertions(+), 195 deletions(-) delete mode 100644 layout/_partials/search/algolia-search.njk delete mode 100644 layout/_partials/search/localsearch.njk diff --git a/_vendors.yml b/_vendors.yml index b75d8b8..f078b62 100644 --- a/_vendors.yml +++ b/_vendors.yml @@ -121,11 +121,6 @@ algolia_search: version: 4.23.3 file: dist/algoliasearch-lite.umd.js integrity: sha256-1QNshz86RqXe/qsCBldsUu13eAX6n/O98uubKQs87UI= -instant_search: - name: instantsearch.js - version: 4.67.0 - file: dist/instantsearch.production.min.js - integrity: sha256-TW7D3X/i/W+RUgEeDppEnFT2ixv5lzplKH0c58D92dY= local_search: name: hexo-generator-searchdb version: 1.4.1 diff --git a/layout/_partials/search/algolia-search.njk b/layout/_partials/search/algolia-search.njk deleted file mode 100644 index 59f5b4e..0000000 --- a/layout/_partials/search/algolia-search.njk +++ /dev/null @@ -1,14 +0,0 @@ -
- - - -
- - - -
-
-

-
-
-
diff --git a/layout/_partials/search/index.njk b/layout/_partials/search/index.njk index 0a22e24..edb64d1 100644 --- a/layout/_partials/search/index.njk +++ b/layout/_partials/search/index.njk @@ -1,11 +1,24 @@ {%- if theme.algolia_search.enable or theme.local_search.enable %}
{%- endif %} diff --git a/layout/_partials/search/localsearch.njk b/layout/_partials/search/localsearch.njk deleted file mode 100644 index 6fa6f65..0000000 --- a/layout/_partials/search/localsearch.njk +++ /dev/null @@ -1,18 +0,0 @@ -
- - - -
- -
- - - -
-
-
- -
-
diff --git a/layout/_third-party/search/algolia-search.njk b/layout/_third-party/search/algolia-search.njk index 9e69c92..6ab729c 100644 --- a/layout/_third-party/search/algolia-search.njk +++ b/layout/_third-party/search/algolia-search.njk @@ -1,4 +1,3 @@ {{ next_vendors('algolia_search') }} -{{ next_vendors('instant_search') }} {{- next_js('third-party/search/algolia-search.js') }} diff --git a/source/css/_common/components/third-party/search.styl b/source/css/_common/components/third-party/search.styl index d5b6a15..5b229ad 100644 --- a/source/css/_common/components/third-party/search.styl +++ b/source/css/_common/components/third-party/search.styl @@ -79,11 +79,14 @@ if (hexo-config('local_search.enable') or hexo-config('algolia_search.enable')) } .search-result-container { + display: flex; + flex-direction: column; height: calc(100% - 55px); overflow: auto; padding: 5px 25px; hr { + flex-shrink: 0; margin: 5px 0 10px; &:first-child { @@ -105,18 +108,27 @@ if (hexo-config('local_search.enable') or hexo-config('algolia_search.enable')) border-bottom: 1px dashed $grey-light; padding: 5px 0; } + + .search-input-container { + flex-grow: 1; + padding: 2px; + } + + .search-result-icon { + color: $grey-light; + margin: auto; + } } } +mark.search-keyword { + background: transparent; + border-bottom: 1px dashed $red; + color: $red; + font-weight: bold; +} + if (hexo-config('algolia_search.enable')) { - .search-input-container { - flex-grow: 1; - - form { - padding: 2px; - } - } - .search-stats { align-items: center; display: flex; @@ -128,52 +140,13 @@ if (hexo-config('algolia_search.enable')) { } } - .algolia-pagination { - // Override default style of ul - margin: 40px 0; - opacity: 1; - padding: 0; - - .pagination-item { - display: inline-block; - } - - .current .page-number { - @extend $page-number-current; - cursor: default; - } - - .disabled-item { - visibility: hidden; - } - } -} - -if (hexo-config('local_search.enable')) { - .search-popup { - .search-input-container { - flex-grow: 1; - padding: 2px; - } - - .no-result { - display: flex; - } - - .search-result-list { - width: 100%; - } - - .search-result-icon { - color: $grey-light; - margin: auto; - } - } - - mark.search-keyword { - background: transparent; - border-bottom: 1px dashed $red; - color: $red; - font-weight: bold; + .pagination.algolia-pagination { + // Override the default style of pagination + // Put pagination at the bottom when there is sufficient height + margin-top: auto; + padding: 40px 0; + // Override motion + // https://github.com/theme-next/hexo-theme-next/issues/537 + visibility: visible; } } diff --git a/source/images/logo-algolia-nebula-blue-full.svg b/source/images/logo-algolia-nebula-blue-full.svg index 886c422..8b45b88 100644 --- a/source/images/logo-algolia-nebula-blue-full.svg +++ b/source/images/logo-algolia-nebula-blue-full.svg @@ -1 +1 @@ - \ No newline at end of file +Algolia logo blue diff --git a/source/js/third-party/search/algolia-search.js b/source/js/third-party/search/algolia-search.js index 12a554c..10a70c3 100644 --- a/source/js/third-party/search/algolia-search.js +++ b/source/js/third-party/search/algolia-search.js @@ -1,112 +1,104 @@ -/* global instantsearch, algoliasearch, CONFIG, pjax */ +/* global CONFIG, NexT, pjax, algoliasearch */ document.addEventListener('DOMContentLoaded', () => { const { indexName, appID, apiKey, hits } = CONFIG.algolia; + const client = algoliasearch(appID, apiKey); + const index = client.initIndex(indexName); - const search = instantsearch({ - indexName, - searchClient : algoliasearch(appID, apiKey), - searchFunction: helper => { - if (document.querySelector('.search-input').value) { - helper.search(); - } + 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 = `
  • ${title.value}`; + const content = excerpt || excerptStrip || contentStripTruncate; + if (content && content.value) { + const div = document.createElement('div'); + div.innerHTML = content.value; + result += `

    ${div.textContent.substring(0, 100)}...

  • `; } - }); + return result; + }; - if (typeof pjax === 'object') { - search.on('render', () => { - pjax.refresh(document.querySelector('.algolia-hits')); + 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 data = await index.search(searchText, { + page, + attributesToRetrieve : ['permalink'], + attributesToHighlight: ['title', 'excerpt', 'excerptStrip', 'contentStripTruncate'], + hitsPerPage : hits.per_page || 10, + highlightPreTag : '', + highlightPostTag : '' }); - } - - // Registering Widgets - search.addWidgets([ - instantsearch.widgets.configure({ - hitsPerPage: hits.per_page || 10 - }), - - instantsearch.widgets.searchBox({ - container : '.search-input-container', - placeholder : CONFIG.i18n.placeholder, - // Hide default icons of algolia search - showReset : false, - showSubmit : false, - showLoadingIndicator: false, - cssClasses : { - input: 'search-input' - } - }), - - instantsearch.widgets.stats({ - container: '.algolia-stats', - templates: { - text: data => { - const stats = CONFIG.i18n.hits_time - .replace('${hits}', data.nbHits) - .replace('${time}', data.processingTimeMS); - return `${stats} - Algolia`; - } - }, - cssClasses: { - text: 'search-stats' - } - }), - - instantsearch.widgets.hits({ - container : '.algolia-hits', - escapeHTML: false, - templates : { - item: data => { - const { title, excerpt, excerptStrip, contentStripTruncate } = data._highlightResult; - let result = `${title.value}`; - const content = excerpt || excerptStrip || contentStripTruncate; - if (content && content.value) { - const div = document.createElement('div'); - div.innerHTML = content.value; - result += `

    ${div.textContent.substring(0, 100)}...

    `; + if (data.nbHits === 0) { + container.innerHTML = '
    '; + } else { + const stats = CONFIG.i18n.hits_time + .replace('${hits}', data.nbHits) + .replace('${time}', Date.now() - startTime); + let pagination = ''; + if (data.nbPages > 1) { + pagination += ''; } - }), - instantsearch.widgets.pagination({ - container: '.algolia-pagination', - scrollTo : false, - showFirst: false, - showLast : false, - templates: { - first : '', - last : '', - previous: '', - next : '' - }, - cssClasses: { - list : ['pagination', 'algolia-pagination'], - item : 'pagination-item', - link : 'page-number', - selectedItem: 'current', - disabledItem: 'disabled-item' - } - }) - ]); + container.innerHTML = `
    + ${stats} + Algolia +
    +
    + + ${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); + } + }; - search.start(); + const inputEventFunction = async() => { + const searchText = input.value.trim(); + if (searchText === '') { + container.innerHTML = '
    '; + 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', () => { document.body.classList.add('search-active'); - setTimeout(() => document.querySelector('.search-input').focus(), 500); + // Wait for search-popup animation to complete + setTimeout(() => input.focus(), 500); }); }); diff --git a/source/js/third-party/search/local-search.js b/source/js/third-party/search/local-search.js index 92a264d..a44ef75 100644 --- a/source/js/third-party/search/local-search.js +++ b/source/js/third-party/search/local-search.js @@ -13,22 +13,20 @@ document.addEventListener('DOMContentLoaded', () => { }); const input = document.querySelector('.search-input'); + const container = document.querySelector('.search-result-container'); const inputEventFunction = () => { if (!localSearch.isfetched) return; const searchText = input.value.trim().toLowerCase(); const keywords = searchText.split(/[-\s]+/); - const container = document.querySelector('.search-result-container'); let resultItems = []; if (searchText.length > 0) { // Perform local searching resultItems = localSearch.getResultItems(keywords); } if (keywords.length === 1 && keywords[0] === '') { - container.classList.add('no-result'); container.innerHTML = '
    '; } else if (resultItems.length === 0) { - container.classList.add('no-result'); container.innerHTML = '
    '; } else { resultItems.sort((left, right) => { @@ -41,7 +39,6 @@ document.addEventListener('DOMContentLoaded', () => { }); const stats = CONFIG.i18n.hits.replace('${hits}', resultItems.length); - container.classList.remove('no-result'); container.innerHTML = `
    ${stats}

    `; diff --git a/source/js/utils.js b/source/js/utils.js index ebd8552..2822e7b 100644 --- a/source/js/utils.js +++ b/source/js/utils.js @@ -498,5 +498,14 @@ NexT.utils = { }); intersectionObserver.observe(element); }); + }, + + debounce: function(func, wait) { + let timeout; + return function(...args) { + const context = this; + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(context, args), wait); + }; } };