侧边栏壁纸
博主昵称

HUGO支持站内搜索功能

本站暂时还不支持搜索功能,我看很多网站都有这个搜索,所以也决定给自己搞个站内搜索,就不支持站外的百度或者谷歌了,毕竟我的站点没有收录,所以也搜不到什么东西,何必给自己找不愉快呢,言归正传,记录这个站内搜索。

如何实时获取内容呢?据说有两点,一是获取站点所有的页面内容,二是实时获取

我们本次是根据hugo自身属性获取,把你希望被删选的内容保存在页面里,这样就能通过内容快速筛选,本身就是自己搜索自己,所以速度理论上是非常快的(其实我也不知道)

首先新建一个laouts/search/single.html,把搜索页面代码拷贝进来。

 1{{define "main" }}
 2<div class="container-search">
 3    <div id="data" style="display: none;">
 4        {{ range where .Site.Pages "Kind" "page" }}
 5            {{ if (and (ne .Section "snippets"))}}
 6                [{{- dict 
 7                        "title" (lower .Title) 
 8                        "permalink" .Permalink 
 9                        "date" (.Date | time.Format "2006-01-02")
10                        "summary" .Summary
11                        "content" (lower .Plain)
12                    | jsonify -}},]
13            {{ end }}
14        {{ end }}
15    </div>
16
17    <div id="search">
18        <!-- 🔎 -->
19        <span class="sc-icon"><img src="/imgs/icons/search.svg" width="48"> </span>
20        <span id="sc-clear" onclick="clearInputVal()"></span>
21        <input id="sc-input" oninput="search()" type="text" placeholder="here search search..." />
22        <div id="sc-res"></div>
23    </div>
24    <script src="/js/search01.js" defer></script>
25</div>
26{{end }}

然后再建一个用来承载页面的md文件content/search.md,拷贝如下内容

1---
2title: "查找"
3url: search
4Type: search
5date: 2023-01-01T08:00:00-07:00
6Description: "查找search页面"
7---

接着就是准备新建一个js文件js/search01.js,拷贝如下内容

  1let data = document.querySelector('#data').innerText.trim();
  2data = data.slice(0, data.length - 2) + ']';
  3data = data.replace(/\]\s+\[/g, '');
  4let map = JSON.parse(data);
  5// console.log(map);
  6
  7let scClear = document.querySelector('#sc-clear');
  8let scInput = document.querySelector('#sc-input');
  9let scRes = document.querySelector('#sc-res')
 10let scVal = '';
 11
 12// 自动聚集搜索框
 13scInput.focus();
 14scClear.style = 'opacity: 0;'
 15
 16
 17function search() {
 18    let post = '';
 19    scVal = scInput.value.trim().toLowerCase();
 20    // console.log(scVal);
 21    // if (scVal.length === 1) return;
 22    let scResPostsCounts = 0, // 搜索结果所在页面数
 23        scResScValCounts = 0; // 搜索词出现的总次数
 24
 25        
 26    map.forEach(item => {
 27        if (!scVal) return;
 28        if (item.content.indexOf(scVal) > -1 || item.title.indexOf(scVal) > -1) {
 29            let _arrIndex = scanStr(item.content, scVal);
 30            let strRes = '';
 31            let _radius = 100;  // 搜索字符前后截取的长度
 32            let _strStyle0 = '<span style="background: yellow;">'
 33            let _strStyle1 = '</span>'
 34            let _strSeparator = '<hr>'
 35            
 36            scResPostsCounts += 1;
 37            scResScValCounts += _arrIndex.length;
 38
 39            // 统计与首个与其前邻的索引(不妨称为基准索引)差值小于截取半径的索引位小于截取半径的索引的个数
 40            // 如果差值小于半径,则表示当前索引内容已包括在概要范围内,则不重复截取,且
 41            // 下次比较的索引应继续与基准索引比较,直到大于截取半径, _count重新置 为 0;
 42            let _count = 0;
 43
 44            for (let i = 0, len = _arrIndex.length; i < len; i++) {
 45                let _idxItem = _arrIndex[i];
 46                let _relidx = i;
 47
 48
 49                // 如果相邻搜索词出现的距离小于截取半径,那么忽略后一个出现位置的内容截取(因为已经包含在内了)
 50                if (_relidx > 0 && (_arrIndex[_relidx] - _arrIndex[_relidx - 1 - _count] < _radius)) {
 51                    _count += 1;
 52                    continue;
 53                }
 54                _count = 0;
 55
 56                // 概要显示
 57                // _startIdx, _endIdx 会在超限时自动归限(默认,无需处理)
 58                strRes += _strSeparator;
 59                let _startIdx = _idxItem - _radius + (_relidx + 1) * _strSeparator.length;
 60                let _endIdx = _idxItem + _radius + (_relidx + 1) * _strSeparator.length;
 61                strRes +=  item.content.substring(_startIdx, _endIdx);
 62            }
 63
 64            // 进一步对搜索摘要进行处理,高亮搜索词
 65            let _arrStrRes = scanStr(strRes, scVal);
 66            // console.log(_arrStrRes)
 67            for (let i = 0, len = _arrStrRes.length; i < len; i++) {
 68                let _idxItem = _arrStrRes[i];
 69                let _realidx = i;
 70
 71                strRes =
 72                    strRes.slice(0, (_idxItem + _realidx * (_strStyle0.length + _strStyle1.length))) + // 当前索引位置之前的部分
 73                    _strStyle0 + scVal + _strStyle1 +
 74                    strRes.slice(_idxItem + scVal.length + _realidx * (_strStyle0.length + _strStyle1.length)); // 之后的部分
 75            }
 76
 77            post += `
 78                <div class="item" >
 79                    <a href="${item.permalink}">
 80                        <span>📄</span>
 81                        <span class="date">${item.date}</span>
 82                        <span>${item.title}</span>
 83                    </a>
 84                    <div class="summary">${strRes}</div>
 85                </div>
 86            `
 87            // <div>${item.summary}</div>
 88        }
 89    })
 90
 91    let _total = '条目',
 92        _times = '次数';
 93    let res = `
 94        <div class="statistics">${_total}${scResPostsCounts} &nbsp;&nbsp;${_times}${scResScValCounts}</div>
 95        <div class="list  'list-single-col' : ''}">
 96            ${post}
 97        </div>
 98    `;
 99    scRes.innerHTML = res;
100
101    // Hmm... 强迫症,为 0 的时候,不想统计条目显示
102    if (scResPostsCounts == 0) {
103        document.querySelector('.statistics').style = 'opacity: 0;'
104    } 
105    // 同样无值时不显示清空符号
106    scClear.style = scVal ? 'opacity: 1' : 'opacity: 0';
107}
108
109// search()
110function scanStr(content, str) {
111    let index = content.indexOf(str);   // str 出现的位置
112    let num = 0;                        // str 出现的次数
113    let arrIndex = [];                  // str 出现的位置集合
114
115    while(index !== -1) {
116        // console.log(index);
117        arrIndex.push(index);
118        num += 1;
119        index = content.indexOf(str, index + 1); // 从 str 出现的位置下一位置继续
120    }
121
122    // console.log(arrIndex);
123    return arrIndex;
124
125}
126
127
128// 在字符串指定位置插入新的字符串
129function insertStr(str, start, newStr){   
130    return str.slice(0, start) + newStr + str.slice(start);
131}
132
133// 清空搜索框
134function clearInputVal() {
135    if (!scInput.value) return;
136    scInput.value = '';    
137    search();
138}

这样你就可以通过 site.com/search/ 访问到查询页面,试试看怎么样?

如果不满意我们还有第二种方案,通过 hugo lunr 把文章内容输出到json文件,然后来查找json文件, 我是在看云上看到的,这种方式对我的博客小站已经够用了

首先在hugo.toml设置设置output format

1[outputs]
2     section = [ "HTML", "JSON"]

然后设置输出的json模版layouts/post/list.json,设置要筛选的文章类型,填写需要输出的内容

 1[
 2{{ range $index, $value := where .Site.Pages "Type" "posts" }}
 3{{ if $index }}, {{ end }}
 4{
 5    "url": "{{ .RelPermalink }}",
 6    "title": "{{ .Title }}",
 7    "content": {{ .Content | plainify | jsonify }}
 8}
 9{{ end }}
10]

这时可以试试hugo命令,可以看到已经生成了index.json

接着就是需要使用js筛选,新建layouts/partials/search/internalsearch.html,填写如下信息

  1<script src="https://unpkg.com/lunr/lunr.js"></script>
  2 <script type="text/javascript">
  3
  4 // define globale variables
  5 var idx, searchInput, searchResults = null
  6 var documents = []
  7
  8 function renderSearchResults(results){
  9
 10    if (results.length > 0) {
 11
 12       // show max 10 results
 13       if (results.length > 9){
 14             results = results.slice(0,10)
 15       }
 16
 17       // reset search results
 18       searchResults.innerHTML = ''
 19
 20       // append results
 21       results.forEach(result => {
 22       
 23             // create result item
 24             var article = document.createElement('article')
 25             article.innerHTML = `
 26             <a href="${result.ref}"><h3 class="title">${documents[result.ref].title}</h3></a>
 27             <p><a href="${result.ref}">${documents[result.ref].title}</a></p>
 28             <br>
 29             `
 30             searchResults.appendChild(article)
 31       })
 32
 33    // if results are empty
 34    } else {
 35       searchResults.innerHTML = '<p>No results found.</p>'
 36    }
 37 }
 38
 39 function registerSearchHandler() {
 40
 41    // register on input event
 42    searchInput.oninput = function(event) {
 43
 44       // remove search results if the user empties the search input field
 45       if (searchInput.value == '') {
 46             
 47             searchResults.innerHTML = ''
 48       } else {
 49             
 50             // get input value
 51             var query = event.target.value
 52
 53             // run fuzzy search
 54             var results = idx.search(query + '*')
 55
 56             // render results
 57             renderSearchResults(results)
 58       }
 59    }
 60
 61    // set focus on search input and remove loading placeholder
 62    searchInput.focus()
 63    searchInput.placeholder = ''
 64 }
 65
 66 window.onload = function() {
 67
 68    // get dom elements
 69    searchInput = document.getElementById('search-input')
 70    searchResults = document.getElementById('search-results')
 71
 72    // request and index documents
 73    fetch('/posts/index.json', {
 74       method: 'get'
 75    }).then(
 76       res => res.json()
 77    ).then(
 78       res => {
 79
 80             // index document
 81             idx = lunr(function() {
 82                this.ref('url')
 83                this.field('title')
 84                this.field('content')
 85
 86                res.forEach(function(doc) {
 87                   this.add(doc)
 88                   documents[doc.url] = {
 89                         'title': doc.title,
 90                         'content': doc.content,
 91                   }
 92                }, this)
 93             })
 94
 95             // data is loaded, next register handler
 96             registerSearchHandler()
 97       }
 98    ).catch(
 99       err => {
100             searchResults.innerHTML = `<p>${err}</p>`
101       }
102    )
103 }
104 </script>
105
106 <input id="search-input" type="text" placeholder="Loading..." name="search">
107
108 <section id="search-results" class="search"></section>

接着在适当的位置启用search,就可以进行查询了,不过当前默认的查询结果只展示对应的标题,如果需要更多的内容需要自己设置

如果觉得不合适,自己的服务器不错的话,可以把关键信息存储到MySQL,然后建立查询服务,用nginx建立代理

后面有机会单独搞一篇文章 还可以使用第三方服务,不过因为在国内且比较麻烦,我懒的搞,也就没有记录。

博主栏壁纸
博主头像 爱喝酸奶的我

一个随缘记录学习生活并取悦自己的博客小站

31 文章数
10 评论量
标签云