hexo 添加文章搜索功能

官方有插件 hexo-generator-search,不过这插件有一下几点问题:

  1. 没移除特殊字符串,如标签,html字符,代码等,导致生成的搜索文件很大
  1. 所有文章内容按照 .md 源码文件原样输出到 content 字段,如果要存心拷贝盗取文章,直接复制 content 就可得到文章源码。
  1. 不方便自定义搜索字段

为解决以上问题,根据官方插件源码改造了一下,代码如下

  1. 添加文件 themes/landscape/scripts/generator_search_db.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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
const { deepMerge, stripHTML } = require('hexo-util');

hexo.config.search = deepMerge({
path: 'search.xml',
field: 'post'
}, hexo.config.search);

// 移除 hexo 标签,如 {% xxx %}
const stripHexoTag = (str) => str.replace(/\{%[\S\s]+?%\}/mg, '');

// 移除 代码,如 ` ` ` xxx ` ` `
const stripCode = (str) => str.replace(/```[\S\s]+?```/mg, '');

// 移除 换行符,如 \n\n
const stripLineChar = (str) => str.replace(/[\r\n]+?/mg, '');

// 移除 style 标签内容,如 < style>xxxx</>
const stripStyle = (str) => str.replace(/<style.*?>.+<\/style>/img, '');

// 移除 script 标签内容,如 <script>xxxx</script>
const stripScript = (str) => str.replace(/<script.*?>.+<\/script>/img, '');

// 移除 超链接,如 [github](https:www.github.com/xxx)
const stripUrl = (str) => str.replace(/(\[.+?\])\((http)?s?:[^)]+?\)/img, '$1');

// 移除特殊字符串
const stripSpecialString = (str) => (
stripUrl(
stripCode(
stripHexoTag(
stripHTML(
stripScript(
stripStyle(
stripLineChar(str)
)
)
)
)
)
)
);

hexo.extend.generator.register('json', function (locals) {
var config = this.config;
var searchConfig = config.search;
var searchField = searchConfig.field;
var content = searchConfig.content;

var posts, pages;

if (searchField.trim() != '') {
searchField = searchField.trim();
if (searchField == 'post') {
posts = locals.posts.sort('-date');
} else if (searchField == 'page') {
pages = locals.pages;
} else {
posts = locals.posts.sort('-date');
pages = locals.pages;
}
} else {
posts = locals.posts.sort('-date');
}

var res = new Array();
var index = 0;

if (posts) {
posts.each(function (post) {
if (post.indexing != undefined && !post.indexing) return;
var temp_post = new Object();
if (post.title) {
temp_post.title = post.title;
}
if (post.path) {
temp_post.url = config.root + post.path;
}
if (content != false && post._content) {
temp_post.content = stripSpecialString(post._content);
}
if (post.tags && post.tags.length > 0) {
var tags = [];
post.tags.forEach(function (tag) {
tags.push(tag.name);
});
temp_post.tags = tags;
}
if (post.categories && post.categories.length > 0) {
var categories = [];
post.categories.forEach(function (cate) {
categories.push(cate.name);
});
temp_post.categories = categories;
}
res[index] = temp_post;
index += 1;
});
}
if (pages) {
pages.each(function (page) {
if (page.indexing != undefined && !page.indexing) return;
var temp_page = new Object()
if (page.title) {
temp_page.title = page.title;
}
if (page.path) {
temp_page.url = config.root + page.path;
}
if (content != false && page._content) {
temp_page.content = stripSpecialString(page._content);
}
if (page.tags && page.tags.length > 0) {
var tags = new Array();
var tag_index = 0;
page.tags.each(function (tag) {
tags[tag_index] = tag.name;
});
temp_page.tags = tags;
}
if (page.categories && page.categories.length > 0) {
temp_page.categories = [];
(page.categories.each || page.categories.forEach)(function (item) {
temp_page.categories.push(item);
});
}
res[index] = temp_page;
index += 1;
});
}


var json = JSON.stringify(res);

return {
path: searchConfig.path,
data: json
};
});
  1. 修改主题模版 themes/landscape/layout/_partial/header.ejs

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    -      <div id="search-form-wrap">
    - <%- search_form({button: '&#xF002;'}) %>
    - </div>
    + <div class="search-form-wrap">
    + <form class="search-form">
    + <input type="search" name="q" class="search-form-input" id="js_site_search_input" placeholder="Search" autocomplete="off">
    + <button type="submit" class="search-form-submit">&#xF002;</button>
    + </form>
    + <div id="js_site_search_result" class="local-search-result">
    + </div
  1. 添加文件 themes/landscape/source/js/search.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
89
90
91
92
93
94
95
96
97
98
// 获取搜索文件
const getSearchFile = function () {
return new Promise((resolve, reject) => {
if (getSearchFile.ret) {
return resolve(getSearchFile.ret);
}
const path = '/search.json';
$.ajax({
url: path,
dataType: 'json',
success: function (data) {
getSearchFile.ret = data;
resolve(getSearchFile.ret);
},
error: function () {
resolve(null);
},
});
});
};

// 获取搜搜结果
const getSearchResult = (key) => {
const keywords = key.toLowerCase().split(/[\s\-]+/);
return getSearchFile().then(data => {
if (!data) {
return '';
}
let str = '<ul class="search-result-list">';
const beforeLength = 10; // content 关键词之前显示数量
const contentLength = 30; // content 显示长度
data.forEach(function ({ title = 'Untitled', content = '', url = '' }) {
title = title.trim().toLowerCase();
content = content.trim().toLowerCase().replace(/<[^>]+>/g, '');
let isMatch = true;
let indexTitle = -1;
let indexContent = -1;
let firstOccur = -1;
keywords.forEach(function (wd, i) {
indexTitle = title.indexOf(wd);
indexContent = content.indexOf(wd);

if (indexTitle < 0 && indexContent < 0) {
isMatch = false;
} else {
if (indexContent < 0) {
indexContent = 0;
}
if (i == 0) {
firstOccur = indexContent;
}
}
});
if (isMatch && content) {
str += '<li class="search-result-item"><a href=' + url + ' class="search-result-title" target="_blank">' + title + '</a>';
if (firstOccur >= 0) {
let start = Math.max(firstOccur - beforeLength, 0);

let match_content = content.substr(start, contentLength);

// highlight all keywords
keywords.forEach(function (wd) {
const reg = new RegExp(wd, 'gi');
match_content = match_content.replace(reg, '<em class="search-wd">' + wd + '</em>');
});

str += '<p class="search-result-content">' + match_content + '...</p>'
}
str += '</li>';
}
});
str += '</ul>';
if (str.indexOf('</li>') === -1) {
return '';
}
return str;
});
};

const event = () => {
const $resultContent = $('#js_site_search_result');
$('#js_site_search_input').on('input', function () {
const val = $(this).val().trim();
$resultContent.html('');
if (val.length <= 0) {
return;
}
$resultContent.html('<div><span class="local-search-empty">正在载入索引文件,请稍后……<span></div>');
getSearchResult(val).then(ret => {
if (!ret) {
return $resultContent.html('<div><span class="local-search-empty">没有找到内容,请尝试更换检索词。<span></div>');
}
$resultContent.html(ret);
});
});
};

export default event;
  1. 引入js并执行,修改文件 themes/landscape/source/js/search.js
1
2
+ import search from './search';
+ search();
  1. 添加样式 themes/landscape/source/css/_partial/search.styl
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
.local-search-result
position: absolute
z-index: 99
width: 240px
top: 30px
right: 0
.local-search-empty
color: #888
line-height: 1.6em
text-align: center
display: block
font-size: 16px
font-weight: normal
background: #fff
padding: 5px 8px
border-radius: 3px
ul
width: 100%
max-height: 450px
min-height: 0
height: auto
overflow-y: auto
border: 1px solid color-border
padding: 10px
background: #FFF
box-sizing: border-box
border-radius: 3px

// 滚动条样式
&::-webkit-scrollbar
width: 5px
height: 5px
/*滑块*/
&::-webkit-scrollbar-thumb
background-color: #666
&::-webkit-scrollbar-thumb:hover
background-color: #999
/*滑道*/
&::-webkit-scrollbar-track
background-color: #ccc

.search-result-item
text-align: left
border-bottom: 1px solid color-border
padding: 10px 0
font-weight: normal
&:last-child
border-bottom: none
margin-bottom: 0
.search-wd
color: #e58c7c
.search-result-title
line-height: 1.4em
font-size: 16px
color: color-default
display block
ell()
.search-result-content
margin-top: 10px
font-size: 14px
overflow: hidden
  1. 引入样式,修改文件 themes/landscape/source/css/style.styl
1
+ @import "_partial/search"
  1. 重启项目,如果不出意外,顶部导航栏输入字符串应该可搜索,效果如下图:
本文由 linx(544819896@qq.com) 创作,采用 CC BY 4.0 CN协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。本文链接为: https://blog.jijian.link/2020-01-22/hexo-search/

如果您觉得文章不错,可以点击文章中的广告支持一下!