mirror of
https://github.com/jerryc127/hexo-theme-butterfly.git
synced 2025-09-15 12:58:48 +08:00
feat: 本地搜索增加preload,cdn 配置 closed #859
feat: 搜索內容顯示區域變大 feat: algolia 搜索增加文章內容顯示和高亮 feat: 搜索內容增加 ... 縮略辨識 fix: algolia 第一次進入時會自動搜索 fix: mathjax 顯示不完全的 bug
This commit is contained in:
parent
3cb2120630
commit
a33602e023
@ -51,6 +51,8 @@ algolia_search:
|
||||
# Local search
|
||||
local_search:
|
||||
enable: false
|
||||
preload: false
|
||||
CDN:
|
||||
|
||||
# Math (數學)
|
||||
# --------------------------------------
|
||||
|
||||
@ -19,7 +19,8 @@
|
||||
let localSearch = 'undefined';
|
||||
if (theme.local_search && theme.local_search.enable) {
|
||||
localSearch = JSON.stringify({
|
||||
path: config.search.path,
|
||||
path: theme.local_search.CDN ? theme.local_search.CDN : config.root + config.search.path,
|
||||
preload: theme.local_search.preload,
|
||||
languages: {
|
||||
// search languages
|
||||
hits_empty: _p("search.local_search.hits_empty"),
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hexo-theme-butterfly",
|
||||
"version": "4.2.0-b1",
|
||||
"version": "4.2.0-b2",
|
||||
"description": "A Simple and Card UI Design theme for Hexo",
|
||||
"main": "package.json",
|
||||
"scripts": {
|
||||
|
||||
@ -54,14 +54,15 @@ hexo.extend.helper.register('related_posts', function (currentPost, allPosts) {
|
||||
relatedPosts[i].cover === false
|
||||
? relatedPosts[i].randomcover
|
||||
: relatedPosts[i].cover
|
||||
result += `<div><a href="${this.url_for(relatedPosts[i].path)}" title="${relatedPosts[i].title}">`
|
||||
const title = this.escape_html(relatedPosts[i].title)
|
||||
result += `<div><a href="${this.url_for(relatedPosts[i].path)}" title="${title}">`
|
||||
result += `<img class="cover" src="${this.url_for(cover)}" alt="cover">`
|
||||
if (dateType === 'created') {
|
||||
result += `<div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> ${this.date(relatedPosts[i].created, hexoConfig.date_format)}</div>`
|
||||
} else {
|
||||
result += `<div class="content is-center"><div class="date"><i class="fas fa-history fa-fw"></i> ${this.date(relatedPosts[i].updated, hexoConfig.date_format)}</div>`
|
||||
}
|
||||
result += `<div class="title">${relatedPosts[i].title}</div>`
|
||||
result += `<div class="title">${title}</div>`
|
||||
result += '</div></a></div>'
|
||||
}
|
||||
|
||||
|
||||
@ -89,6 +89,7 @@ mjx-container[display],
|
||||
.has-jax
|
||||
overflow-x: auto
|
||||
overflow-y: hidden
|
||||
line-height: normal !important
|
||||
|
||||
.aplayer
|
||||
color: $font-black
|
||||
|
||||
@ -11,7 +11,8 @@
|
||||
color: var(--search-input-color)
|
||||
|
||||
.ais-Hits-list
|
||||
padding-left: 24px
|
||||
margin: 0
|
||||
padding: 0
|
||||
@extend .list-beauty
|
||||
|
||||
a
|
||||
@ -25,12 +26,17 @@
|
||||
color: $search-keyword-highlight
|
||||
font-weight: bold
|
||||
|
||||
.algolia-hit-item-content
|
||||
margin: 0 0 8px
|
||||
word-break: break-all
|
||||
|
||||
.ais-Pagination
|
||||
margin: 16px 0 0
|
||||
margin: 20px 0 0
|
||||
padding: 0
|
||||
text-align: center
|
||||
|
||||
.ais-Pagination-list
|
||||
margin: 0
|
||||
padding: 0
|
||||
list-style: none
|
||||
|
||||
@ -52,7 +58,27 @@
|
||||
color: #eee
|
||||
cursor: default
|
||||
|
||||
.ais-Pagination-item--disabled
|
||||
visibility: hidden
|
||||
|
||||
.algolia-logo
|
||||
padding-top: 2px
|
||||
width: 80px
|
||||
height: 30px
|
||||
|
||||
#algolia-hits
|
||||
> div
|
||||
overflow-y: scroll
|
||||
|
||||
+minWidth768()
|
||||
max-height: calc(80vh - 240px)
|
||||
|
||||
+maxWidth768()
|
||||
height: calc(100vh - 260px)
|
||||
|
||||
.apple
|
||||
#algolia-search
|
||||
#algolia-hits
|
||||
> div
|
||||
+maxWidth768()
|
||||
height: calc(90vh - 260px)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
.search-dialog
|
||||
position: fixed
|
||||
top: 100px
|
||||
top: 10%
|
||||
left: 50%
|
||||
z-index: 1001
|
||||
display: none
|
||||
|
||||
@ -60,7 +60,7 @@
|
||||
|
||||
.search-result-list
|
||||
overflow-y: auto
|
||||
max-height: 210px
|
||||
max-height: calc(80vh - 130px)
|
||||
|
||||
+maxWidth768()
|
||||
padding-bottom: 40px
|
||||
|
||||
@ -26,16 +26,39 @@ window.addEventListener('load', () => {
|
||||
|
||||
const searchClickFn = () => {
|
||||
document.querySelector('#search-button > .search').addEventListener('click', openSearch)
|
||||
}
|
||||
|
||||
const searchClickFnOnce = () => {
|
||||
document.getElementById('search-mask').addEventListener('click', closeSearch)
|
||||
document.querySelector('#algolia-search .search-close-button').addEventListener('click', closeSearch)
|
||||
}
|
||||
|
||||
searchClickFn()
|
||||
const cutContent = content => {
|
||||
if (content === '') return ''
|
||||
|
||||
window.addEventListener('pjax:complete', function () {
|
||||
getComputedStyle(document.querySelector('#algolia-search .search-dialog')).display === 'block' && closeSearch()
|
||||
searchClickFn()
|
||||
})
|
||||
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
|
||||
}
|
||||
|
||||
const algolia = GLOBAL_CONFIG.algolia
|
||||
const isAlgoliaValid = algolia.appId && algolia.apiKey && algolia.indexName
|
||||
@ -43,38 +66,37 @@ window.addEventListener('load', () => {
|
||||
return console.error('Algolia setting is invalid!')
|
||||
}
|
||||
|
||||
const searchClient = window.algoliasearch(algolia.appId, algolia.apiKey)
|
||||
const search = instantsearch({
|
||||
indexName: algolia.indexName,
|
||||
searchClient
|
||||
searchClient: algoliasearch(algolia.appId, algolia.apiKey),
|
||||
searchFunction(helper) {
|
||||
helper.state.query && helper.search()
|
||||
},
|
||||
})
|
||||
|
||||
search.addWidgets([
|
||||
instantsearch.widgets.configure({
|
||||
const configure = instantsearch.widgets.configure({
|
||||
hitsPerPage: 5
|
||||
})
|
||||
])
|
||||
|
||||
search.addWidgets([
|
||||
instantsearch.widgets.searchBox({
|
||||
const searchBox = instantsearch.widgets.searchBox({
|
||||
container: '#algolia-search-input',
|
||||
showReset: false,
|
||||
showSubmit: false,
|
||||
placeholder: GLOBAL_CONFIG.algolia.languages.input_placeholder,
|
||||
showLoadingIndicator: true
|
||||
})
|
||||
])
|
||||
|
||||
search.addWidgets([
|
||||
instantsearch.widgets.hits({
|
||||
const hits = instantsearch.widgets.hits({
|
||||
container: '#algolia-hits',
|
||||
templates: {
|
||||
item: function (data) {
|
||||
item(data) {
|
||||
console.log(data);
|
||||
const link = data.permalink ? data.permalink : (GLOBAL_CONFIG.root + data.path)
|
||||
return `
|
||||
<a href="${link}" class="algolia-hit-item-link">
|
||||
${data._highlightResult.title.value || 'no-title'}
|
||||
</a>`
|
||||
</a>
|
||||
<p class="algolia-hit-item-content">${cutContent(data._highlightResult.contentStripTruncate.value)}</p>`
|
||||
},
|
||||
empty: function (data) {
|
||||
return (
|
||||
@ -85,10 +107,8 @@ window.addEventListener('load', () => {
|
||||
}
|
||||
}
|
||||
})
|
||||
])
|
||||
|
||||
search.addWidgets([
|
||||
instantsearch.widgets.stats({
|
||||
const stats = instantsearch.widgets.stats({
|
||||
container: '#algolia-stats',
|
||||
templates: {
|
||||
text: function (data) {
|
||||
@ -102,10 +122,8 @@ window.addEventListener('load', () => {
|
||||
}
|
||||
}
|
||||
})
|
||||
])
|
||||
|
||||
search.addWidgets([
|
||||
instantsearch.widgets.pagination({
|
||||
const pagination = instantsearch.widgets.pagination({
|
||||
container: '#algolia-pagination',
|
||||
totalPages: 5,
|
||||
templates: {
|
||||
@ -115,9 +133,20 @@ window.addEventListener('load', () => {
|
||||
next: '<i class="fas fa-angle-right"></i>'
|
||||
}
|
||||
})
|
||||
])
|
||||
|
||||
|
||||
search.addWidgets([configure,searchBox,hits,stats,pagination]) // add the widgets to the instantsearch instance
|
||||
|
||||
search.start()
|
||||
|
||||
searchClickFn()
|
||||
searchClickFnOnce()
|
||||
|
||||
window.addEventListener('pjax:complete', () => {
|
||||
getComputedStyle(document.querySelector('#algolia-search .search-dialog')).display === 'block' && closeSearch()
|
||||
searchClickFn()
|
||||
})
|
||||
|
||||
window.pjax && search.on('render', () => {
|
||||
window.pjax.refresh(document.getElementById('algolia-hits'))
|
||||
})
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
window.addEventListener('load', () => {
|
||||
let loadFlag = false
|
||||
let dataObj = []
|
||||
const $searchMask = document.getElementById('search-mask')
|
||||
|
||||
const openSearch = () => {
|
||||
const bodyStyle = document.body.style
|
||||
bodyStyle.width = '100%'
|
||||
bodyStyle.overflow = 'hidden'
|
||||
btf.animateIn(document.getElementById('search-mask'), 'to_show 0.5s')
|
||||
btf.animateIn($searchMask, 'to_show 0.5s')
|
||||
btf.animateIn(document.querySelector('#local-search .search-dialog'), 'titleScale 0.5s')
|
||||
setTimeout(() => { document.querySelector('#local-search-input input').focus() }, 100)
|
||||
if (!loadFlag) {
|
||||
search(GLOBAL_CONFIG.localSearch.path)
|
||||
search()
|
||||
loadFlag = true
|
||||
}
|
||||
// shortcut: ESC
|
||||
@ -25,35 +28,35 @@ window.addEventListener('load', () => {
|
||||
bodyStyle.width = ''
|
||||
bodyStyle.overflow = ''
|
||||
btf.animateOut(document.querySelector('#local-search .search-dialog'), 'search_close .5s')
|
||||
btf.animateOut(document.getElementById('search-mask'), 'to_hide 0.5s')
|
||||
btf.animateOut($searchMask, 'to_hide 0.5s')
|
||||
}
|
||||
|
||||
// click function
|
||||
const searchClickFn = () => {
|
||||
document.querySelector('#search-button > .search').addEventListener('click', openSearch)
|
||||
document.getElementById('search-mask').addEventListener('click', closeSearch)
|
||||
document.querySelector('#local-search .search-close-button').addEventListener('click', closeSearch)
|
||||
}
|
||||
|
||||
searchClickFn()
|
||||
const searchClickFnOnce = () => {
|
||||
document.querySelector('#local-search .search-close-button').addEventListener('click', closeSearch)
|
||||
$searchMask.addEventListener('click', closeSearch)
|
||||
if (GLOBAL_CONFIG.localSearch.preload) dataObj = fetchData(GLOBAL_CONFIG.localSearch.path)
|
||||
}
|
||||
|
||||
// pjax
|
||||
window.addEventListener('pjax:complete', function () {
|
||||
getComputedStyle(document.querySelector('#local-search .search-dialog')).display === 'block' && closeSearch()
|
||||
searchClickFn()
|
||||
})
|
||||
// check url is json or not
|
||||
const isJson = url => {
|
||||
const reg = /\.json$/
|
||||
return reg.test(url)
|
||||
}
|
||||
|
||||
async function search (path) {
|
||||
let datas = []
|
||||
const typeF = path.split('.')[1]
|
||||
const response = await fetch(GLOBAL_CONFIG.root + path)
|
||||
if (typeF === 'json') {
|
||||
datas = await response.json()
|
||||
} else if (typeF === 'xml') {
|
||||
const fetchData = async (path) => {
|
||||
let data = []
|
||||
const response = await fetch(path)
|
||||
if (isJson(path)) {
|
||||
data = await response.json()
|
||||
} else {
|
||||
const res = await response.text()
|
||||
const t = await new window.DOMParser().parseFromString(res, 'text/xml')
|
||||
const a = await t
|
||||
datas = [...a.querySelectorAll('entry')].map(function (item) {
|
||||
data = [...a.querySelectorAll('entry')].map(item =>{
|
||||
return {
|
||||
title: item.querySelector('title').textContent,
|
||||
content: item.querySelector('content') && item.querySelector('content').textContent,
|
||||
@ -66,10 +69,18 @@ window.addEventListener('load', () => {
|
||||
$loadDataItem.nextElementSibling.style.display = 'block'
|
||||
$loadDataItem.remove()
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
const search = () => {
|
||||
if (!GLOBAL_CONFIG.localSearch.preload) {
|
||||
dataObj = fetchData(GLOBAL_CONFIG.localSearch.path)
|
||||
}
|
||||
|
||||
const $input = document.querySelector('#local-search-input input')
|
||||
const $resultContent = document.getElementById('local-search-results')
|
||||
const $loadingStatus = document.getElementById('loading-status')
|
||||
|
||||
$input.addEventListener('input', function () {
|
||||
const keywords = this.value.trim().toLowerCase().split(/[\s]+/)
|
||||
if (keywords[0] !== '') $loadingStatus.innerHTML = '<i class="fas fa-spinner fa-pulse"></i>'
|
||||
@ -79,7 +90,8 @@ window.addEventListener('load', () => {
|
||||
if (keywords.length <= 0) return
|
||||
let count = 0
|
||||
// perform local searching
|
||||
datas.forEach(function (data) {
|
||||
dataObj.then(data => {
|
||||
data.forEach(data => {
|
||||
let isMatch = true
|
||||
let dataTitle = data.title ? data.title.trim().toLowerCase() : ''
|
||||
const dataContent = data.content ? data.content.trim().replace(/<[^>]+>/g, '').toLowerCase() : ''
|
||||
@ -87,9 +99,9 @@ window.addEventListener('load', () => {
|
||||
let indexTitle = -1
|
||||
let indexContent = -1
|
||||
let firstOccur = -1
|
||||
// only match artiles with not empty titles and contents
|
||||
// only match articles with not empty titles and contents
|
||||
if (dataTitle !== '' || dataContent !== '') {
|
||||
keywords.forEach(function (keyword, i) {
|
||||
keywords.forEach((keyword, i) => {
|
||||
indexTitle = dataTitle.indexOf(keyword)
|
||||
indexContent = dataContent.indexOf(keyword)
|
||||
if (indexTitle < 0 && indexContent < 0) {
|
||||
@ -115,6 +127,8 @@ window.addEventListener('load', () => {
|
||||
// let end = firstOccur + 50 > dataContent.length ? dataContent.length : firstOccur + 50
|
||||
let start = firstOccur - 30
|
||||
let end = firstOccur + 100
|
||||
let pre = ''
|
||||
let post = ''
|
||||
|
||||
if (start < 0) {
|
||||
start = 0
|
||||
@ -122,16 +136,20 @@ window.addEventListener('load', () => {
|
||||
|
||||
if (start === 0) {
|
||||
end = 100
|
||||
} else {
|
||||
pre = '...'
|
||||
}
|
||||
|
||||
if (end > dataContent.length) {
|
||||
end = dataContent.length
|
||||
} else {
|
||||
post = '...'
|
||||
}
|
||||
|
||||
let matchContent = dataContent.substring(start, end)
|
||||
|
||||
// highlight all keywords
|
||||
keywords.forEach(function (keyword) {
|
||||
keywords.forEach(keyword => {
|
||||
const regS = new RegExp(keyword, 'gi')
|
||||
matchContent = matchContent.replace(regS, '<span class="search-keyword">' + keyword + '</span>')
|
||||
dataTitle = dataTitle.replace(regS, '<span class="search-keyword">' + keyword + '</span>')
|
||||
@ -141,7 +159,7 @@ window.addEventListener('load', () => {
|
||||
count += 1
|
||||
|
||||
if (dataContent !== '') {
|
||||
str += '<p class="search-result">' + matchContent + '...</p>'
|
||||
str += '<p class="search-result">' + pre + matchContent + post + '</p>'
|
||||
}
|
||||
}
|
||||
str += '</div>'
|
||||
@ -156,5 +174,15 @@ window.addEventListener('load', () => {
|
||||
if (keywords[0] !== '') $loadingStatus.innerHTML = ''
|
||||
window.pjax && window.pjax.refresh($resultContent)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
searchClickFn()
|
||||
searchClickFnOnce()
|
||||
|
||||
// pjax
|
||||
window.addEventListener('pjax:complete', () => {
|
||||
!btf.isHidden($searchMask) && closeSearch()
|
||||
searchClickFn()
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
Reference in New Issue
Block a user