1window.addEventListener("load", () => { 2 let loadFlag = false; 3 let dataObj = []; 4 const $searchMask = document.getElementById("search-mask"); 5 6 const openSearch = () => { 7 const bodyStyle = document.body.style; 8 bodyStyle.width = "100%"; 9 bodyStyle.overflow = "hidden"; 10 anzhiyu.animateIn($searchMask, "to_show 0.5s"); 11 anzhiyu.animateIn(document.querySelector("#local-search .search-dialog"), "titleScale 0.5s"); 12 setTimeout(() => { 13 document.querySelector("#local-search-input input").focus(); 14 }, 100); 15 if (!loadFlag) { 16 search(); 17 loadFlag = true; 18 } 19 // shortcut: ESC 20 document.addEventListener("keydown", function f(event) { 21 if (event.code === "Escape") { 22 closeSearch(); 23 document.removeEventListener("keydown", f); 24 } 25 }); 26 }; 27 28 const closeSearch = () => { 29 const bodyStyle = document.body.style; 30 bodyStyle.width = ""; 31 bodyStyle.overflow = ""; 32 anzhiyu.animateOut(document.querySelector("#local-search .search-dialog"), "search_close .5s"); 33 anzhiyu.animateOut($searchMask, "to_hide 0.5s"); 34 }; 35 36 const searchClickFn = () => { 37 document.querySelector("#search-button > .search").addEventListener("click", openSearch); 38 document.querySelector("#menu-search").addEventListener("click", openSearch); 39 }; 40 41 const searchClickFnOnce = () => { 42 document.querySelector("#local-search .search-close-button").addEventListener("click", closeSearch); 43 $searchMask.addEventListener("click", closeSearch); 44 if (GLOBAL_CONFIG.localSearch.preload) dataObj = fetchData(GLOBAL_CONFIG.localSearch.path); 45 }; 46 47 // check url is json or not 48 const isJson = url => { 49 const reg = /\.json$/; 50 return reg.test(url); 51 }; 52 53 const fetchData = async path => { 54 let data = []; 55 const response = await fetch(path); 56 if (isJson(path)) { 57 data = await response.json(); 58 } else { 59 const res = await response.text(); 60 const t = await new window.DOMParser().parseFromString(res, "text/xml"); 61 const a = await t; 62 63 data = [...a.querySelectorAll("entry")].map(item => { 64 let tagsArr = []; 65 if (item.querySelector("tags") && item.querySelector("tags").getElementsByTagName("tag")) { 66 Array.prototype.forEach.call(item.querySelector("tags").getElementsByTagName("tag"), function (item, index) { 67 tagsArr.push(item.textContent); 68 }); 69 } 70 let content = item.querySelector("content") && item.querySelector("content").textContent; 71 let imgReg = /<img.*?(?:>|\/>)/gi; //å¹é å¾çä¸çimgæ ç¾ 72 let srcReg = /src=[\'\"]?([^\'\"]*)[\'\"]?/i; // å¹é å¾çä¸çsrc 73 let arr = content.match(imgReg); //çéåºææçimg 74 75 let srcArr = []; 76 if (arr) { 77 for (let i = 0; i < arr.length; i++) { 78 let src = arr[i].match(srcReg); 79 // è·åå¾çå°å
80 if (!src[1].indexOf("http")) srcArr.push(src[1]); 81 } 82 } 83 84 return { 85 title: item.querySelector("title").textContent, 86 content: content, 87 url: item.querySelector("url").textContent, 88 tags: tagsArr, 89 oneImage: srcArr && srcArr[0], 90 }; 91 }); 92 } 93 if (response.ok) { 94 const $loadDataItem = document.getElementById("loading-database"); 95 $loadDataItem.nextElementSibling.style.display = "block"; 96 $loadDataItem.remove(); 97 } 98 return data; 99 }; 100 101 const search = () => { 102 if (!GLOBAL_CONFIG.localSearch.preload) { 103 dataObj = fetchData(GLOBAL_CONFIG.localSearch.path); 104 } 105 const $input = document.querySelector("#local-search-input input"); 106 const $resultContent = document.getElementById("local-search-results"); 107 const $loadingStatus = document.getElementById("loading-status"); 108 109 $input.addEventListener("input", function () { 110 const keywords = this.value.trim().toLowerCase().split(/[\s]+/); 111 if (keywords[0] !== "") 112 $loadingStatus.innerHTML = '<i class="anzhiyufont anzhiyu-icon-spinner anzhiyu-pulse-icon"></i>'; 113 114 $resultContent.innerHTML = ""; 115 let str = '<div class="search-result-list">'; 116 if (keywords.length <= 0) return; 117 let count = 0; 118 // perform local searching 119 dataObj.then(data => { 120 data.forEach(data => { 121 let isMatch = true; 122 let dataTitle = data.title ? data.title.trim().toLowerCase() : ""; 123 let dataTags = data.tags; 124 let oneImage = data.oneImage ?? ""; 125 const dataContent = data.content 126 ? data.content 127 .trim() 128 .replace(/<[^>]+>/g, "") 129 .toLowerCase() 130 : ""; 131 const dataUrl = data.url.startsWith("/") ? data.url : GLOBAL_CONFIG.root + data.url; 132 let indexTitle = -1; 133 let indexContent = -1; 134 let firstOccur = -1; 135 // only match articles with not empty titles and contents 136 if (dataTitle !== "" || dataContent !== "") { 137 keywords.forEach((keyword, i) => { 138 indexTitle = dataTitle.indexOf(keyword); 139 indexContent = dataContent.indexOf(keyword); 140 if (indexTitle < 0 && indexContent < 0) { 141 isMatch = false; 142 } else { 143 if (indexContent < 0) { 144 indexContent = 0; 145 } 146 if (i === 0) { 147 firstOccur = indexContent; 148 } 149 } 150 }); 151 } else { 152 isMatch = false; 153 } 154 155 // show search results 156 if (isMatch) { 157 if (firstOccur >= 0) { 158 // cut out 130 characters 159 // let start = firstOccur - 30 < 0 ? 0 : firstOccur - 30 160 // let end = firstOccur + 50 > dataContent.length ? dataContent.length : firstOccur + 50 161 let start = firstOccur - 30; 162 let end = firstOccur + 100; 163 let pre = ""; 164 let post = ""; 165 166 if (start < 0) { 167 start = 0; 168 } 169 170 if (start === 0) { 171 end = 100; 172 } else { 173 pre = "..."; 174 } 175 176 if (end > dataContent.length) { 177 end = dataContent.length; 178 } else { 179 post = "..."; 180 } 181 182 let matchContent = dataContent.substring(start, end); 183 184 // highlight all keywords 185 keywords.forEach(keyword => { 186 const regS = new RegExp(keyword, "gi"); 187 matchContent = matchContent.replace(regS, '<span class="search-keyword">' + keyword + "</span>"); 188 dataTitle = dataTitle.replace(regS, '<span class="search-keyword">' + keyword + "</span>"); 189 }); 190 191 str += '<div class="local-search__hit-item">'; 192 if (oneImage) { 193 str += `<div class="search-left"><img src=${oneImage} alt=${dataTitle} data-fancybox='gallery'>`; 194 } else { 195 str += '<div class="search-left" style="width:0">'; 196 } 197 198 str += "</div>"; 199 200 if (oneImage) { 201 str += 202 '<div class="search-right"><a href="' + 203 dataUrl + 204 '" class="search-result-title">' + 205 dataTitle + 206 "</a>"; 207 } else { 208 str += 209 '<div class="search-right" style="width: 100%"><a href="' + 210 dataUrl + 211 '" class="search-result-title">' + 212 dataTitle + 213 "</a>"; 214 } 215 216 count += 1; 217 218 if (dataContent !== "") { 219 str += 220 '<p class="search-result" onclick="pjax.loadUrl(`' + 221 dataUrl + 222 '`)">' + 223 pre + 224 matchContent + 225 post + 226 "</p>"; 227 } 228 if (dataTags.length) { 229 str += '<div class="search-result-tags">'; 230 231 for (let i = 0; i < dataTags.length; i++) { 232 const element = dataTags[i].trim(); 233 234 str += 235 '<a class="tag-list" href="/tags/' + 236 element + 237 '/" data-pjax-state="" one-link-mark="yes">#' + 238 element + 239 "</a>"; 240 } 241 242 str += "</div>"; 243 } 244 } 245 str += "</div></div>"; 246 } 247 }); 248 if (count === 0) { 249 str += 250 '<div id="local-search__hits-empty">' + 251 GLOBAL_CONFIG.localSearch.languages.hits_empty.replace(/\$\{query}/, this.value.trim()) + 252 "</div>"; 253 } 254 str += "</div>"; 255 $resultContent.innerHTML = str; 256 if (keywords[0] !== "") $loadingStatus.innerHTML = ""; 257 window.pjax && window.pjax.refresh($resultContent); 258 }); 259 }); 260 }; 261 262 searchClickFn(); 263 searchClickFnOnce(); 264 265 // pjax 266 window.addEventListener("pjax:complete", () => { 267 !anzhiyu.isHidden($searchMask) && closeSearch(); 268 searchClickFn(); 269 }); 270});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.