PageSourceSearch

https://sebastianraschka.com/js/site-search.js?v=20260905

js sebastianraschka.com collected 2026-09-24 08:29:36 UTC 8,421 bytes, 292 lines download raw bytes

1(function () {
2  function escapeHtml(value) {
3    return String(value || '')
4      .replace(/&/g, '&')
5      .replace(/</g, '&lt;')
6      .replace(/>/g, '&gt;')
7      .replace(/"/g, '&quot;')
8      .replace(/'/g, '&#39;');
9  }
10
11  function init() {
12    var container = document.querySelector('[data-header-search]');
13    var toggle = container ? container.querySelector('[data-search-toggle]') : null;
14    var panel = document.getElementById('site-search-panel');
15    var input = document.getElementById('site-search-input');
16    var results = document.getElementById('site-search-results');
17    var status = document.getElementById('site-search-status');
18    if (!container || !toggle || !panel || !input || !results) {
19      return;
20    }
21
22    var scriptLoaded = typeof window.SiteSearch === 'function';
23    var searchReady = false;
24    var searchInitializing = false;
25    var searchIndex = null;
26    var isOpen = false;
27    var resultTotal = 0;
28    var renderedQuery = '';
29    var completedQuery = '';
30    var completionTimer = null;
31    var completionRequested = false;
32
33    function cancelCompletion() {
34      window.clearTimeout(completionTimer);
35      completionTimer = null;
36    }
37
38    function completeSearch() {
39      cancelCompletion();
40      var query = input.value.trim();
41      if (!isOpen || !searchReady || !query || query !== renderedQuery) return;
42      var key = query.toLowerCase().replace(/\s+/g, ' ');
43      if (key === completedQuery) return;
44      if (window.SiteAnalytics && window.SiteAnalytics.search(query, resultTotal)) {
45        completedQuery = key;
46      }
47    }
48
49    function setStatus(message) {
50      if (status) {
51        status.textContent = message || '';
52      }
53    }
54
55    function clearResults() {
56      results.innerHTML = '';
57      results.removeAttribute('aria-busy');
58    }
59
60    function announceResults() {
61      if (!isOpen || !searchReady) {
62        return;
63      }
64      var query = input.value ? input.value.trim() : '';
65      if (!query) {
66        setStatus('');
67        return;
68      }
69      var resultCount = results.querySelectorAll('.header-search__item').length;
70      if (resultCount === 0) {
71        setStatus('No results found for "' + query + '".');
72      } else if (resultCount === 1) {
73        setStatus('1 result found for "' + query + '".');
74      } else {
75        setStatus(resultCount + ' results found for "' + query + '".');
76      }
77    }
78
79    function setOpen(value) {
80      isOpen = !!value;
81      container.classList.toggle('header-search--open', isOpen);
82      panel.hidden = !isOpen;
83      toggle.setAttribute('aria-expanded', String(isOpen));
84      toggle.setAttribute('aria-label', isOpen ? 'Close search' : 'Open search');
85
86      if (!isOpen) {
87        cancelCompletion();
88        completionRequested = false;
89        setStatus('');
90        clearResults();
91      }
92    }
93
94    function openSearch() {
95      if (!isOpen) {
96        setOpen(true);
97      }
98      input.focus();
99      searchCurrentQuery();
100    }
101
102    function closeSearch(refocusToggle) {
103      if (!isOpen) {
104        return;
105      }
106      setOpen(false);
107      if (refocusToggle) {
108        toggle.focus();
109      }
110    }
111
112    function searchFailed() {
113      cancelCompletion();
114      searchInitializing = false;
115      searchReady = false;
116      clearResults();
117      if (isOpen && input.value.trim()) {
118        setStatus('Search could not load. Press Enter or edit your query to retry.');
119      }
120    }
121
122    function loadSearchScript(callback) {
123      if (scriptLoaded || typeof window.SiteSearch === 'function') {
124        scriptLoaded = true;
125        callback();
126        return;
127      }
128
129      var existing = document.querySelector('script[data-site-search-lib="true"]');
130      if (existing) {
131        existing.remove();
132      }
133
134      var script = document.createElement('script');
135      script.src = container.getAttribute('data-search-script') || '/js/search-ranking.js';
136      script.async = true;
137      script.setAttribute('data-site-search-lib', 'true');
138      var timeout = window.setTimeout(failed, 10000);
139      function failed() {
140        window.clearTimeout(timeout);
141        script.onload = script.onerror = null;
142        script.remove();
143        searchFailed();
144      }
145      script.onload = function () {
146        window.clearTimeout(timeout);
147        script.onload = script.onerror = null;
148        if (typeof window.SiteSearch !== 'function') {
149          failed();
150          return;
151        }
152        scriptLoaded = true;
153        callback();
154      };
155      script.onerror = failed;
156      document.head.appendChild(script);
157    }
158
159    function loadSearchIndex(callback) {
160      var request = new XMLHttpRequest();
161      request.open('GET', '/search.json', true);
162      request.timeout = 10000;
163      request.onerror = request.ontimeout = request.onabort = searchFailed;
164      request.onload = function () {
165        if (request.status < 200 || request.status >= 300) {
166          searchFailed();
167          return;
168        }
169        try {
170          var index = JSON.parse(request.responseText);
171          if (!Array.isArray(index) || !index.every(function (entry) {
172            return entry && typeof entry.title === 'string' &&
173              typeof entry.url === 'string' && Object.keys(entry).every(function (key) {
174                return typeof entry[key] === 'string';
175              });
176          })) {
177            throw new Error('Invalid search index');
178          }
179          callback(index);
180        } catch (error) {
181          searchFailed();
182        }
183      };
184      request.send();
185    }
186
187    function initializeSearch() {
188      if (searchReady || searchInitializing) {
189        return;
190      }
191
192      searchInitializing = true;
193      results.setAttribute('aria-busy', 'true');
194      setStatus('Loading search index...');
195      loadSearchScript(function () {
196        loadSearchIndex(function (index) {
197          searchIndex = index;
198          searchReady = true;
199          searchInitializing = false;
200          results.removeAttribute('aria-busy');
201          searchCurrentQuery();
202        });
203      });
204    }
205
206    function searchCurrentQuery() {
207      cancelCompletion();
208      var query = input.value ? input.value.trim() : '';
209      if (!isOpen) return;
210
211      if (!query) {
212        clearResults();
213        setStatus('');
214        return;
215      }
216
217      if (!searchReady || !searchIndex) {
218        initializeSearch();
219        return;
220      }
221
222      var matches = window.SiteSearch(searchIndex, query, 8);
223      resultTotal = matches.total;
224      renderedQuery = query;
225      results.innerHTML = matches.results.map(function (entry) {
226        return '<li class="header-search__item"><a class="header-search__link" href="' +
227          escapeHtml(entry.url) + '"><span class="header-search__title">' + escapeHtml(entry.title) +
228          '</span><span class="header-search__desc">' + escapeHtml(entry.description) + '</span></a></li>';
229      }).join('') || '<li class="header-search__empty">No results found</li>';
230      announceResults();
231      if (completionRequested) {
232        completionRequested = false;
233        completeSearch();
234      } else {
235        completionTimer = window.setTimeout(completeSearch, 800);
236      }
237    }
238
239    toggle.addEventListener('click', function (event) {
240      event.preventDefault();
241      if (isOpen) {
242        closeSearch(false);
243      } else {
244        openSearch();
245      }
246    });
247
248    input.addEventListener('focus', function () {
249      if (!isOpen) {
250        setOpen(true);
251      }
252    });
253
254    input.addEventListener('input', function () {
255      completionRequested = false;
256      searchCurrentQuery();
257    });
258
259    panel.addEventListener('submit', function (event) {
260      event.preventDefault();
261      completionRequested = true;
262      searchCurrentQuery();
263    });
264
265    results.addEventListener('click', function (event) {
266      var link = event.target.closest('a');
267      if (link) {
268        completeSearch();
269        closeSearch(false);
270      }
271    });
272
273    document.addEventListener('click', function (event) {
274      if (!container.contains(event.target)) {
275        closeSearch(false);
276      }
277    });
278
279    document.addEventListener('keydown', function (event) {
280      if ((event.key === 'Escape' || event.key === 'Esc') && isOpen) {
281        closeSearch(true);
282      }
283    });
284
285  }
286
287  if (document.readyState === 'loading') {
288    document.addEventListener('DOMContentLoaded', init);
289  } else {
290    init();
291  }
292})();

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.