hexo-theme-volantis
Version:
Elegant and powerful theme for Hexo.
240 lines (213 loc) • 7.21 kB
JavaScript
let SearchService = (() => {
const fn = {};
let search, algolia, timerId;
fn.queryText = null;
fn.template = `<div id="u-search">
<div class="modal">
<header class="modal-header" class="clearfix">
<button type="submit" id="u-search-modal-btn-submit" class="u-search-btn-submit">
<span class="fa-solid fa-search"></span>
</button>
<div id="algolia-search-input"></div>
<a id="u-search-btn-close" class="btn-close"> <span class="fa-solid fa-times"></span> </a>
</header>
<main class="modal-body">
<div id="algolia-search-results">
<div id="algolia-hits">
<div class="search-icon"><i class="fa-sharp fa-solid fa-telescope"></i></i></div>
</div>
</div>
</main>
<footer>
<div id="algolia-pagination"></div>
<hr>
<div id="algolia-info">
<div class="algolia-stats"></div>
<div class="algolia-poweredBy"></div>
</div>
</footer>
</div>
<div id="modal-overlay" class="modal-overlay"></div>
</div>
`;
fn.init = () => {
let div = document.createElement("div");
div.innerHTML += fn.template;
document.body.append(div);
algolia = volantis.GLOBAL_CONFIG.search;
if (algolia.appId && algolia.apiKey && algolia.indexName) {
fn.event();
fn.setAlgolia();
} else {
document.querySelector('#u-search main.modal-body').innerHTML = 'Algolia setting is invalid!';
document.querySelector('#u-search main.modal-body').style.textAlign = 'center';
document.querySelector('#u-search .modal').style.maxHeight = '128px';
}
}
fn.event = () => {
document
.querySelector("#u-search-btn-close")
.addEventListener("click", fn.close, false);
document
.querySelector("#modal-overlay")
.addEventListener("click", fn.close, false);
document.querySelectorAll(".u-search-form").forEach((e) => {
e.addEventListener("submit", fn.onSubmit, false);
});
document.querySelector("#algolia-search-input").addEventListener("input", event => {
let input = event.target.querySelector(".ais-SearchBox-input");
if (input) {
fn.queryText = input.value;
} else {
fn.queryText = event.target.value;
}
})
}
fn.setAlgolia = () => {
search = instantsearch({
indexName: algolia.indexName,
searchClient: algoliasearch(algolia.appId, algolia.apiKey),
searchFunction(helper) {
helper.state.query && helper.search()
},
})
const configure = instantsearch.widgets.configure({
hitsPerPage: algolia.hitsPerPage
})
const searchBox = instantsearch.widgets.searchBox({
container: '#algolia-search-input',
autofocus: true,
showReset: false,
showSubmit: false,
showLoadingIndicator: false,
searchAsYouType: algolia.searchAsYouType,
placeholder: algolia.placeholder,
templates: {
input: 'algolia-input'
},
queryHook(query, refine) {
clearTimeout(timerId)
timerId = setTimeout(() => refine(query), 500)
}
})
const hits = instantsearch.widgets.hits({
container: '#algolia-hits',
templates: {
item(data) {
const keyword = !!fn.queryText ? `?keyword=${fn.queryText}` : ''
const link = data.permalink ? data.permalink : `${volantis.GLOBAL_CONFIG.root}${data.path}`
const result = data._highlightResult
const content = result.contentStripTruncate
? fn.cutContent(result.contentStripTruncate.value)
: result.contentStrip
? fn.cutContent(result.contentStrip.value)
: result.content
? fn.cutContent(result.content.value)
: ''
return `
<a href="${link}${keyword}" class="result">
<span class="title">${result.title.value || 'no-title'}</span>
<span class="digest">${content}</span>
</a>`
},
empty: function (data) {
return (
`<div id="resule-hits-empty"><i class="fa-solid fa-box-open"></i><p>${volantis.GLOBAL_CONFIG.languages.search.hits_empty.replace(/\$\{query}/, data.query)}</p></div>`
)
}
}
})
const stats = instantsearch.widgets.stats({
container: '#algolia-info > .algolia-stats',
templates: {
text: function (data) {
const stats = volantis.GLOBAL_CONFIG.languages.search.hits_stats
.replace(/\$\{hits}/, data.nbHits)
.replace(/\$\{time}/, data.processingTimeMS)
return (
`${stats}`
)
}
}
})
const powerBy = instantsearch.widgets.poweredBy({
container: '#algolia-info > .algolia-poweredBy',
theme: volantis.dark?.mode === 'dark' ? 'dark' : 'light'
})
const pagination = instantsearch.widgets.pagination({
container: '#algolia-pagination',
totalPages: 5,
templates: {
first: '<i class="fas fa-angle-double-left"></i>',
last: '<i class="fas fa-angle-double-right"></i>',
previous: '<i class="fas fa-angle-left"></i>',
next: '<i class="fas fa-angle-right"></i>'
}
})
search.addWidgets([configure, searchBox, hits, stats, powerBy, pagination])
search.start()
window.pjax && search.on('render', () => {
window.pjax.refresh(document.getElementById('algolia-hits'))
})
}
fn.setQueryText = queryText => {
fn.queryText = queryText;
if (!search) {
fn.init()
}
search?.setUiState({
[algolia.indexName]: {
query: queryText
}
})
}
fn.search = () => {
document.querySelector("#u-search").style.display = "block";
document.addEventListener("keydown", event => {
if (event.code === "Escape") {
fn.close();
}
}, { once: true })
}
fn.onSubmit = (event) => {
event.preventDefault();
let input = event.target.querySelector(".u-search-input");
fn.setQueryText(input?.value ? input.value : event.target.value)
fn.search();
};
fn.cutContent = content => {
if (content === '') return ''
const firstOccur = content.indexOf('<mark>')
let start = firstOccur - 30
let end = firstOccur + 120
let pre = ''
let post = ''
if (start <= 0) {
start = 0
end = 140
} else {
pre = '...'
}
if (end > content.length) {
end = content.length
} else {
post = '...'
}
let matchContent = pre + content.substring(start, end) + post
return matchContent
}
fn.close = () => {
document.querySelector("#u-search").style.display = "none";
};
return {
init: fn.init,
setQueryText: queryText => {
fn.setQueryText(queryText);
},
search: fn.search,
close: fn.close
}
})()
Object.freeze(SearchService);
SearchService.init();
document.addEventListener("pjax:send", SearchService.close);