|
| 1 | +let SearchService = (() => { |
| 2 | + fn = {}; |
| 3 | + fn.queryText = null; |
| 4 | + fn.data = null; |
| 5 | + fn.template = `<div id="u-search"> |
| 6 | + <div class="modal"> |
| 7 | + <header class="modal-header" class="clearfix"> |
| 8 | + <form id="u-search-modal-form" class="u-search-form" name="uSearchModalForm"> |
| 9 | + <input type="text" id="u-search-modal-input" class="u-search-input" /> |
| 10 | + <button type="submit" id="u-search-modal-btn-submit" class="u-search-btn-submit"> |
| 11 | + <span class="fas fa-search"></span> |
| 12 | + </button> |
| 13 | + </form> |
| 14 | + <a id="u-search-btn-close" class="btn-close"> <span class="fas fa-times"></span> </a> |
| 15 | + </header> |
| 16 | + <main class="modal-body"> |
| 17 | + <ul class="modal-results"></ul> |
| 18 | + </main> |
| 19 | + </div> |
| 20 | + <div id="modal-overlay" class="modal-overlay"></div> |
| 21 | +</div> |
| 22 | +`; |
| 23 | + fn.init = () => { |
| 24 | + let div = document.createElement("div"); |
| 25 | + div.innerHTML += fn.template; |
| 26 | + document.body.append(div); |
| 27 | + document.querySelectorAll(".u-search-form").forEach((e) => { |
| 28 | + e.addEventListener("submit", fn.onSubmit, false); |
| 29 | + }); |
| 30 | + let uSearchModalInput = document.querySelector("#u-search-modal-input"); |
| 31 | + uSearchModalInput.addEventListener("input", fn.onSubmit); |
| 32 | + document |
| 33 | + .querySelector("#u-search-btn-close") |
| 34 | + .addEventListener("click", fn.close, false); |
| 35 | + document |
| 36 | + .querySelector("#modal-overlay") |
| 37 | + .addEventListener("click", fn.close, false); |
| 38 | + }; |
| 39 | + fn.onSubmit = (event) => { |
| 40 | + event.preventDefault(); |
| 41 | + let input = event.target.querySelector(".u-search-input"); |
| 42 | + if (input) { |
| 43 | + fn.queryText = input.value; |
| 44 | + } else { |
| 45 | + fn.queryText = event.target.value; |
| 46 | + } |
| 47 | + |
| 48 | + if (fn.queryText) { |
| 49 | + fn.search(); |
| 50 | + } |
| 51 | + }; |
| 52 | + fn.search = async () => { |
| 53 | + document.querySelectorAll(".u-search-input").forEach((e) => { |
| 54 | + e.value = fn.queryText; |
| 55 | + }); |
| 56 | + document.querySelector("#u-search").style.display = "block"; |
| 57 | + if (!fn.data) { |
| 58 | + fn.data = await fn.fetchData(); |
| 59 | + } |
| 60 | + let results = ""; |
| 61 | + results += fn.buildResultList(data.pages); |
| 62 | + results += fn.buildResultList(data.posts); |
| 63 | + document.querySelector("#u-search .modal-results").innerHTML = results; |
| 64 | + window.pjax && pjax.refresh(document.querySelector("#u-search")); |
| 65 | + document.addEventListener("keydown", function f(event) { |
| 66 | + if (event.code === "Escape") { |
| 67 | + fn.close(); |
| 68 | + document.removeEventListener("keydown", f); |
| 69 | + } |
| 70 | + }); |
| 71 | + }; |
| 72 | + fn.close = () => { |
| 73 | + document.querySelector("#u-search").style.display = "none"; |
| 74 | + }; |
| 75 | + fn.fetchData = () => { |
| 76 | + return fetch(SearchServiceDataPath) |
| 77 | + .then((response) => response.text()) |
| 78 | + .then((res) => { |
| 79 | + data = JSON.parse(res); |
| 80 | + // console.log(data); |
| 81 | + return data; |
| 82 | + }); |
| 83 | + }; |
| 84 | + fn.buildResultList = (data) => { |
| 85 | + let html = ""; |
| 86 | + data.forEach((post) => { |
| 87 | + if (post.text) { |
| 88 | + post.text = post.text.replace(/12345\d*/g, "") // 简易移除代码行号 |
| 89 | + } |
| 90 | + if (fn.contentSearch(post)) { |
| 91 | + html += fn.buildResult(post.permalink, post.title, post.digest); |
| 92 | + } |
| 93 | + }); |
| 94 | + return html; |
| 95 | + }; |
| 96 | + fn.contentSearch = (post) => { |
| 97 | + let post_title = post.title.trim().toLowerCase(); |
| 98 | + let post_content = post.text.trim().toLowerCase(); |
| 99 | + let keywords = fn.queryText |
| 100 | + .trim() |
| 101 | + .toLowerCase() |
| 102 | + .split(/[-\s]+/); |
| 103 | + let foundMatch = false; |
| 104 | + let index_title = -1; |
| 105 | + let index_content = -1; |
| 106 | + let first_occur = -1; |
| 107 | + if (post_title && post_content) { |
| 108 | + keywords.forEach((word, index) => { |
| 109 | + index_title = post_title.indexOf(word); |
| 110 | + index_content = post_content.indexOf(word); |
| 111 | + if (index_title < 0 && index_content < 0) { |
| 112 | + foundMatch = false; |
| 113 | + } else { |
| 114 | + foundMatch = true; |
| 115 | + if (index_content < 0) { |
| 116 | + index_content = 0; |
| 117 | + } |
| 118 | + if (index === 0) { |
| 119 | + first_occur = index_content; |
| 120 | + } |
| 121 | + } |
| 122 | + if (foundMatch) { |
| 123 | + post_content = post.text.trim(); |
| 124 | + let start = 0; |
| 125 | + let end = 0; |
| 126 | + if (first_occur >= 0) { |
| 127 | + start = Math.max(first_occur - 40, 0); |
| 128 | + end = |
| 129 | + start === 0 |
| 130 | + ? Math.min(200, post_content.length) |
| 131 | + : Math.min(first_occur + 120, post_content.length); |
| 132 | + let match_content = post_content.substring(start, end); |
| 133 | + keywords.forEach(function (keyword) { |
| 134 | + let regS = new RegExp(keyword, "gi"); |
| 135 | + match_content = match_content.replace( |
| 136 | + regS, |
| 137 | + "<b mark>" + keyword + "</b>" |
| 138 | + ); |
| 139 | + }); |
| 140 | + post.digest = match_content + "......"; |
| 141 | + } else { |
| 142 | + end = Math.min(200, post_content.length); |
| 143 | + post.digest = post_content.trim().substring(0, end); |
| 144 | + } |
| 145 | + } |
| 146 | + }); |
| 147 | + } |
| 148 | + return foundMatch; |
| 149 | + }; |
| 150 | + fn.buildResult = (url, title, digest) => { |
| 151 | + let result = fn.getUrlRelativePath(url); |
| 152 | + let html = ""; |
| 153 | + html += "<li>"; |
| 154 | + html += |
| 155 | + "<a class='result' href='" + result + "?keyword=" + fn.queryText + "'>"; |
| 156 | + html += "<span class='title'>" + title + "</span>"; |
| 157 | + if (digest !== "") html += "<span class='digest'>" + digest + "</span>"; |
| 158 | + html += "</a>"; |
| 159 | + html += "</li>"; |
| 160 | + return html; |
| 161 | + }; |
| 162 | + fn.getUrlRelativePath = function (url) { |
| 163 | + let arrUrl = url.split("//"); |
| 164 | + let start = arrUrl[1].indexOf("/"); |
| 165 | + let relUrl = arrUrl[1].substring(start); |
| 166 | + if (relUrl.indexOf("?") != -1) { |
| 167 | + relUrl = relUrl.split("?")[0]; |
| 168 | + } |
| 169 | + return relUrl; |
| 170 | + }; |
| 171 | + return { |
| 172 | + init: () => { |
| 173 | + fn.init(); |
| 174 | + }, |
| 175 | + setQueryText: (queryText) => { |
| 176 | + fn.queryText = queryText; |
| 177 | + }, |
| 178 | + search: () => { |
| 179 | + fn.search(); |
| 180 | + }, |
| 181 | + }; |
| 182 | +})(); |
| 183 | +Object.freeze(SearchService); |
| 184 | + |
| 185 | +SearchService.init(); |
| 186 | +document.addEventListener("pjax:success", SearchService.init); |
| 187 | +document.addEventListener("pjax:send", function () { |
| 188 | + document.querySelector("#u-search").style.display = "none"; |
| 189 | +}); |
0 commit comments