本文也算是一篇教程,可以给 hugo 网站加个搜索功能,并且实现热更新,体验感更好。

如果是其他程序,只需要按照特定的模板生成以下格式的文件即可,主要代码从 第 2 部分 开始,第一章节写的是如何用 hugo 输出文章列表 json 文件。

title 是文章标题,permalink 是文章链接。

[{
  "permalink": "",
  "title": ""
}, {
  "permalink": "",
  "title": ""
}]

1. hugo 模板生成文章列表 json 文件

layouts 文件夹下新建 index.json 文件,模板内容如下:

其中第 2 行最后面的 "blog" 是你文章文件夹的名称。大部分是 posts 等等,这里是我个人的名称。

{{- $.Scratch.Set "posts" slice -}}
{{- range where .Site.RegularPages "Type" "blog" -}}
    {{- $.Scratch.Add "posts" (dict "title" .Title "permalink" .Permalink) -}}
{{- end -}}
{{- $.Scratch.Get "posts" | jsonify -}}

按照这个模板,hugo 本地预览可以打开 http://localhost:1313/index.json 查看,如果输出了一些数据如下图,说明你成功了。

image

2. js 代码

/layouts/_default 新建一个模板文件 search.html,大致的结构参考其他模板文件,然后写入我们需要的内容。

首先是一个简单的 html 结构,给 input 绑定一个事件。

<form class="search"> 
  <input type="text" id="searchTerm" name="searchTerm" autocomplete="off" oninput="initiateSearch()">
</form>
<div id="resultsContainer">请输入关键词进行搜索...</div>

然后通过一个 get 请求获取 json 文件,传入关键词参数,生成搜索列表。

<script>
  function search(jsonData, searchTerm) {
    let results = [];
    for (let i = 0; i < jsonData.length; i++) {
      for (let property in jsonData[i]) {
        if (jsonData[i].hasOwnProperty(property) && jsonData[i][property].toString().indexOf(searchTerm) > -1) {
          results.push(jsonData[i]);
          break;
        }
      }
    }
    return results;
  }

  function displayResults(searchResults) {
    let container = document.getElementById("resultsContainer");
    container.innerHTML = "";
    if (searchResults.length > 0) {
      for (let i = 0; i < searchResults.length; i++) {
        let resultDiv = document.createElement("div");
        let resultTitle = document.createElement("a");
        resultTitle.innerText = searchResults[i].title;
        resultTitle.setAttribute('href', searchResults[i].permalink)
        resultDiv.appendChild(resultTitle);
        container.appendChild(resultDiv);
      }
    } else {
      let noResultsMessage = document.createElement("p");
      noResultsMessage.innerText = "没有找到搜索结果。";
      container.appendChild(noResultsMessage);
    }
  }

  function initiateSearch() {
    let searchTerm = document.getElementById("searchTerm").value;
    let xhr = new XMLHttpRequest();
    xhr.open('GET', '/index.json', true);
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4 && xhr.status === 200) {
        let jsonData = JSON.parse(xhr.responseText);
        let searchResults = search(jsonData, searchTerm);
        displayResults(searchResults);
      }
    };
    xhr.send();
  }
</script>

然后再在 /content 新建一个 search.md 文件调用该模板即可。

---
slug: search
title: 搜索
layout: search
---

写了一个基础的样式,可以直接使用。

.search {
  width: 100%;
  display: flex;
  align-items: center;
  height: 36px;
}
.search #searchTerm {
  width: 100%;
  height: 100%;
  outline: none;
  border: none;
  padding: 0 15px;
  box-shadow: 1px 2px 10px rgba(0, 0, 0, 0.1);
}

#resultsContainer {
  margin-top: 20px;
}
#resultsContainer div {
  margin-bottom: 10px;
  margin: 0;
}
#resultsContainer div a {
  display: block;
  width: 100%;
  padding: 6px 10px;
  transition: all 0.1s linear;
  border-radius: 4px;
}
#resultsContainer div a:hover {
  background: #f3f3f3;
}