WithdewHua 1723 words 4 minutes to read

本文最后更新于2018-12-30,若有失效或者错误内容请留言或者通过其他方式联系我,尽可能及时更新。


前言

之前主题里面采用的是谷歌的站内搜索,感觉不是太好,所以想找找替代方案。Hugo 官方文档里面提供了几种方案,最后采用了Algolia,以下为折腾过程。

Algolia属于商业解决方案,但是提供了免费计划,对于我这种小站免费就够用了。

参考教程:

Index 配置过程

生成 Algolia 端索引

  • 前往Algolia注册,然后前往Algolia 主页,点击NEW APPLICATIONName可选,方案选择FREE,然后创建,随后的地区选择邻近地区即可;
    new-application.png

  • 选择地区后会跳转到Dashboard,此时点击侧栏的Indices,然后点击Create IndexIndex name自定义(例如自己的域名);
    algolia-index.png

  • 点击侧栏API Keys,记住以下的 keys,之后都会用到;
    api-keys.png

本地生成搜索索引

  1. 自定义输出格式,在config.toml中添加以下内容:

     [outputFormats.Algolia]
       baseName = "algolia"
       isPlainText = true
       mediaType = "application/json"
       notAlternative = true
    
     [params.algolia]
       vars = ["title", "summary", "date", "publishdate", "expirydate", "permalink"]
       params = ["categories", "tags","series"]
    

    vars是 Hugo 内置变量; params是自定义变量,按自己情况增减。

  2. 建立 JSON 模版,在layouts/_default中建立一个list.algolia.json文件,然后粘贴以下内容保存:

     {{/* Generates a valid Algolia search index */}}
     {{- $.Scratch.Add "index" slice -}}
     {{- $section := $.Site.GetPage "section" .Section }}
     {{- range .Site.AllPages -}}
       {{- if or (and (.IsDescendant $section) (and (not .Draft) (not .Params.private))) $section.IsHome -}}
         {{- $.Scratch.Add "index" (dict "objectID" .UniqueID "date" .Date.UTC.Unix "description" .Description "dir" .Dir "expirydate" .ExpiryDate.UTC.Unix "fuzzywordcount" .FuzzyWordCount "keywords" .Keywords "kind" .Kind "lang" .Lang "lastmod" .Lastmod.UTC.Unix "permalink" .Permalink "publishdate" .PublishDate "readingtime" .ReadingTime "relpermalink" .RelPermalink "summary" .Summary "title" .Title "type" .Type "url" .URL "weight" .Weight "wordcount" .WordCount "section" .Section "tags" .Params.Tags "categories" .Params.Categories "authors" .Params.Authors)}}
       {{- end -}}
     {{- end -}}
     {{- $.Scratch.Get "index" | jsonify -}}
    
  3. 生成索引,在config.toml中添加以下内容:

     [outputs]
     home = ["HTML", "RSS", "Algolia"]
    
  4. 使用hugo命令,即可在public文件夹下看到aligolia.json索引文件。

发送索引至 Algolia

我们将使用 NPM 包atomic-algolia来帮助我们更新Algolia上的 Index。

使用 NPM 需要安装 Node,可去官网下载

  • 在博客项目文件夹下,cmd或者bash里执行以下命令:
npm init  // 初始化,不懂具体内容一路回车就好
npm install atomic-algolia --save

执行完后会生成 node_modules 文件夹(如果代码托管在 GitHub 的话,可以在.gitignore中添加/node_modules以忽略该文件)。在项目根目录下还会有一个package.json文件,打开该文件,在scripts下添加"algolia": "atomic-algolia"后如下:

 "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "algolia": "atomic-algolia"
  },

一定要注意test一行后需要添加英文逗号。

  • 在博客项目根目录下建立一个.env文件,文件内容如下:
ALGOLIA_APP_ID={{ YOUR_APP_ID }}
ALGOLIA_ADMIN_KEY={{ YOUR_ADMIN_KEY }}
ALGOLIA_INDEX_NAME={{ YOUR_INDEX_NAME }}
ALGOLIA_INDEX_FILE={{ PATH/TO/algolia.json }}

注意

  1. 实际填写时不需要添加花括号;
  2. PATH/TO/algolia.json 默认应填写为public/algolia.json
  • 一切就绪后,使用以下命令即可更新Algolia的 Index:
  npm run algolia

没有报错的话就成功了。

前端内容

Index 弄好之后,就差我们在自己的主题中运用Algolia了。以下是我用的主题内容,可以根据自己情况进行修改。

  • 建立/search页面:
  hugo new search/_index.md

具体内容看情况修改;

  • layouts/partials/下建立search.html文件,内容如下:
  <script src="https://cdn.jsdelivr.net/npm/instantsearch.js@2.7.1"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.20.1/moment.min.js"></script>
  <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/instantsearch.js@2.7.1/dist/instantsearch.min.css">
  <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/instantsearch.js@2.7.1/dist/instantsearch-theme-algolia.min.css">


  <div id="search-box"><!-- SearchBox widget will appear here --></div>
  <!-- include algolia logo -->
  <img src="https://www.algolia.com/static_assets/images/pricing/pricing_new/algolia-powered-by-14773f38.svg" style="float:right"></img>

  <div id="hits" >
    <!-- Hits widget will appear here -->
  </div>

  <div id="pagination">
    <!-- Pagination widget will appear here -->
  </div>

  <script>
  // initialize instantsearch
  const search = instantsearch({
    appId: 'YOUR_APP_ID',
    apiKey: 'YOUR_SEARCH_ONLY_KEY',
    indexName: 'YOUR_INDEX_NAME',
    urlSync: true
  });

  const hitTemplate = function(hit) {
  /*  if (hit === null){
   *  return;
    }
  */
    let date = '';
    if (hit.date) {
    date = moment.unix(hit.date).format('MMM D, YYYY');
    }
    let url = `${hit.url}`;
    const title = hit._highlightResult.title.value;

    let breadcrumbs = '';
    if (hit._highlightResult.headings) {
    breadcrumbs = hit._highlightResult.headings.map(match => {
    return `<span class="post-breadcrumb">${match.value}</span>`
    }).join(' > ')
    }

    let content = "" ;
    if (hit._highlightResult.content){
    content = hit._highlightResult.content.value;
    }
    else{
    content = hit.summary;
    }


    return `
    <div class="post-item">
      <span class="post-meta">${date}</span>
        <h2><a class="post-link" href="${url}">${title}</a></h2>
        <a href="${url}" class="post-breadcrumbs">${breadcrumbs}</a>
      <div class="post-snippet">${content}</div>
    </div>
    `;
  }

  search.addWidget(
    instantsearch.widgets.searchBox({
      container: '#search-box',
      placeholder: 'Search'
    })
  );

  search.addWidget(
    instantsearch.widgets.hits({
      container: '#hits',
      templates: {
        item: hitTemplate
      }
    })
  );

  search.start();
  </script>

该文件内容原出自hugo-theme-cleanwhite,我只是稍加修改。<style>标签内容未放上来,可以自己根据需要修改。

  • 修改 search 页面,在layouts/search/下建立文件list.html,具体内容根据自己网站修改,最重要的就是引入search.html,以下是我的list.html内容:
  {{ partial "general-title" . }} {{ partial "header" . }} {{ partial "navbar" .
  }}
  <!-- Main -->
  <div id="main">
    <article class="post">
      <header>
        <div class="title">
          {{ if $.Scratch.Get "h1" }}
          <h1><a href="{{ .RelPermalink }}">{{ .Title }}</a></h1>
          {{ $.Scratch.Set "h1" false }} {{ else }}
          <h2><a href="{{ .RelPermalink }}">{{ .Title }}</a></h2>
          {{ end }} {{ with .Description }}
          <p>{{ . }}</p>
          {{ end }}
        </div>
      </header>
      <div id="content">{{ partial "search" . }}</div>
    </article>
  </div>
  {{ partial "sidebar" . }} {{ partial "footer" . }}
  • 在导航栏或其他位置添加搜索入口:
  <form id="search" role="search" action="/search/">
    <input type="text" name="q" placeholder="Search" />
  </form>

至此,就基本完成了,我的搜索界面效果如下:
search.png

结束语

本来准备用 Serverless Webtask Function来实现 Algolia Index 的自动化更新,但是建立 Webtasks profile 过程中验证码一直提示错误,暂时放弃了。