mirror of
https://github.com/next-theme/hexo-theme-next.git
synced 2026-01-19 18:42:34 +00:00
Refactor Algolia search: drop instantsearch.js (#795)
This commit is contained in:
parent
263841bd7a
commit
1ca10776ff
@ -121,11 +121,6 @@ algolia_search:
|
|||||||
version: 4.23.3
|
version: 4.23.3
|
||||||
file: dist/algoliasearch-lite.umd.js
|
file: dist/algoliasearch-lite.umd.js
|
||||||
integrity: sha256-1QNshz86RqXe/qsCBldsUu13eAX6n/O98uubKQs87UI=
|
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:
|
local_search:
|
||||||
name: hexo-generator-searchdb
|
name: hexo-generator-searchdb
|
||||||
version: 1.4.1
|
version: 1.4.1
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
<div class="search-header">
|
|
||||||
<span class="search-icon">
|
|
||||||
<i class="fa fa-search"></i>
|
|
||||||
</span>
|
|
||||||
<div class="search-input-container"></div>
|
|
||||||
<span class="popup-btn-close" role="button">
|
|
||||||
<i class="fa fa-times-circle"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="search-result-container">
|
|
||||||
<div class="algolia-stats"><hr></div>
|
|
||||||
<div class="algolia-hits"></div>
|
|
||||||
<div class="algolia-pagination"></div>
|
|
||||||
</div>
|
|
||||||
@ -1,11 +1,24 @@
|
|||||||
{%- if theme.algolia_search.enable or theme.local_search.enable %}
|
{%- if theme.algolia_search.enable or theme.local_search.enable %}
|
||||||
<div class="search-pop-overlay">
|
<div class="search-pop-overlay">
|
||||||
<div class="popup search-popup">
|
<div class="popup search-popup">
|
||||||
{%- if theme.algolia_search.enable %}
|
<div class="search-header">
|
||||||
{%- include 'algolia-search.njk' -%}
|
<span class="search-icon">
|
||||||
{% elif theme.local_search.enable %}
|
<i class="fa fa-search"></i>
|
||||||
{%- include 'localsearch.njk' -%}
|
</span>
|
||||||
{%- endif %}
|
<div class="search-input-container">
|
||||||
|
<input autocomplete="off" autocapitalize="off" maxlength="80"
|
||||||
|
placeholder="{{ __('search.placeholder') }}" spellcheck="false"
|
||||||
|
type="search" class="search-input">
|
||||||
|
</div>
|
||||||
|
<span class="popup-btn-close" role="button">
|
||||||
|
<i class="fa fa-times-circle"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="search-result-container">
|
||||||
|
<div class="search-result-icon">
|
||||||
|
<i class="{% if theme.algolia_search.enable %}fab fa-algolia{% elif theme.local_search.enable %}fa fa-spinner fa-pulse{% endif %} fa-5x"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
<div class="search-header">
|
|
||||||
<span class="search-icon">
|
|
||||||
<i class="fa fa-search"></i>
|
|
||||||
</span>
|
|
||||||
<div class="search-input-container">
|
|
||||||
<input autocomplete="off" autocapitalize="off" maxlength="80"
|
|
||||||
placeholder="{{ __('search.placeholder') }}" spellcheck="false"
|
|
||||||
type="search" class="search-input">
|
|
||||||
</div>
|
|
||||||
<span class="popup-btn-close" role="button">
|
|
||||||
<i class="fa fa-times-circle"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="search-result-container no-result">
|
|
||||||
<div class="search-result-icon">
|
|
||||||
<i class="fa fa-spinner fa-pulse fa-5x"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -1,4 +1,3 @@
|
|||||||
{{ next_vendors('algolia_search') }}
|
{{ next_vendors('algolia_search') }}
|
||||||
{{ next_vendors('instant_search') }}
|
|
||||||
|
|
||||||
{{- next_js('third-party/search/algolia-search.js') }}
|
{{- next_js('third-party/search/algolia-search.js') }}
|
||||||
|
|||||||
@ -79,11 +79,14 @@ if (hexo-config('local_search.enable') or hexo-config('algolia_search.enable'))
|
|||||||
}
|
}
|
||||||
|
|
||||||
.search-result-container {
|
.search-result-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
height: calc(100% - 55px);
|
height: calc(100% - 55px);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 5px 25px;
|
padding: 5px 25px;
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
|
flex-shrink: 0;
|
||||||
margin: 5px 0 10px;
|
margin: 5px 0 10px;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
@ -105,18 +108,27 @@ if (hexo-config('local_search.enable') or hexo-config('algolia_search.enable'))
|
|||||||
border-bottom: 1px dashed $grey-light;
|
border-bottom: 1px dashed $grey-light;
|
||||||
padding: 5px 0;
|
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')) {
|
if (hexo-config('algolia_search.enable')) {
|
||||||
.search-input-container {
|
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
form {
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-stats {
|
.search-stats {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -128,52 +140,13 @@ if (hexo-config('algolia_search.enable')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.algolia-pagination {
|
.pagination.algolia-pagination {
|
||||||
// Override default style of ul
|
// Override the default style of pagination
|
||||||
margin: 40px 0;
|
// Put pagination at the bottom when there is sufficient height
|
||||||
opacity: 1;
|
margin-top: auto;
|
||||||
padding: 0;
|
padding: 40px 0;
|
||||||
|
// Override motion
|
||||||
.pagination-item {
|
// https://github.com/theme-next/hexo-theme-next/issues/537
|
||||||
display: inline-block;
|
visibility: visible;
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 4.8 KiB |
176
source/js/third-party/search/algolia-search.js
vendored
176
source/js/third-party/search/algolia-search.js
vendored
@ -1,112 +1,104 @@
|
|||||||
/* global instantsearch, algoliasearch, CONFIG, pjax */
|
/* global CONFIG, NexT, pjax, algoliasearch */
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const { indexName, appID, apiKey, hits } = CONFIG.algolia;
|
const { indexName, appID, apiKey, hits } = CONFIG.algolia;
|
||||||
|
const client = algoliasearch(appID, apiKey);
|
||||||
|
const index = client.initIndex(indexName);
|
||||||
|
|
||||||
const search = instantsearch({
|
const input = document.querySelector('.search-input');
|
||||||
indexName,
|
const container = document.querySelector('.search-result-container');
|
||||||
searchClient : algoliasearch(appID, apiKey),
|
|
||||||
searchFunction: helper => {
|
const formatHits = data => {
|
||||||
if (document.querySelector('.search-input').value) {
|
const { title, excerpt, excerptStrip, contentStripTruncate } = data._highlightResult;
|
||||||
helper.search();
|
let result = `<li><a href="${data.permalink}" class="search-result-title">${title.value}</a>`;
|
||||||
}
|
const content = excerpt || excerptStrip || contentStripTruncate;
|
||||||
|
if (content && content.value) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.innerHTML = content.value;
|
||||||
|
result += `<a href="${data.permalink}"><p class="search-result">${div.textContent.substring(0, 100)}...</p></a></li>`;
|
||||||
}
|
}
|
||||||
});
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
if (typeof pjax === 'object') {
|
let isSearching = false;
|
||||||
search.on('render', () => {
|
let pendingQuery = null;
|
||||||
pjax.refresh(document.querySelector('.algolia-hits'));
|
|
||||||
|
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 : '<mark class="search-keyword">',
|
||||||
|
highlightPostTag : '</mark>'
|
||||||
});
|
});
|
||||||
}
|
if (data.nbHits === 0) {
|
||||||
|
container.innerHTML = '<div class="search-result-icon"><i class="far fa-frown fa-5x"></i></div>';
|
||||||
// Registering Widgets
|
} else {
|
||||||
search.addWidgets([
|
const stats = CONFIG.i18n.hits_time
|
||||||
instantsearch.widgets.configure({
|
.replace('${hits}', data.nbHits)
|
||||||
hitsPerPage: hits.per_page || 10
|
.replace('${time}', Date.now() - startTime);
|
||||||
}),
|
let pagination = '';
|
||||||
|
if (data.nbPages > 1) {
|
||||||
instantsearch.widgets.searchBox({
|
pagination += '<nav class="pagination algolia-pagination">';
|
||||||
container : '.search-input-container',
|
for (let i = 0; i < data.nbPages; i++) {
|
||||||
placeholder : CONFIG.i18n.placeholder,
|
if (i === page) {
|
||||||
// Hide default icons of algolia search
|
pagination += `<span class="page-number current">${i + 1}</span>`;
|
||||||
showReset : false,
|
} else {
|
||||||
showSubmit : false,
|
pagination += `<a class="page-number" href="#" data-index=${i}>${i + 1}</a>`;
|
||||||
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 `<span>${stats}</span>
|
|
||||||
<img src="${CONFIG.images}/logo-algolia-nebula-blue-full.svg" alt="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 = `<a href="${data.permalink}" class="search-result-title">${title.value}</a>`;
|
|
||||||
const content = excerpt || excerptStrip || contentStripTruncate;
|
|
||||||
if (content && content.value) {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.innerHTML = content.value;
|
|
||||||
result += `<a href="${data.permalink}"><p class="search-result">${div.textContent.substring(0, 100)}...</p></a>`;
|
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
},
|
|
||||||
empty: data => {
|
|
||||||
return `<div class="algolia-hits-empty">
|
|
||||||
${CONFIG.i18n.empty.replace('${query}', data.query)}
|
|
||||||
</div>`;
|
|
||||||
}
|
}
|
||||||
},
|
pagination += '</nav>';
|
||||||
cssClasses: {
|
|
||||||
list: 'search-result-list'
|
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
|
|
||||||
instantsearch.widgets.pagination({
|
container.innerHTML = `<div class="search-stats">
|
||||||
container: '.algolia-pagination',
|
<span>${stats}</span>
|
||||||
scrollTo : false,
|
<img src="${CONFIG.images}/logo-algolia-nebula-blue-full.svg" alt="Algolia">
|
||||||
showFirst: false,
|
</div>
|
||||||
showLast : false,
|
<hr>
|
||||||
templates: {
|
<ul class="search-result-list">${data.hits.map(formatHits).join('')}</ul>
|
||||||
first : '<i class="fa fa-angle-double-left"></i>',
|
${pagination}`;
|
||||||
last : '<i class="fa fa-angle-double-right"></i>',
|
if (typeof pjax === 'object') pjax.refresh(container);
|
||||||
previous: '<i class="fa fa-angle-left"></i>',
|
container.querySelectorAll('.page-number').forEach(element => {
|
||||||
next : '<i class="fa fa-angle-right"></i>'
|
element.addEventListener('click', async event => {
|
||||||
},
|
event.preventDefault();
|
||||||
cssClasses: {
|
await searchAlgolia(searchText, Number(element.dataset.index));
|
||||||
list : ['pagination', 'algolia-pagination'],
|
});
|
||||||
item : 'pagination-item',
|
});
|
||||||
link : 'page-number',
|
}
|
||||||
selectedItem: 'current',
|
isSearching = false;
|
||||||
disabledItem: 'disabled-item'
|
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 = '<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
|
// Handle and trigger popup window
|
||||||
document.querySelectorAll('.popup-trigger').forEach(element => {
|
document.querySelectorAll('.popup-trigger').forEach(element => {
|
||||||
element.addEventListener('click', () => {
|
element.addEventListener('click', () => {
|
||||||
document.body.classList.add('search-active');
|
document.body.classList.add('search-active');
|
||||||
setTimeout(() => document.querySelector('.search-input').focus(), 500);
|
// Wait for search-popup animation to complete
|
||||||
|
setTimeout(() => input.focus(), 500);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
5
source/js/third-party/search/local-search.js
vendored
5
source/js/third-party/search/local-search.js
vendored
@ -13,22 +13,20 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const input = document.querySelector('.search-input');
|
const input = document.querySelector('.search-input');
|
||||||
|
const container = document.querySelector('.search-result-container');
|
||||||
|
|
||||||
const inputEventFunction = () => {
|
const inputEventFunction = () => {
|
||||||
if (!localSearch.isfetched) return;
|
if (!localSearch.isfetched) return;
|
||||||
const searchText = input.value.trim().toLowerCase();
|
const searchText = input.value.trim().toLowerCase();
|
||||||
const keywords = searchText.split(/[-\s]+/);
|
const keywords = searchText.split(/[-\s]+/);
|
||||||
const container = document.querySelector('.search-result-container');
|
|
||||||
let resultItems = [];
|
let resultItems = [];
|
||||||
if (searchText.length > 0) {
|
if (searchText.length > 0) {
|
||||||
// Perform local searching
|
// Perform local searching
|
||||||
resultItems = localSearch.getResultItems(keywords);
|
resultItems = localSearch.getResultItems(keywords);
|
||||||
}
|
}
|
||||||
if (keywords.length === 1 && keywords[0] === '') {
|
if (keywords.length === 1 && keywords[0] === '') {
|
||||||
container.classList.add('no-result');
|
|
||||||
container.innerHTML = '<div class="search-result-icon"><i class="fa fa-search fa-5x"></i></div>';
|
container.innerHTML = '<div class="search-result-icon"><i class="fa fa-search fa-5x"></i></div>';
|
||||||
} else if (resultItems.length === 0) {
|
} else if (resultItems.length === 0) {
|
||||||
container.classList.add('no-result');
|
|
||||||
container.innerHTML = '<div class="search-result-icon"><i class="far fa-frown fa-5x"></i></div>';
|
container.innerHTML = '<div class="search-result-icon"><i class="far fa-frown fa-5x"></i></div>';
|
||||||
} else {
|
} else {
|
||||||
resultItems.sort((left, right) => {
|
resultItems.sort((left, right) => {
|
||||||
@ -41,7 +39,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
const stats = CONFIG.i18n.hits.replace('${hits}', resultItems.length);
|
const stats = CONFIG.i18n.hits.replace('${hits}', resultItems.length);
|
||||||
|
|
||||||
container.classList.remove('no-result');
|
|
||||||
container.innerHTML = `<div class="search-stats">${stats}</div>
|
container.innerHTML = `<div class="search-stats">${stats}</div>
|
||||||
<hr>
|
<hr>
|
||||||
<ul class="search-result-list">${resultItems.map(result => result.item).join('')}</ul>`;
|
<ul class="search-result-list">${resultItems.map(result => result.item).join('')}</ul>`;
|
||||||
|
|||||||
@ -498,5 +498,14 @@ NexT.utils = {
|
|||||||
});
|
});
|
||||||
intersectionObserver.observe(element);
|
intersectionObserver.observe(element);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
debounce: function(func, wait) {
|
||||||
|
let timeout;
|
||||||
|
return function(...args) {
|
||||||
|
const context = this;
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(() => func.apply(context, args), wait);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user