背景
Hexo 默认的模板 landscape 对搜索的支持并不友好(也许是我没有领会到精髓😟),一般人选择先做好 SEO,然后把站内搜索直接转发给本地搜索引擎(如Google、百度等)。
本网站严格意义上来讲,并不算是个博客,只是本人随手瞎记录一下平时遇到的问题以及感想,所以,完全没心思去做 SEO 优化,仅作为个人备忘录在用。但是偶尔会遇到自己有印象写过xx知识点,但是又不记得具体在哪里的时候。这时,就需要搜索了。
出发点交代完毕,正文开启:
实现思路
实现思路很简单:
安装 hexo-generator-search
详细请参考 hexo-generator-search
1
| npm install hexo-generator-search --save
|
配置 hexo-generator-search
一般情况下,同样内容的数据,json 文件要比 xml 小,所以,此处我选择 search.json
文件。
修改 landscape 模板的页面生成脚本,
打开 header.ejs
文件中,找到搜索相关代码,作相应修改。
我的 header.ejs
相关代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <div id="search-form-wrap"> <%- search_form_cn({button: ''}) %> <% if (config.search){ %> <input type="hidden" id="search-index-file" value="<%=config.root+config.search.path%>" /> <div id="search-form-datalist"> </div> <% } %> </div> <nav id="sub-nav"> <% if (theme.rss){ %> <a id="nav-rss-link" class="nav-icon" href="<%- url_for(theme.rss) %>" title="<%= __('rss_feed') %>"></a> <% } %> <a id="nav-search-btn" class="nav-icon" title="<%= __('search') %>"></a> </nav>
|
修改 script.js
脚本
更新逻辑有如下:
- 初始化 web worker,用于搜索实现(TODO:搜索排序);
- 加载
search.json
索引文件;
- 监听搜索关键词输入框按键事件(如
keyup
、blur
等),当用户输入变更时,搜索索引文件;并根据返回结果渲染页面;
- 响应键盘 ↑ ↓ 箭头按钮事件,高亮用户想要查看的内容;响应键盘确定按钮事件,跳转到对应的内容;
- 调整 UI,完善 UX。
script.js
部分源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
| const $searchWrap = document.getElementById("search-form-wrap"); let isSearchAnim = false; const searchAnimDuration = 200; const startSearchAnim = () => (isSearchAnim = true); const stopSearchAnim = callback => setTimeout(() => { isSearchAnim = false; callback && callback(); }, searchAnimDuration); const showSearchForm = () => { if (isSearchAnim) return; startSearchAnim(); $searchWrap.classList.add("on"); stopSearchAnim(() => document.querySelector(".search-form-input").focus() ); }; document .getElementById("nav-search-btn") .addEventListener("click", showSearchForm); const $searchInput = document.querySelector(".search-form-input"); const $searchDataList = document.getElementById("search-form-datalist"); let search_ww; $searchInput.addEventListener("keyup", evt => { const options = Array.from($searchDataList.children); let activeOptIdx = -1; options.some((_, i) => { if (_.classList.contains("active")) { activeOptIdx = i; } }); const keyCode = evt.key; if (keyCode === "Enter") { const theOpt = $searchDataList.querySelector(".active"); if (theOpt) { window.location.pathname = theOpt.dataset.url; } } else if (keyCode === "ArrowUp" || keyCode === "ArrowDown") { if (!(options && options.length && activeOptIdx !== -1)) { return; } if (keyCode === "ArrowUp") { activeOptIdx--; if (activeOptIdx < 0) { activeOptIdx = options.length - 1; } } else { activeOptIdx = (activeOptIdx + 1) % options.length; } options.forEach((_, i) => { if (activeOptIdx === i) { _.classList.add("active"); } else { _.classList.remove("active"); } }); $searchDataList.scrollTo({ top: options[activeOptIdx].offsetTop, left: 0, behavior: "smooth" }); } else { search_ww.postMessage({ action: "SEARCH", data: $searchInput.value }); } }); $searchInput.addEventListener("blur", () => { setTimeout(() => { startSearchAnim(); $searchWrap.classList.remove("on"); stopSearchAnim(); }, 128); }); if (window.Worker) { search_ww = new Worker("/js/search_ww.js"); search_ww.onmessage = e => { const matchedPosts = e.data; $searchDataList.innerHTML = ""; if (matchedPosts && matchedPosts.length) { matchedPosts.forEach((_, i) => { const $opt = document.createElement("p"); $opt.dataset.url = _.url; const $h = document.createElement("h5"); $h.innerHTML = `<a href="${_.url}">${_.title}</a>`; $opt.appendChild($h); const $body = document.createElement("div"); $body.innerText = _.content .trim() .replace(/\n/ig, " ") .substring(0, 256); $opt.appendChild($body); $searchDataList.appendChild($opt); if (i === 0) { $opt.classList.add("active"); } }); } else { $searchDataList.innerHTML = "<p>NO post(s) that matched with your input can be found, please try other keywords.</p>"; } }; setTimeout(() => { if (document.getElementById("search-index-file")) { const indexFilePath = document.getElementById("search-index-file") .value; search_ww.postMessage({ action: "INIT", data: indexFilePath }); } document.body.addEventListener("keyup", evt => { if (["E", "e"].includes(evt.key)) { showSearchForm(); } }); }, 1024); }
|
search-ww.js
源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| let searchStore = [];
onmessage = async e => { let { action, data } = e.data; if (typeof action === "string" && action.length) { action = action.toUpperCase().trim(); switch (action) { case "INIT": const response = await fetch(data); const content = await response.json(); searchStore = content || []; break; case "SEARCH": case "QUERY": default: let value = (data || "").toLowerCase(); if (value.length === 0) { return; } const matchedPosts = searchStore .filter( _ => _.title.toLowerCase().includes(value) || _.content.toLowerCase().includes(value) ) .slice(0, 10); postMessage(matchedPosts); break; } } else { console.log("please specify action when invoking search web worker!"); } };
|
具体可查看本博客源码(按 F12
快捷键即可)。
本文链接:
content_copy
https://zxs66.github.io/2020/12/16/search-within-hexo-site/