1(function () { 2 function escapeHtml(value) { 3 return String(value || '') 4 .replace(/&/g, '&') 5 .replace(/</g, '<') 6 .replace(/>/g, '>') 7 .replace(/"/g, '"') 8 .replace(/'/g, '''); 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.