Hugo实现简单站内搜索功能

对于静态站点,搜索功能应该是一个比较棘手的问题。对于内容比较庞大的网站,可能直接跳转到第三方搜索引擎比较适合。例如使用以下搜索指令:

关键词 site:www.beizigen.com

但这种方式容易造成用户跳出网站,体验也不如站内搜索好。另外,这种方式只能搜索到已被搜索引擎收录了的文章,新发表的文章则会被雪藏。

本文要介绍的是使用Fuse.js实现站内搜索,Fuse.js项目地址:

https://fusejs.io/

配置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

Like (0)
Previous 2025年3月8日
Next 2025年3月8日

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注