本站暂时还不支持搜索功能,我看很多网站都有这个搜索,所以也决定给自己搞个站内搜索,就不支持站外的百度或者谷歌了,毕竟我的站点没有收录,所以也搜不到什么东西,何必给自己找不愉快呢,言归正传,记录这个站内搜索。
如何实时获取内容呢?据说有两点,一是获取站点所有的页面内容,二是实时获取
我们本次是根据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} ${_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建立代理
后面有机会单独搞一篇文章 还可以使用第三方服务,不过因为在国内且比较麻烦,我懒的搞,也就没有记录。