PageSourceSearch

https://ghsc223311.github.io/js/search/local-search.js

js ghsc223311.github.io collected 2026-10-03 09:14:31 UTC 9,255 bytes, 270 lines download raw bytes

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.