起因

我的博客使用了 hugo 作为静态生成工具,自带的主题里也没有附带搜索功能。看来,还是得自己给博客添加一个搜索功能。

经过多方查找,从 Hugo Fast Search · GitHub 找到一片详细、可用的教程(虽然后面魔改了一些)。

实际案例

步骤

  1. 在 config.toml 文件做好相关配置;
  2. 添加导出 JSON 格式文件的脚本,即在 layouts/_default 目录下添加 index.json 文件;
  3. 增加依赖的 JS 脚本,包含自己的 search.js 和 fuse.js 文件;
  4. 添加相关 HTML 代码;
  5. 添加相关 CSS 样式。

配置

[params]
# 是否开启本地搜索
fastSearch = true [outputs]
# 增加 JSON 配置
home = ["HTML", "RSS", "JSON"]

添加 index.json 文件

{{- $.Scratch.Add "index" slice -}}
{{- range .Site.RegularPages -}}
{{- $.Scratch.Add "index" (dict "title" .Title "permalink" .Permalink "content" .Plain) -}}
{{- end -}}
{{- $.Scratch.Get "index" | jsonify -}}

添加依赖

首先,可以先添加 fuse.js 依赖,它是一个功能强大的轻量级模糊搜索库,可以到 官网 访问更多信息:

{{- if .Site.Params.fastSearch -}}
<script src="https://cdn.jsdelivr.net/npm/fuse.js@6.4.6"></script>
{{- end -}}

然后,就是添加自定义的 search.js 文件以实现搜索功能,文件放置在 assets/js 目录下。

这里的代码和 Gist 上的有些许不同,经过了自己的魔改。

var fuse; // holds our search engine
var searchVisible = false;
var firstRun = true; // allow us to delay loading json data unless search activated
var list = document.getElementById('searchResults'); // targets the <ul>
var first = list.firstChild; // first child of search list
var last = list.lastChild; // last child of search list
var maininput = document.getElementById('searchInput'); // input box for search
var resultsAvailable = false; // Did we get any search results? // ==========================================
// The main keyboard event listener running the show
//
document.addEventListener("click", event => {
var cDom = document.getElementById("fastSearch");
var sDom = document.getElementById('search-click');
var tDom = event.target;
if (sDom == tDom || sDom.contains(tDom)) {
showSearchInput();
} else if (cDom == tDom || cDom.contains(tDom)) {
// ...
} else if (searchVisible) {
cDom.style.display = "none"
searchVisible = false;
}
}); document.addEventListener('keydown', function(event) { // CMD-/ to show / hide Search
if (event.metaKey && event.which === 191) {
showSearchInput()
} // Allow ESC (27) to close search box
if (event.keyCode == 27) {
if (searchVisible) {
document.getElementById("fastSearch").style.display = "none";
document.activeElement.blur();
searchVisible = false;
}
} // DOWN (40) arrow
if (event.keyCode == 40) {
if (searchVisible && resultsAvailable) {
event.preventDefault(); // stop window from scrolling
if ( document.activeElement == maininput) { first.focus(); } // if the currently focused element is the main input --> focus the first <li>
else if ( document.activeElement == last ) { last.focus(); } // if we're at the bottom, stay there
else { document.activeElement.parentElement.nextSibling.firstElementChild.focus(); } // otherwise select the next search result
}
} // UP (38) arrow
if (event.keyCode == 38) {
if (searchVisible && resultsAvailable) {
event.preventDefault(); // stop window from scrolling
if ( document.activeElement == maininput) { maininput.focus(); } // If we're in the input box, do nothing
else if ( document.activeElement == first) { maininput.focus(); } // If we're at the first item, go to input box
else { document.activeElement.parentElement.previousSibling.firstElementChild.focus(); } // Otherwise, select the search result above the current active one
}
}
}); // ==========================================
// execute search as each character is typed
//
document.getElementById("searchInput").onkeyup = function(e) {
executeSearch(this.value);
} function showSearchInput() {
// Load json search index if first time invoking search
// Means we don't load json unless searches are going to happen; keep user payload small unless needed
if(firstRun) {
loadSearch(); // loads our json data and builds fuse.js search index
firstRun = false; // let's never do this again
} // Toggle visibility of search box
if (!searchVisible) {
document.getElementById("fastSearch").style.display = "block"; // show search box
document.getElementById("searchInput").focus(); // put focus in input box so you can just start typing
searchVisible = true; // search visible
}
else {
document.getElementById("fastSearch").style.display = "none"; // hide search box
document.activeElement.blur(); // remove focus from search box
searchVisible = false; // search not visible
}
} // ==========================================
// fetch some json without jquery
//
function fetchJSONFile(path, callback) {
var httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function() {
if (httpRequest.readyState === 4) {
if (httpRequest.status === 200) {
var data = JSON.parse(httpRequest.responseText);
if (callback) callback(data);
}
}
};
httpRequest.open('GET', path);
httpRequest.send();
} // ==========================================
// load our search index, only executed once
// on first call of search box (CMD-/)
//
function loadSearch() {
fetchJSONFile('/index.json', function(data){ var options = { // fuse.js options; check fuse.js website for details
includeMatches: true,
shouldSort: true,
ignoreLocation: true,
keys: [
{
name: 'title',
weight: 1,
},
{
name: 'content',
weight: 0.6,
},
],
};
fuse = new Fuse(data, options); // build the index from the json file
});
} // ==========================================
// using the index we loaded on CMD-/, run
// a search query (for "term") every time a letter is typed
// in the search box
//
function executeSearch(term) {
if (term.length == 0) {
document.getElementById("searchResults").setAttribute("style", "");
return;
}
let results = fuse.search(term); // the actual query being run using fuse.js
let searchItems = ''; // our results bucket if (results.length === 0) { // no results based on what was typed into the input box
resultsAvailable = false;
searchItems = '<li class="noSearchResult">无结果</li>';
} else { // build our html
permalinkList = []
searchItemCount = 0
for (let item in results) {
if (permalinkList.includes(results[item].item.permalink)) {
continue;
}
// 去重
permalinkList.push(results[item].item.permalink);
searchItemCount += 1; title = results[item].item.title;
content = results[item].item.content.slice(0, 50);
for (const match of results[item].matches) {
if (match.key == 'title') {
startIndex = match.indices[0][0];
endIndex = match.indices[0][1] + 1;
highText = '<span class="search-highlight">' + match.value.slice(startIndex, endIndex) + '</span>';
title = match.value.slice(0, startIndex) + highText + match.value.slice(endIndex);
} else if (match.key == 'content') {
startIndex = match.indices[0][0];
endIndex = match.indices[0][1] + 1;
highText = '<span class="search-highlight">' + match.value.slice(startIndex, endIndex) + '</span>';
content = match.value.slice(Math.max(0, startIndex - 30), startIndex) + highText + match.value.slice(endIndex, endIndex + 30);
}
}
searchItems = searchItems + '<li><a href="' + results[item].item.permalink + '">' + '<span class="title">' + title + '</span><br /> <span class="sc">'+ content +'</span></a></li>';
// only show first 5 results
if (searchItemCount >= 5) {
break;
}
}
resultsAvailable = true;
} document.getElementById("searchResults").setAttribute("style", "display: block;");
document.getElementById("searchResults").innerHTML = searchItems;
if (results.length > 0) {
first = list.firstChild.firstElementChild; // first result container — used for checking against keyboard up/down location
last = list.lastChild.firstElementChild; // last result container — used for checking against keyboard up/down location
}
}

最后,需要将 search.js 依赖引入,如下是引入的代码:

{{ $search := resources.Get "js/search.js" | minify | fingerprint }}
<script type="text/javascript" src="{{ $search.RelPermalink }}"></script>

添加 HTML 代码

HTML 页面的代码分为两个部分:搜索的按钮、搜索框和结果展示。

我这里将搜索的按钮放到的菜单栏,主要是一个可点击的按钮:

{{ if .Site.Params.fastSearch -}}
<li id="search-click" class="menu-item">
<a class="menu-item-link" href="javascript:void(0)">搜索</a>
</li>
{{- end }}

对于搜索框,我选择的是弹出式的窗口,这里比较重要的是标签的 ID 需要和 search.js 脚本一致:

{{ if .Site.Params.fastSearch -}}
<div id="fastSearch">
<input id="searchInput">
<ul id="searchResults"></ul>
</div>
{{- end }}

添加 CSS 样式

页面样式这部分,主要是看个人的喜好,这里只放出自己的样式:

#fastSearch {
display: none;
position: fixed;
left: 50%;
top: calc(5vw + 40px);
transform: translateX(-50%);
z-index: 4;
width: 650px;
background-color: #fff;
box-shadow: 0 1px 2px #3c40434d, 0 2px 6px 2px #3c404326;
border-radius: 4px;
overflow: hidden; input {
padding: 10px;
width: 100%;
height: 30px;
font-size: 18px;
line-height: 30px;
border: none;
outline: none;
font-family: inherit;
} #searchResults {
display: none;
overflow-y: auto;
max-height: 60vh;
padding-left: 0;
margin: 0;
border-top: 1px dashed #ddd; .search-highlight {
color: red;
} li {
list-style: none;
margin: 0; a {
text-decoration: none;
color: inherit;
padding: 6px 10px;
display: block;
font-size: 14px;
letter-spacing: .04em;
} a:hover,
a:focus {
filter: brightness(93%);
outline: 0;
background-color: rgb(240, 240, 240);
} .title {
font-weight: 600;
}
} li.noSearchResult {
text-align: center;
margin: 8px 0;
color: #888;
}
}
}

样例展示

总结

经过两天时间的奋斗,终于是将搜索功能给上线了。

不得不说,理想总是一开始美好,最初以为是一个完整、可用的教程,却没想到复制到代码之后就不可用了,最终是经过自己的魔改才得以使用。

总结一下就是,没有实践就没有话语权,千万不要做管中窥豹的那个人。

给 hugo 博客添加搜索功能的更多相关文章

  1. 给jekyll博客添加搜索功能

    使用SWIFTYPE为jekyll博客添加搜索引擎 步骤 1.首先去swiftype注册一个账号 2.接着添加自己想要配置的网站地址并为新设定的引擎添加一个名字(非会员只能设置一个引擎). 3.收到验 ...

  2. hexo next主题为博客添加分享功能

    title: hexo next主题为博客添加分享功能 date: 2018-01-06 20:20:02 tags: [hexo博客, 博客配置] categories: hexo next主题配置 ...

  3. 为CSDN博客添加打赏功能

    随着移动支付在国内的兴起,越来越多的付费内容越多如雨后春笋般的冒了出来.其中以<逻辑思维>.罗振宇.李笑来为主要代表作品和人物. 现在很多博客或者个人网站里面都有打赏功能,这算是对博主的劳 ...

  4. 【hugo】- hugo 博客 添加鼠标单击特效

    hugo 博客 监听鼠标点击事件,添加动画效果 js下载 链接:https://pan.baidu.com/s/1SZu76WdEXRxLCfqJ2lbbtQ 密码:r056 移入hugo博客中 打开 ...

  5. hexo+next博客添加搜索

    1.为什么添加algolia搜索 第一当然是可以方便的查找所需文章,第二点就是之前常用的swiftype插件不再免费.我的个人博客是这个月初搭建完成的,这时候swiftype已经不再免费,而且只开放企 ...

  6. 使用Hugo框架搭建博客的过程 - 功能拓展

    前言 本文介绍一些拓展功能,如文章页面功能增加二级菜单,相关文章推荐和赞赏.另外,使用脚本会大大简化写作后的上传流程. 文章页面功能 这部分功能的拓展主要是用前端的JS和CSS,如果对前端不了解,可以 ...

  7. 【干货】2个小时教你hexo博客添加评论、打赏、RSS等功能 (转)

    备注:该教程基于Hexo 2.x版本,目前Hexo是3.x版本,照本教程实现有可能会出现404错误,笔者目前还未找时间去解决,待笔者找时间解决该问题后,再写一篇该问题的解决教程,给各位读者带来困扰,还 ...

  8. hexo博客添加功能

    设置Hexo主题模式 Hexo主题中,有三种不同的模式,通过切换模式,让NexT主题显示不一样的样式.在NexT根目录下有一个同样名称为_config.yml,为了区分hexo根目录下的_config ...

  9. 如何利用腾讯云COS为静态博客添加动态相册

    前言 本文首发于个人网站Jianger's Blog,欢迎访问订阅.个人博客小站刚建站不久,想着除了主题里的功能外再添加上相册模块,于是半搜索半摸索把相册模块搞出来了,最后采用了利用腾讯云对象存储作图 ...

随机推荐

  1. 金灿灿的季节 - Apache DolphinScheduler收获5位新Committer

    在这个金灿灿的收获季节,经过 Apache DolphinScheduler PPMC 们的推荐和投票,Apache DolphinScheduler 收获了 5 位新Committer .他们是:n ...

  2. Luogu2290 [HNOI2004]树的计数 (组合计数,prufer编码)

    这不prufer编码吗,防爆long long就行了啊 #include <iostream> #include <cstdio> #include <cstring&g ...

  3. CSO视角:Sigstore如何保障软件供应链安全?

    本文作者 Chris Hughes,Aquia的联合创始人及CISO,拥有近20年的网络安全经验. SolarWinds 和 Log4j 等影响广泛的软件供应链攻击事件引起了业界对软件供应链安全的关注 ...

  4. python包合集-shutil

    一.简介 shutil是 python 中的高级文件操作模块,与os模块形成互补的关系,os主要提供了文件或文件夹的新建.删除.查看等方法,还提供了对文件以及目录的路径操作.shutil模块提供了移动 ...

  5. 【JDBC】学习路径4-分页查询

    第一章:什么是分页查询呢? 简而言之,分页数 就是百度搜索引擎中的网页的页数. 分页查询,就是从数据库中提取一部分出来,给用户. 用处:减少服务器负担. 为了方便测试,我们先给数据库添加大量信息. 还 ...

  6. 配置IConfiguration

    前言 配置是我们必不可少的功能,我们在开发中,经常会遇到需要获取配置信息的需求,那么如何才能优雅的获取配置信息? 我们希望新的配置: 支持强类型 配置变更后通知 学习难度低 快速入门 根据使用场景我们 ...

  7. 第五十一篇:webpack中的loader(二) --less-loader

    好家伙 先扩充一下知识点: 什么是.less文件? 作为一名前端开发的同学,很多时候我们都无法避免地要去写大量的 CSS 代码, 而且耗费的时间还不少,所以学习一种能够提升开发效率的 CSS 预处理器 ...

  8. Linux KVM创建虚拟机

    原博文(https://www.cnblogs.com/supermao12/p/16382767.html) 开启虚拟化 KVM需要64位操作系统 [root@localhost 4.18.0-19 ...

  9. 性能调优——小小的log大大的坑

    引言 "只有被线上服务问题毒打过的人才明白日志有多重要!" 我先说结论,谁赞成,谁反对?如果你深有同感,那恭喜你是个社会人了:) 日志对程序的重要性不言而喻,轻巧.简单.无需费脑, ...

  10. 用bash反弹shell

    用bash反弹shell 受害主机:linux系统 攻击机:需要安装netcat(nc) 受害主机执行:ifconfig  ## 查看受害主机ip 攻击机执行:nc -lvp 19999 ## 在攻击 ...