PageSourceSearch

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

js kee02p.github.io collected 2026-10-03 09:27:15 UTC 5,718 bytes, 159 lines download raw bytes

1/* global CONFIG */
2
3(function() {
4  // Modified from [hexo-generator-search](https://github.com/wzpan/hexo-generator-search)
5  function localSearchFunc(path, searchSelector, resultSelector) {
6    'use strict';
7    // 0x00. environment initialization
8    var $input = jQuery(searchSelector);
9    var $result = jQuery(resultSelector);
10
11    if ($input.length === 0) {
12      // eslint-disable-next-line no-console
13      throw Error('No element selected by the searchSelector');
14    }
15    if ($result.length === 0) {
16      // eslint-disable-next-line no-console
17      throw Error('No element selected by the resultSelector');
18    }
19
20    if ($result.attr('class').indexOf('list-group-item') === -1) {
21      $result.html('<div class="m-auto text-center"><div class="spinner-border" role="status"><span class="sr-only">Loading...</span></div><br/>Loading...</div>');
22    }
23
24    jQuery.ajax({
25      // 0x01. load xml file
26      url     : path,
27      dataType: 'xml',
28      success : function(xmlResponse) {
29        // 0x02. parse xml file
30        var dataList = jQuery('entry', xmlResponse).map(function() {
31          return {
32            title  : jQuery('title', this).text(),
33            content: jQuery('content', this).text(),
34            url    : jQuery('url', this).text()
35          };
36        }).get();
37
38        if ($result.html().indexOf('list-group-item') === -1) {
39          $result.html('');
40        }
41
42        $input.on('input', function() {
43          // 0x03. parse query to keywords list
44          var content = $input.val();
45          var resultHTML = '';
46          var keywords = content.trim().toLowerCase().split(/[\s-]+/);
47          $result.html('');
48          if (content.trim().length <= 0) {
49            return $input.removeClass('invalid').removeClass('valid');
50          }
51          // 0x04. perform local searching
52          dataList.forEach(function(data) {
53            var isMatch = true;
54            if (!data.title || data.title.trim() === '') {
55              data.title = 'Untitled';
56            }
57            var orig_data_title = data.title.trim();
58            var data_title = orig_data_title.toLowerCase();
59            var orig_data_content = data.content.trim().replace(/<[^>]+>/g, '');
60            var data_content = orig_data_content.toLowerCase();
61            var data_url = data.url;
62            var index_title = -1;
63            var index_content = -1;
64            var first_occur = -1;
65            // Skip matching when content is included in search and content is empty
66            if (CONFIG.include_content_in_search && data_content === '') {
67              isMatch = false;
68            } else {
69              keywords.forEach(function (keyword, i) {
70                index_title = data_title.indexOf(keyword);
71                index_content = data_content.indexOf(keyword);
72
73                if (index_title < 0 && index_content < 0) {
74                  isMatch = false;
75                } else {
76                  if (index_content < 0) {
77                    index_content = 0;
78                  }
79                  if (i === 0) {
80                    first_occur = index_content;
81                  }
82                }
83              });
84            }
85            // 0x05. show search results
86            if (isMatch) {
87              resultHTML += '<a href=\'' + data_url + '\' class=\'list-group-item list-group-item-action font-weight-bolder search-list-title\'>' + orig_data_title + '</a>';
88              var content = orig_data_content;
89              if (first_occur >= 0) {
90                // cut out 100 characters
91                var start = first_occur - 20;
92                var end = first_occur + 80;
93
94                if (start < 0) {
95                  start = 0;
96                }
97
98                if (start === 0) {
99                  end = 100;
100                }
101
102                if (end > content.length) {
103                  end = content.length;
104                }
105
106                var match_content = content.substring(start, end);
107
108                // highlight all keywords
109                keywords.forEach(function(keyword) {
110                  var regS = new RegExp(keyword, 'gi');
111                  match_content = match_content.replace(regS, '<span class="search-word">' + keyword + '</span>');
112                });
113
114                resultHTML += '<p class=\'search-list-content\'>' + match_content + '...</p>';
115              }
116            }
117          });
118          if (resultHTML.indexOf('list-group-item') === -1) {
119            return $input.addClass('invalid').removeClass('valid');
120          }
121          $input.addClass('valid').removeClass('invalid');
122          $result.html(resultHTML);
123        });
124      }
125    });
126  }
127
128  function localSearchReset(searchSelector, resultSelector) {
129    'use strict';
130    var $input = jQuery(searchSelector);
131    var $result = jQuery(resultSelector);
132
133    if ($input.length === 0) {
134      // eslint-disable-next-line no-console
135      throw Error('No element selected by the searchSelector');
136    }
137    if ($result.length === 0) {
138      // eslint-disable-next-line no-console
139      throw Error('No element selected by the resultSelector');
140    }
141
142    $input.val('').removeClass('invalid').removeClass('valid');
143    $result.html('');
144  }
145
146  var modal = jQuery('#modalSearch');
147  var searchSelector = '#local-search-input';
148  var resultSelector = '#local-search-result';
149  modal.on('show.bs.modal', function() {
150    var path = CONFIG.search_path || '/local-search.xml';
151    localSearchFunc(path, searchSelector, resultSelector);
152  });
153  modal.on('shown.bs.modal', function() {
154    jQuery('#local-search-input').focus();
155  });
156  modal.on('hidden.bs.modal', function() {
157    localSearchReset(searchSelector, resultSelector);
158  });
159})();

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.