让 Hexo 博客支持本地站内搜索

前言

最近给 Yelee 主题加上了本地搜索功能,终于能在自己的博客里愉快地搜索资料了。大致思路来源于 HaHack 的 教程,根据需要做了些调整。重新整理一下本地搜索折腾记录,分享于此。


Hexo Local Search

索引生成

要使用搜索,必须先生成博客索引数据,Hexo 可以通过插件生成:

1
npm install --save hexo-generator-search

插件默认只索引文章(post),要想页面(page)也能被检索,只需要在 Hexo 站点 _config.yml 中添加如下配置即可:

1
2
3
search:
path: search.xml
field: all

更多配置说明可到插件页面查看:hexo-generator-search

界面结构

因为自己博客是双栏,顺手就把搜索结果放在边栏中了,大致 HTML 结构如下:

1
2
3
4
5
6
<form id="search-form"> <!-- 搜索框相关 -->
<input type="text" id="local-search-input" name="q" results="0" placeholder="search..." class="search form-control" autocomplete="off" autocorrect="off"/>
<i class="fa fa-times" onclick="resetSearch()"></i> <!-- 清空/重置搜索框 -->
</form>
<div id="local-search-result"></div> <!-- 搜索结果区 -->
<p class='no-result'>No results found </p> <!-- 无匹配时显示,注意请在 CSS 中设置默认隐藏 -->

搜索重置按钮使用了 Font Awesome 图标,可按需更换

功能代码

基础代码

基础搜索函数 (jQuery) 来源于 HaHack 的教程,个人进行了些调整:
1.新标签中打开文章页面;2.减少截取的字符数;3.去掉部分非必要的代码

  • 使用时将下边代码保存为 js 文件并在页面中的合适位置引入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// A local search script with the help of [hexo-generator-search](https://github.com/PaicHyperionDev/hexo-generator-search)
// Copyright (C) 2015
// Joseph Pan <http://github.com/wzpan>
// Shuhao Mao <http://github.com/maoshuhao>
// Edited by MOxFIVE <http://github.com/MOxFIVE>

var searchFunc = function(path, search_id, content_id) {
'use strict';
$.ajax({
url: path,
dataType: "xml",
success: function( xmlResponse ) {
// get the contents from search data
var datas = $( "entry", xmlResponse ).map(function() {
return {
title: $( "title", this ).text(),
content: $("content",this).text(),
url: $( "url" , this).text()
};
}).get();
var $input = document.getElementById(search_id);
var $resultContent = document.getElementById(content_id);
$input.addEventListener('input', function(){
var str='<ul class=\"search-result-list\">';
var keywords = this.value.trim().toLowerCase().split(/[\s\-]+/);
$resultContent.innerHTML = "";
if (this.value.trim().length <= 0) {
return;
}
// perform local searching
datas.forEach(function(data) {
var isMatch = true;
var content_index = [];
var data_title = data.title.trim().toLowerCase();
var data_content = data.content.trim().replace(/<[^>]+>/g,"").toLowerCase();
var data_url = data.url;
var index_title = -1;
var index_content = -1;
var first_occur = -1;
// only match artiles with not empty titles and contents
if(data_title != '' && data_content != '') {
keywords.forEach(function(keyword, i) {
index_title = data_title.indexOf(keyword);
index_content = data_content.indexOf(keyword);
if( index_title < 0 && index_content < 0 ){
isMatch = false;
} else {
if (index_content < 0) {
index_content = 0;
}
if (i == 0) {
first_occur = index_content;
}
}
});
}
// show search results
if (isMatch) {
str += "<li><a href='"+ data_url +"' class='search-result-title' target='_blank'>"+ "> " + data_title +"</a>";
var content = data.content.trim().replace(/<[^>]+>/g,"");
if (first_occur >= 0) {
// cut out characters
var start = first_occur - 6;
var end = first_occur + 6;
if(start < 0){
start = 0;
}
if(start == 0){
end = 10;
}
if(end > content.length){
end = content.length;
}
var match_content = content.substr(start, end);
// highlight all keywords
keywords.forEach(function(keyword){
var regS = new RegExp(keyword, "gi");
match_content = match_content.replace(regS, "<em class=\"search-keyword\">"+keyword+"</em>");
})
str += "<p class=\"search-result\">" + match_content +"...</p>"
}
}
})
$resultContent.innerHTML = str;
})
}
})
}

调用函数

  • search.xml 使用默认路径,可以直接把下面代码放到 js 文件中
1
2
var path = "/search.xml";
searchFunc(path, 'local-search-input', 'local-search-result');
  • 上面的函数, search.xml 文件会跟随页面一起加载,如果索引文件太大,可能会影响页面加载速度,可以将其调整为激活搜索框时再下载所需文件
1
2
3
4
5
6
7
var inputArea = document.querySelector("#local-search-input");
var getSearchFile = function(){
var path = "/search.xml";
searchFunc(path, 'local-search-input', 'local-search-result');
}

inputArea.onfocus = function(){ getSearchFile() }

搜索重置

  • 提供按钮用于清空搜索结果和重置搜索框,按钮已经绑定了点击事件,直接写函数就行
1
2
3
4
5
6
7
8
9
10
var $resetButton = $("#search-form .fa-times");
var $resultArea = $("#local-search-result");

inputArea.oninput = function(){ $resetButton.show(); }
resetSearch = function(){
$resultArea.html("");
document.querySelector("#search-form").reset();
$resetButton.hide();
$(".no-result").hide();
}

屏蔽回车

  • 虽然用了表单但其实并没有数据要提交,所以这里需要手动屏蔽掉回车键
1
inputArea.onkeydown = function(){ if(event.keyCode==13) return false}

无搜索结果

  • 无搜索结果时,显示指定的提示内容。
  • 原本想在基础搜索函数上改,折腾无果,只能曲线救国,通过监听搜索区内容变动来判断是否有匹配的内容
1
2
3
4
5
6
7
$resultArea.bind("DOMNodeRemoved DOMNodeInserted", function(e) {
if (!$(e.target).text()) {
$(".no-result").show(200);
} else {
$(".no-result").hide();
}
})

CSS 样式

样式部分按自己喜好设计即可,下面是个人目前使用的样式,可参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/*搜索框*/
.search {
width: 68%;
height: 18px;
margin-top: 1px;
padding: 0;
font-family: inherit;
border: 2px solid transparent;
border-bottom: 2px solid #d3d3d3;
border-radius: 2px;
opacity: 0.65;
background: none;
}
.search:hover {
border: 2px solid #d3d3d3;
opacity: 1;
box-shadow: 0 0 10px rgba(0,0,0,0.3);
}

/*搜索重置按钮*/
#search-form .fa-times {
display: none;
padding: 1px 0.7em;
box-shadow: 0 0 3px rgba(0,0,0,0.15);
cursor: pointer;
color: #808080;
}
#search-form .fa-times:active {
background: #d3d3d3;
}
#search-form .fa-times:hover {
zoom: 1.1;
padding: 1px 0.6em;
border: 1px solid #d3d3d3;
box-shadow: 0 0 6px rgba(0,0,0,0.25);
}

/*搜索结果区*/
#local-search-result {
margin: auto -12% auto -6%;
font-size: 0.9em;
text-align: left;
word-break: break-all;
}

#local-search-result ul.search-result-list li:hover {
font-weight: normal;
}

/*单条搜索结果*/
#local-search-result li {
margin: 0.5em auto;
border-bottom: 2px solid #d3d3d3;
}
#local-search-result .search-result-list li:hover {
background: rgba(158,188,226,0.21);
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}

/*匹配的标题*/
#local-search-result a.search-result-title {
line-height: 1.2;
font-weight: bold;
color: #708090;
}

/*搜索预览段落*/
#local-search-result p.search-result {
margin: 0.4em auto;
line-height: 1.2em;
max-height: 3.6em;
overflow: hidden;
font-size: 0.8em;
text-align: justify;
color: #808080;
}

/*匹配的关键词*/
#local-search-result em.search-keyword {
color: #f58e90;
border-bottom: 1px dashed #f58e90;
font-weight: bold;
font-size: 0.85em;
}

/*无匹配搜索结果时显示*/
p.no-result {
display: none;
margin: 2em 0 2em 6%;
padding-bottom: 0.5em;
text-align: left;
color: #808080;
font-family: font-serif serif;
border-bottom: 2px solid #d3d3d3;
}

一些不足

  • [ ] 如果文章很多,索引文件可能很大,无论是随页面下载还是激活搜索框再下载,似乎都不理想;
  • [ ] 搜索结果中全部内容都转成了小写,应该有办法忽略大小写的同时,保持文本原始大小写格式;
  • [ ] 使用自动补全填上的内容并不能立即显示搜索结果,待改进。

相关链接

  1. jQuery-based Local Search Engine for Hexo by HaHack on 2015/10/08: http://hahack.com/codes/local-search-engine-for-hexo/
  2. feat: Local Site Search | 本地站内搜索 by MOxFIVE on 2016/05/25: https://github.com/MOxFIVE/hexo-theme-yelee/commit/0280f6a17d1a0fa1f52056856082e022dfac220c
  3. hexo-generator-search: https://github.com/PaicHyperionDev/hexo-generator-search
文章目录
  1. 1. 前言
  2. 2. 索引生成
  3. 3. 界面结构
  4. 4. 功能代码
    1. 4.1. 基础代码
    2. 4.2. 调用函数
    3. 4.3. 搜索重置
    4. 4.4. 屏蔽回车
    5. 4.5. 无搜索结果
  5. 5. CSS 样式
  6. 6. 一些不足
  7. 7. 相关链接
|