hexo本地静态搜索实现

背景

通过Hexo搭建了静态博客,使用的是hexo-theme-skapp主题,此主题默认支持本地搜索,但由于依赖了lunr库,一直无法安装成本lunr库,导致搜索功能一直无法使用。

同时为了更加深入的了解Hexo的原理,就想基于hexo-theme-skapp的源码修改,让其支持本地搜索。

思路分析

由于Hexo是静态博客,没有后台服务,不能利用常规后端做数据库的存储及查询来实现搜索功能,只能通过前端js来实现查询逻辑。

静态博客实现搜索的整个流程:

  1. 通过Hexo插件,生成所有文章的索引文件search.json
  2. 前端搜索页面,请求并载入索引文件search.json
  3. 解析检索词,并进行分词处理
  4. 在索引中进行搜索词的匹配
  5. 展示搜索结果

可复用主题hexo-theme-skapp上的搜索结果展示功能,只需要完成前面4步就行。

实现过程

生成索引文件search.json

刚开始是使用Hexo插件hexo-generator-search来实现此功能,但由于此插件生成的索引文件只有文章的基本数据,缺少很多关键数据,如cover, date, subtitle, author等等信息。

最终基于插件hexo-generator-search的源码重写索引生成功能,整体实现过程很简单,主要是从locals.posts对象里,读取需要数据存入索引文件里。

展示所需要的数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"cover": "页面头部图片地址",
"title": "标题",
"month": "十月",
"day": 31,
"date": "2020-10-31",
"url": "地址",
"desc": "字标题",
"authorNick": "作者",
"authorLink": "作者地址",
"tagArr": [
{
"name": "标签名",
"path": "标签地址"
}
]
}

上面的大部分都不难,只有一个功能比较麻烦,生成的月份需要转换为中文月份,通过一个数组进行转换,如下所示:

1
2
3
4
var MonthArr = ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"]

var dateObj = new Date(data.date);
var month = MonthArr[dateObj.getMonth()];

整体实现如下所示:

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
var pathFn = require('path');
var fs = require('fs');

function json_generator(locals){

var searchName = "search.json";
var datas = locals.posts.sort('-date') || [];
// var datas = locals.pages;

var MonthArr = ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"]
var res = new Array()
var index = 0

datas.each(function(data) {
if (data.indexing != undefined && !data.indexing) return;
var temp_data = new Object()

if (data.cover) {
temp_data.cover = data.cover
}
if (data.title) {
temp_data.title = data.title
}
if (data.date) {
var dateObj = new Date(data.date);
var year = dateObj.getFullYear();
var month = dateObj.getMonth();
var day = dateObj.getDate();

temp_data.month = MonthArr[month];
temp_data.day = day;
temp_data.date = year + "-" + (month+1) + "-" + day;
}
if (data.path) {
temp_data.url = '/' + data.path
}
if (data.subtitle) {
temp_data.desc = data.subtitle
}
if (data.author && data.author.nick) {
temp_data.authorNick = data.author.nick
}
if (data.author && data.author.link) {
temp_data.authorLink = data.author.link
}
// if (content != false && data._content) {
// temp_data.content = data._content
// }
if (data.tags && data.tags.length > 0) {
var tags = [];

data.tags.forEach(function (tag) {
tags.push({
'name': tag.name,
'path': '/' + tag.path
});
});
temp_data.tagArr = tags
}
res[index] = temp_data;
index += 1;
});

var json = JSON.stringify(res);

return {
path: searchName,
data: json
};
};

hexo.extend.generator.register('json', json_generator);

前端的检索逻辑

通过axios库请求索引文件search.json:

1
2
3
4
5
6
7
8
9
10
11
axios
.get('/search.json')
.then(function(res){
return res.data;
})
.then(function(data) {
self.initSearch(data);
})
.catch(function(error){
console.log(error);
});

获取搜索词,在索引中进行搜索词的匹配,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function searchFunc(queryString, datas) {
var result = [];

// 对搜索词做简单的分词
var keyword = queryString.trim();
console.log('11111', keyword);
// 本地查找
for(var dataIndex in datas){
var data = datas[dataIndex];

data.title = data.title || '';
data.content = data.content || '';

var index_title = data.title.indexOf(keyword);
var index_content = data.content.indexOf(keyword);
if (index_title >= 0 || index_content >= 0) {
result.push(data);
}
}

return result;
}

最后就是展示搜索结果,此处是复用主题hexo-theme-skapp的展示逻辑。

主题hexo-theme-skapp的其他个性化改造

很多文章只有标题数据,没有其他信息,导致列表显示很丑,如下所示:

修改方式是给各种信息添加默认值。

Cover图片添加默认图

此功能默认已经支持,只需要配置一下就行,如下所示:

1
2
# page default cover
default_cover: /hexo-img/default_cover.png

描述添加默认值

当文章缺少相应的描述信息时,获取文章正文的前200个字符,但由于正文里有html标签,需要使用相应的函数去掉html标签信息,实现代码如下所示:

1
2
3
4
5
6
7
8
<p itemprop="articleSection" class="min-article__desc">
{% if not post.subtitle %}
{{ truncate(strip_html(post.content), 200) }}
{% endif %}
{% if post.subtitle %}
{{ post.subtitle }}
{% endif %}
</p>

作者信息添加默认值

因为此博客是个人博客,作者信息本身可以不用配置,直接使用config里的作者信息就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
{% if post.author %}
<span itemprop="author" itemscope itemtype="http://schema.org/Person">
<a itemprop="url" href="{{ post.author.link }}" target="_blank">
<span itemprop="name">{{ titlecase(post.author.name || post.author.nick) }}</span>
</a>
</span>
{% else %}
<span itemprop="author" itemscope itemtype="http://schema.org/Person">
<a itemprop="url" href="{{ get_setting('author').link }}" target="_blank">
<span itemprop="name">{{ titlecase( get_setting('author').name ) }}</span>
</a>
</span>
{% endif %}

其他

类似的方式修改了其他几处问题:

  1. 详情页作者的默认显示
  2. 详情页文章的显示效果调整,字体调大为16px,减少间距到10px
  3. 修改首页的标题标的字体

参考

  1. 为 Hexo 博客创建本地搜索引擎
感谢您的阅读,本文由 刘阳 版权所有。如若转载,请注明出处:刘阳(https://handsomeliuyang.github.io/2020/10/31/%E7%BB%8F%E9%AA%8C%E6%80%BB%E7%BB%93/hexo%E6%9C%AC%E5%9C%B0%E9%9D%99%E6%80%81%E6%90%9C%E7%B4%A2%E5%AE%9E%E7%8E%B0/
VSCode插件开发之Markdown扩展功能
StudyDemos的Demo列表的动态配置实现