给 Hugo 添加 Pagefind 全文搜索
一。前言
一直想给博客添加一个文章内容搜索的功能,之前看到有用 Fuse.js 和 Algolia 来实现的,浅浅尝试了这两种发现都不是很符合自己的需求,并且折腾起来麻烦,所以一顿搜索,偶然间发现了 Pagefind,配置简单同时功能完美契合自己的需求。
二。数据准备
1.首先进入到 Hugo 博客项目的根目录,安装 Pagefind,自己使用 npm 来管理包的,所以对应的命令是 npm install -D pagefind
。
2.输入命令 hugo
,生成静态页面到 public 目录下,做好生成全文索引前的数据准备工作。
3.生成索引,在当前目录下,准备一个 pagefine.yml 配置文件。然后执行 npx pagefind
命令,这时会根据配置来生成索数据。当然也可以不用配置文件,在刚刚命令后面指定对应参数,同样可以达到目的。
# pagefind.ygml
source: public
glob: "{p/*/index.html,card/*/index.html}" # 需要索引的目录
indexing:
language: "zh" # 强制中文分词
heading_behavior: all_text # 关键设置:标题全文索引(非仅前缀)
term_split: 12 # 提高中文分词粒度
4.执行完上诉命令后,会在 public 目录下面,看到一个 pagefind 文件夹,里面就是刚刚生成的搜索索引的数据,到这里前期的数据基本准备好了。
三。搜索展示
搜索功能,我是把它集成到现有的 nav 导航栏上,也就是多加一个菜单,点击跳转到搜索页面来检索信息。
1.准备导航菜单项目,在 content 目录下,新建一个 search 文件夹,里面准备一个 _index.md 文件。
+++
title = "Search"
menu = "main"
weight = 6
+++
2.布局文件准备,在 layouts 下面,新建 search 文件夹,里面准备一个 list.html 文件,这里就是生成搜索框和结果的地方。
{{ define "main" }}
<div style="margin-top: 15px;"></div>
<div id="search">
<link href="/pagefind/pagefind-ui.css" rel="stylesheet">
<script src="/pagefind/pagefind-ui.js" type="module"></script>
<script>
window.addEventListener('DOMContentLoaded', (event) => {
new PagefindUI({
element: "#search",
showSubResults: false,
showImages: false,
showFilters: true,
filters: ["tag", "type"]
});
});
</script>
{{ end }}
3.在第二步里面,引用了 pagefind-ui.css 和 pagefind-ui.js,它们可以在第一步生成的 pagemfind 文件里面找到,把他们放到 static/pagefind/ 目录下。
4.进入搜索页面验证下查询效果,到这里基础的功能基本上实现了。
四。遇到的问题
1.开始生成标题的问题,默认把 header.html 里面的标题也放进索引,导致搜索时所有文章的标题都是这个,因为他比文章的标题级别高,优先展示它。所以这里用 data-pagefind-ignore 来达到忽略的效果;同样对于 _default/single.html 的文章标题,增加 data-pagefind-meta,能够被索引文件显式记录。
# header.html
<a href="{{ "" | relURL }}" class="title">
<h1 data-pagefind-ignore>{{ .Site.Title }}</h1>
</a>
# single.html
<h1 data-pagefind-meta="title">{{ .Title }}</h1>
2.由于博客是中英文都存在的情况,其中中文占大部分,所以在 2.3 节配置里面,指明了语言,分词颗粒度也做了调整。同样在 hugo 生成的 index.html 里面,也要注意下语言问题,文件开头<html lang="zh">
标签。
3.增加 filter 功能,对现有搜索的增强,这里绕了点弯路,最终还是 Claude 3.7 帮忙解决。这里需要调整 layouts/_default/baseof.html。我增加了标签和文章类型过滤,标签是从 md 文件里面直接提取,如果想用分类也是类似的方法;第二个是文章类型,目前我有 blog 和 card 两个类型,一个是长篇一点,另一个是类似卡片笔记一样。
<!DOCTYPE html>
<html lang="{{ with .Site.LanguageCode }}{{ . }}{{ else }}en-US{{ end }}">
<head>
<meta http-equiv="X-Clacks-Overhead" content="GNU Terry Pratchett" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{{- partial "favicon.html" . -}}
<title>{{- block "title" . }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{- end }}</title>
<meta name="referrer" content="no-referrer-when-downgrade" />
{{ with .OutputFormats.Get "rss" -}}
{{ printf `<link rel="%s" type="%s" href="%s" title="%s" />` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }}
{{ end -}}
{{- partial "style.html" . -}}
<!-- A partial to be overwritten by the user.
Simply place a custom_head.html into
your local /layouts/partials-directory -->
{{- partial "custom_head.html" . -}}
</head>
<!-- 调整点1: data-pagefind-body -->
<body data-pagefind-body>
<!-- 调整点2:添加标签过滤器 -->
{{ with .Params.tags }}
{{ range . }}
<meta data-pagefind-filter="tag:{{ . }}" />
{{ end }}
{{ end }}
<!-- 调整点3:添加文章类型过滤器 -->
{{ if eq .Type "card" }}
<meta data-pagefind-filter="type:card" />
{{ else if eq .Type "blog" }}
<meta data-pagefind-filter="type:blog" />
{{ else }}
<meta data-pagefind-filter="type:blog" />
{{ end }}
<header>
{{- partial "header.html" . -}}
</header>
<main>
{{- block "main" . }}{{- end }}
</main>
<footer>
{{- partial "footer.html" . -}}
</footer>
<!-- A partial to be overwritten by the user.
Simply place a custom_body.html into
your local /layouts/partials-directory -->
{{- partial "custom_body.html" . -}}
</body>
</html>
五。总结
前前后后花了两天时间,中途绕了点弯路,不过结果很满意,所以在此记录下。