【快速教程】Hexo站内搜索的实现

TOC
  1. 1. 源码

源码

本主题站内搜索的实现, 可从标题和内容中搜索单个或多个词条,详情看源码。

<article>
<h1 class="title"><%= page.title %> </h1>
<div class="entry-content wrapper">
<div id="search">
<input type="text" placeholder="请输入关键字" id="Jinput">
</div>
<div class="info">
本次搜索结果共<span id="Jcount">0</span>
</div>
<div id="JresList"></div>

<%
let posts = site.posts.data
let searchData = []
posts.forEach(item => {
searchData.push({
title: trim(item.title),
content: trim(strip_html(item.content)).replace(/&#123;/g,'{').replace(/&#125;/g,'}'),
url:'/'+item.path,
link: item.link,
plink: item.permalink
})
});
let _searchData = JSON.stringify(searchData)
%>


<script>
let searchData = <%- _searchData %>

let searchMod = {
searchTitle: true, // 是否在标题中搜索
searchContent: 1, // 是否在内容中搜索
contentPieces: 2, // 内容切割块数
excludes: ['{{', '}}', '{', '}', '.', '/', '\\', '。'], // 粗略过滤部分字符串
contentDecoration: 50, // 内容命中关键字增加前后50个字
roughFetch: false, // 是否粗略提取, 仅对文本内容有效
trimStr(str) {
return str.trim().toLowerCase()
},
init(input) {
input.addEventListener('input', (e) => {
let searchText = this.trimStr(input.value)
this.fetchTxtFromDB(searchText, searchData)
});
},
fetchTxtFromDB(searchText, DB) {
let resultArr = []

if (searchText) {
let searchTextArr = searchText.split(/[\s\-]+/); // 非首尾空格分割词条
searchTextArr = searchTextArr.filter((item) => {
return !this.excludes.includes(item);
})

if (searchTextArr.length > 1) {
searchTextArr.push(searchText); // 保留原始词条做完全匹配
}

resultArr = DB.filter((item) => {
let articleTitle = this.trimStr(item.title);
let titleHitArr = []
item.tmpTitleArr = []
item._title = ''


let articleContent = this.trimStr(item.content);
let contentHitArr = []
item.tmpContentArr = []
item.indexArr = [] // 词条在内容中第一次出现的位置
item._content = '' // 存储已经高亮关键字的原文
item._contentSliceArr = [] // 存储截取后的原文碎片

searchTextArr.map((one, index) => { // 求出searchTextArr有多少个命中的词条
if (this.searchTitle) {
let b = articleTitle.indexOf(one) > -1;
b && titleHitArr.push(one)
}

if (this.searchContent) {
let b2 = articleContent.indexOf(one) > -1
b2 && contentHitArr.push(one)
}
})

if (this.searchTitle && titleHitArr.length) {
titleHitArr.map((one, index) => {
let keyWordHtml = this.keyWordTpl().replace(/{{searchText}}/, one),
titleArrWithoutKeyWord = (!index ? articleTitle : item.tmpTitleArr[index - 1]).split(one), // 用数组切割的方式把所有文本内包含的关键词替换出来, 下一次的分割依赖上一次分割结果
highlightTxt = titleArrWithoutKeyWord.join(keyWordHtml);

item.tmpTitleArr.push(highlightTxt); // 记录前一次的结果,用于下一次计算
item._title = highlightTxt; // 最后一次为最终结果
})

delete item.tmpTitleArr
}

if (this.searchContent && contentHitArr.length) {
contentHitArr.map((one, index) => {
if (this.roughFetch) { // 先高亮后截取(可能造成高亮标签切割问题)
let keyWordHtml = this.keyWordTpl().replace(/{{searchText}}/, one),
contentArrWithoutKeyWord = (!index ? articleContent : item.tmpContentArr[index - 1]).split(one),
highlightTxt = contentArrWithoutKeyWord.join(keyWordHtml);

item.tmpContentArr.push(highlightTxt);
item._content = highlightTxt;
} else {
item.indexArr.push({
start: articleContent.indexOf(one), // 记录词条第一次出现的位置,和词条的长度
length: one.length,
str: one,
})
}
})

item.indexArr.map((one, index) => { // 有可能把<b>标签给切割了,造成样式异常, 需要另外处理
let keyWordEndPosition = one.start + one.length // 词条的结束位置,用于计算截取长度
let content = this.roughFetch ? item._content : item.content;
let contentLen = content.length;

if (one.start > this.contentDecoration) {
let highLightEndPosition = keyWordEndPosition + this.contentDecoration
highLightEndPosition = highLightEndPosition > contentLen ? contentLen : highLightEndPosition
item._contentSliceArr.push('...'+content.slice(one.start - this.contentDecoration, highLightEndPosition) + '...')
} else {
let highLightEndPosition = keyWordEndPosition + this.contentDecoration * 2
highLightEndPosition = highLightEndPosition > contentLen ? contentLen : highLightEndPosition
item._contentSliceArr.push('...'+content.slice(0, highLightEndPosition) + '...')
}
})

if (!this.roughFetch) {
contentHitArr.map((one, index) => {
let keyWordHtml = this.keyWordTpl().replace(/{{searchText}}/, one),
contentArrWithoutKeyWord = (!index ? item._contentSliceArr.join('<i style="display:block;padding-top:10px"></i>') : item.tmpContentArr[index - 1]).split(one),
highlightTxt = contentArrWithoutKeyWord.join(keyWordHtml);

item.tmpContentArr.push(highlightTxt);
item._content = highlightTxt
})
}

delete item.tmpContentArr
delete item._contentSliceArr
delete item.indexArr
}

return (this.searchTitle && titleHitArr.length) || (this.searchContent && contentHitArr.length)
})
}

this.resultArr = resultArr;
this.render(JresList, Jcount, resultArr)
},
render(ele, countEle, resultArr) {
let htmlArr = resultArr.map((item) => {
return this.itemTpl()
.replace(/{{url}}/g, item.url)
.replace(/{{title}}/g, item._title || item.title)
.replace(/{{content}}/g, item._content)
})

ele.innerHTML = htmlArr.join('')
countEle.innerHTML = htmlArr.length
},
itemTpl() {
return `
<li class="item">
<a target="_blank" href="{{url}}">
<div class="search-result-title">{{title}}</div>
<p class="search-result search-result-link">{{content}}</p>
</a>
</li>
`;
},
keyWordTpl() {
return `<b class="search-keyword">{{searchText}}</b>`
}
}

searchMod.init(Jinput)
</script>
</div>
</article>

访客评论