对于静态站点,搜索功能应该是一个比较棘手的问题。对于内容比较庞大的网站,可能直接跳转到第三方搜索引擎比较适合。例如使用以下搜索指令:
关键词 site:www.beizigen.com
但这种方式容易造成用户跳出网站,体验也不如站内搜索好。另外,这种方式只能搜索到已被搜索引擎收录了的文章,新发表的文章则会被雪藏。
本文要介绍的是使用Fuse.js实现站内搜索,Fuse.js项目地址:
配置Hugo以支持搜索功能
修改Hugo配置文件,通常名称为hugo.yaml,参考Hugo配置文件说明。添加如下内容:
outputs:
home:
- HTML
- RSS
- JSON
在Hugo的content目录中创建search文件夹和_index.md文件:
/search/_index.md
_index.md的文件内容为:
---
title: 搜索
slug: search
---
在主题目录的如下路径创建index.json文件:
/layouts/_default/
index.json文件的内容为:
{{- $.Scratch.Add "index" slice -}}
{{- range .Site.RegularPages -}}
{{- $.Scratch.Add "index" (dict "title" .Title "contents" .Plain "permalink" .Permalink) -}}
{{- end -}}
{{- $.Scratch.Get "index" | jsonify -}}
dict中的字段可以自定义,该字段决定要输出的内容,例如添加tags的输出:
"tags" .tags
添加日期的输出:
"date" .Date.Format .Site.Params.dateFormat
输出的json格式如下:
[
{
"contents": "纯文本的文章内容...",
"permalink": "文章URL",
"title": "文章标题"
},
...
]
模板文件配置
在要添加搜索框的模板文件中(通常是头部文件),添加搜索表单:
在主题目录的如下路径创建search.html模板文件:
/layouts/_default/
search.html模板的内容大致如下,具体根据你的网站布局调整:
{{ define "main" }}
搜索中…
{{ end }}
注意引用的两个js文件路径根据你的实际情况填写。
search.js的文件内容如下:
const options = {
keys: [
'title',
'contents'
]
};
const paginate = 10;
const summaryLength = 90;
const pattern = param('q');
if (pattern) {
fetch('/index.json')
.then(response => response.ok ? response.json() : undefined)
.then(search);
}
function search(json) {
let fuse = new Fuse(json, options);
let result = fuse.search(pattern);
let elem = document.querySelector('#search-results');
if (result.length > 0) {
let maxpage = Math.ceil(result.length / paginate);
let paged = param('p');
if (!paged || paged maxpage) {
paged = maxpage;
}
let start = (paged - 1) * paginate;
let html = '';
for (let i = start; i < start + 10; i++) {
if (!result[i]) continue;
let data = result[i].item;
html += '';
html += '' + data.title + '
';
html += '' + data.contents.substring(0, summaryLength) + '…
';
html += ' ';
}
html += pagination(maxpage, paged, pattern);
elem.innerHTML = html;
} else {
elem.innerHTML = '没有找到结果,可能你要搜索的内容已逃离地球
';
}
}
function param(name) {
let url = new URL(window.location);
return url.searchParams.get(name);
}
function pagination(maxpage, paged, pattern) {
if (maxpage <= 1) return '';
paged = parseInt(paged);
let baseurl = '/search/?q=' + pattern;
let pagination = '';
if (paged > 1) {
pagination += '- «
';
} else {
pagination += '- «
';
}
let minpage = paged - 2;
if (minpage > maxpage - 4) minpage = maxpage - 4;
if (minpage < 1) minpage = 1;
for (let i = minpage; i maxpage) break;
if (i == paged) {
pagination += ' - ' + i + '
';
} else {
pagination += ' - ' + i + '
';
}
}
if (paged < maxpage) {
pagination += '- »
';
} else {
pagination += '- »
';
}
pagination += '
';
return pagination;
}
实现原理解说
对Hugo的一系列配置是为了在网站根目录生成一个index.json,这个json文件包含了所有文章的纯文本内容。
Fuse.js是一个可以搜索json文件内容的库,options配置项定义要搜索的键,本文示例只搜索了文章标题和文章内容:
const options = {
keys: [
'title',
'contents'
]
}
编写了一个param函数用来获取URL中的搜索关键词,fetch网络请求获取index.json的文件内容。
Fuse.js的简单用法如下:
初始化一个Fuse新对象:
let fuse = new Fuse(json, options);
Fuse构造函数需要传两个实参:
- json:之前获取的index.json的内容;
- options:Fuse配置项,这里主要定义了要搜索的字段;
Fuse.js还有一些有意思的配置项,比如定义字段权重:
keys: [
{name: "title", weight: 2},
{name: "contents", weight: 1},
]
Fuse的search方法搜索内容并返回结果:
let result = fuse.search(pattern);
pattern为搜索关键词,搜索的结果保存在变量result中。
剩下的就是JS的一些Dom处理,将搜索结果(json数据)写入页面,这里我做了分页处理,可根据自己的实际情况来编写代码。
这种方式的缺陷是,随着网站内容的增多,index.json文件的体积会越来越大,这样就会导致网络请求延时较长。后期可以考虑index.json只输出文章标题和文章链接,但这样以来就会导致无法搜索文章内容,搜索结果质量会大幅下降。
原创文章,作者:,如若转载,请注明出处:https://ce.771633.xyz/2032.html