mirror of
				https://github.com/HackTricks-wiki/hacktricks.git
				synced 2025-10-10 18:36:50 +00:00 
			
		
		
		
	make search faster
This commit is contained in:
		
							parent
							
								
									e914418d46
								
							
						
					
					
						commit
						ec5829bd0a
					
				| @ -94,4 +94,3 @@ Check the labs to test both scenarios in [https://github.com/0ang3el/websocket-s | ||||
| {{#include ../banners/hacktricks-training.md}} | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										10
									
								
								theme/elasticlunr.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								theme/elasticlunr.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -1,554 +1,40 @@ | ||||
| /* ──────────────────────────────────────────────────────────────── | ||||
|    Polyfill so requestIdleCallback works everywhere (IE 11/Safari) | ||||
|    ─────────────────────────────────────────────────────────────── */ | ||||
| if (typeof window.requestIdleCallback !== "function") { | ||||
| window.requestIdleCallback = function (cb) { | ||||
|     const start = Date.now(); | ||||
|     return setTimeout(function () { | ||||
|     cb({ | ||||
|         didTimeout: false, | ||||
|         timeRemaining: function () { | ||||
|         return Math.max(0, 50 - (Date.now() - start)); | ||||
| /* ht_searcher.js --------------------------------------------------------- */ | ||||
| (() => { | ||||
|     const WRAPPER = document.getElementById('search-wrapper'); | ||||
|     const TOGGLE  = document.getElementById('search-toggle'); | ||||
|     const INPUT   = document.getElementById('searchbar'); | ||||
|     const LIST    = document.getElementById('searchresults'); | ||||
|     const HOTKEY  = 83;                // “s”
 | ||||
|     let   worker, debounce; | ||||
|    | ||||
|     function startWorker() { | ||||
|       if (worker) return; | ||||
|       worker = new Worker('/search-worker.js', { type:'module' }); | ||||
|       worker.onmessage = ({data}) => { | ||||
|         LIST.innerHTML = data.slice(0,30).map(h => | ||||
|           `<li><a href="${h.doc.url}">${h.doc.title}</a></li>` | ||||
|         ).join(''); | ||||
|       }; | ||||
|     } | ||||
|    | ||||
|     async function openUI() { | ||||
|       WRAPPER.classList.remove('hidden'); | ||||
|       INPUT.focus(); | ||||
|       startWorker();                   // fetches CDN/GitHub in parallel
 | ||||
|     } | ||||
|    | ||||
|     TOGGLE.addEventListener('click', openUI); | ||||
|     document.addEventListener('keydown', e => { | ||||
|       if (!e.metaKey && !e.ctrlKey && !e.altKey && e.keyCode === HOTKEY) { | ||||
|         e.preventDefault(); openUI(); | ||||
|       } | ||||
|     }); | ||||
|     }, 1); | ||||
| }; | ||||
| window.cancelIdleCallback = window.clearTimeout; | ||||
| } | ||||
|    | ||||
|    | ||||
| /* ──────────────────────────────────────────────────────────────── | ||||
|    search.js | ||||
|    ─────────────────────────────────────────────────────────────── */ | ||||
| 
 | ||||
| "use strict"; | ||||
| window.search = window.search || {}; | ||||
| (function search(search) { | ||||
|     // Search functionality
 | ||||
|     //
 | ||||
|     // You can use !hasFocus() to prevent keyhandling in your key
 | ||||
|     // event handlers while the user is typing their search.
 | ||||
| 
 | ||||
|     if (!Mark || !elasticlunr) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     //IE 11 Compatibility from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
 | ||||
|     if (!String.prototype.startsWith) { | ||||
|         String.prototype.startsWith = function(search, pos) { | ||||
|             return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search; | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     var search_wrap = document.getElementById('search-wrapper'), | ||||
|         search_modal = document.getElementById('search-modal'), | ||||
|         searchbar = document.getElementById('searchbar'), | ||||
|         searchbar_outer = document.getElementById('searchbar-outer'), | ||||
|         searchresults = document.getElementById('searchresults'), | ||||
|         searchresults_outer = document.getElementById('searchresults-outer'), | ||||
|         searchresults_header = document.getElementById('searchresults-header'), | ||||
|         searchicon = document.getElementById('search-toggle'), | ||||
|         content = document.getElementById('content'), | ||||
| 
 | ||||
|         searchindex = null, | ||||
|         doc_urls = [], | ||||
|         results_options = { | ||||
|             teaser_word_count: 30, | ||||
|             limit_results: 30, | ||||
|         }, | ||||
|         search_options = { | ||||
|             bool: "AND", | ||||
|             expand: true, | ||||
|             fields: { | ||||
|                 title: {boost: 1}, | ||||
|                 body: {boost: 1}, | ||||
|                 breadcrumbs: {boost: 0} | ||||
|             } | ||||
|         }, | ||||
|         mark_exclude = [], | ||||
|         marker = new Mark(content), | ||||
|         current_searchterm = "", | ||||
|         URL_SEARCH_PARAM = 'search', | ||||
|         URL_MARK_PARAM = 'highlight', | ||||
|         teaser_count = 0, | ||||
| 
 | ||||
|         SEARCH_HOTKEY_KEYCODE = 83, | ||||
|         ESCAPE_KEYCODE = 27, | ||||
|         DOWN_KEYCODE = 40, | ||||
|         UP_KEYCODE = 38, | ||||
|         SELECT_KEYCODE = 13; | ||||
| 
 | ||||
|     function hasFocus() { | ||||
|         return searchbar === document.activeElement; | ||||
|     } | ||||
| 
 | ||||
|     function removeChildren(elem) { | ||||
|         while (elem.firstChild) { | ||||
|             elem.removeChild(elem.firstChild); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Helper to parse a url into its building blocks.
 | ||||
|     function parseURL(url) { | ||||
|         var a =  document.createElement('a'); | ||||
|         a.href = url; | ||||
|         return { | ||||
|             source: url, | ||||
|             protocol: a.protocol.replace(':',''), | ||||
|             host: a.hostname, | ||||
|             port: a.port, | ||||
|             params: (function(){ | ||||
|                 var ret = {}; | ||||
|                 var seg = a.search.replace(/^\?/,'').split('&'); | ||||
|                 var len = seg.length, i = 0, s; | ||||
|                 for (;i<len;i++) { | ||||
|                     if (!seg[i]) { continue; } | ||||
|                     s = seg[i].split('='); | ||||
|                     ret[s[0]] = s[1]; | ||||
|                 } | ||||
|                 return ret; | ||||
|             })(), | ||||
|             file: (a.pathname.match(/\/([^/?#]+)$/i) || [,''])[1], | ||||
|             hash: a.hash.replace('#',''), | ||||
|             path: a.pathname.replace(/^([^/])/,'/$1') | ||||
|         }; | ||||
|     } | ||||
|      | ||||
|     // Helper to recreate a url string from its building blocks.
 | ||||
|     function renderURL(urlobject) { | ||||
|         var url = urlobject.protocol + "://" + urlobject.host; | ||||
|         if (urlobject.port != "") { | ||||
|             url += ":" + urlobject.port; | ||||
|         } | ||||
|         url += urlobject.path; | ||||
|         var joiner = "?"; | ||||
|         for(var prop in urlobject.params) { | ||||
|             if(urlobject.params.hasOwnProperty(prop)) { | ||||
|                 url += joiner + prop + "=" + urlobject.params[prop]; | ||||
|                 joiner = "&"; | ||||
|             } | ||||
|         } | ||||
|         if (urlobject.hash != "") { | ||||
|             url += "#" + urlobject.hash; | ||||
|         } | ||||
|         return url; | ||||
|     } | ||||
|      | ||||
|     // Helper to escape html special chars for displaying the teasers
 | ||||
|     var escapeHTML = (function() { | ||||
|         var MAP = { | ||||
|             '&': '&', | ||||
|             '<': '<', | ||||
|             '>': '>', | ||||
|             '"': '"', | ||||
|             "'": ''' | ||||
|         }; | ||||
|         var repl = function(c) { return MAP[c]; }; | ||||
|         return function(s) { | ||||
|             return s.replace(/[&<>'"]/g, repl); | ||||
|         }; | ||||
|     INPUT.addEventListener('input', e => { | ||||
|       clearTimeout(debounce); | ||||
|       debounce = setTimeout(() => { | ||||
|         worker?.postMessage(e.target.value.trim()); | ||||
|       }, 120);                        // small debounce keeps typing smooth
 | ||||
|     }); | ||||
|   })(); | ||||
|    | ||||
|     function formatSearchMetric(count, searchterm) { | ||||
|         if (count == 1) { | ||||
|             return count + " search result for '" + searchterm + "':"; | ||||
|         } else if (count == 0) { | ||||
|             return "No search results for '" + searchterm + "'."; | ||||
|         } else { | ||||
|             return count + " search results for '" + searchterm + "':"; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     function formatSearchResult(result, searchterms) { | ||||
|         var teaser = makeTeaser(escapeHTML(result.doc.body), searchterms); | ||||
|         teaser_count++; | ||||
| 
 | ||||
|         // The ?URL_MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor
 | ||||
|         var url = doc_urls[result.ref].split("#"); | ||||
|         if (url.length == 1) { // no anchor found
 | ||||
|             url.push(""); | ||||
|         } | ||||
| 
 | ||||
|         // encodeURIComponent escapes all chars that could allow an XSS except
 | ||||
|         // for '. Due to that we also manually replace ' with its url-encoded
 | ||||
|         // representation (%27).
 | ||||
|         var searchterms = encodeURIComponent(searchterms.join(" ")).replace(/\'/g, "%27"); | ||||
| 
 | ||||
|         return '<a href="' + path_to_root + url[0] + '?' + URL_MARK_PARAM + '=' + searchterms + '#' + url[1] | ||||
|             + '" aria-details="teaser_' + teaser_count + '">' + result.doc.breadcrumbs  | ||||
|             + '<span class="teaser" id="teaser_' + teaser_count + '" aria-label="Search Result Teaser">'  | ||||
|             + teaser + '</span>' + '</a>'; | ||||
|     } | ||||
|      | ||||
|     function makeTeaser(body, searchterms) { | ||||
|         // The strategy is as follows:
 | ||||
|         // First, assign a value to each word in the document:
 | ||||
|         //  Words that correspond to search terms (stemmer aware): 40
 | ||||
|         //  Normal words: 2
 | ||||
|         //  First word in a sentence: 8
 | ||||
|         // Then use a sliding window with a constant number of words and count the
 | ||||
|         // sum of the values of the words within the window. Then use the window that got the
 | ||||
|         // maximum sum. If there are multiple maximas, then get the last one.
 | ||||
|         // Enclose the terms in <em>.
 | ||||
|         var stemmed_searchterms = searchterms.map(function(w) { | ||||
|             return elasticlunr.stemmer(w.toLowerCase()); | ||||
|         }); | ||||
|         var searchterm_weight = 40; | ||||
|         var weighted = []; // contains elements of ["word", weight, index_in_document]
 | ||||
|         // split in sentences, then words
 | ||||
|         var sentences = body.toLowerCase().split('. '); | ||||
|         var index = 0; | ||||
|         var value = 0; | ||||
|         var searchterm_found = false; | ||||
|         for (var sentenceindex in sentences) { | ||||
|             var words = sentences[sentenceindex].split(' '); | ||||
|             value = 8; | ||||
|             for (var wordindex in words) { | ||||
|                 var word = words[wordindex]; | ||||
|                 if (word.length > 0) { | ||||
|                     for (var searchtermindex in stemmed_searchterms) { | ||||
|                         if (elasticlunr.stemmer(word).startsWith(stemmed_searchterms[searchtermindex])) { | ||||
|                             value = searchterm_weight; | ||||
|                             searchterm_found = true; | ||||
|                         } | ||||
|                     }; | ||||
|                     weighted.push([word, value, index]); | ||||
|                     value = 2; | ||||
|                 } | ||||
|                 index += word.length; | ||||
|                 index += 1; // ' ' or '.' if last word in sentence
 | ||||
|             }; | ||||
|             index += 1; // because we split at a two-char boundary '. '
 | ||||
|         }; | ||||
| 
 | ||||
|         if (weighted.length == 0) { | ||||
|             return body; | ||||
|         } | ||||
| 
 | ||||
|         var window_weight = []; | ||||
|         var window_size = Math.min(weighted.length, results_options.teaser_word_count); | ||||
| 
 | ||||
|         var cur_sum = 0; | ||||
|         for (var wordindex = 0; wordindex < window_size; wordindex++) { | ||||
|             cur_sum += weighted[wordindex][1]; | ||||
|         }; | ||||
|         window_weight.push(cur_sum); | ||||
|         for (var wordindex = 0; wordindex < weighted.length - window_size; wordindex++) { | ||||
|             cur_sum -= weighted[wordindex][1]; | ||||
|             cur_sum += weighted[wordindex + window_size][1]; | ||||
|             window_weight.push(cur_sum); | ||||
|         }; | ||||
| 
 | ||||
|         if (searchterm_found) { | ||||
|             var max_sum = 0; | ||||
|             var max_sum_window_index = 0; | ||||
|             // backwards
 | ||||
|             for (var i = window_weight.length - 1; i >= 0; i--) { | ||||
|                 if (window_weight[i] > max_sum) { | ||||
|                     max_sum = window_weight[i]; | ||||
|                     max_sum_window_index = i; | ||||
|                 } | ||||
|             }; | ||||
|         } else { | ||||
|             max_sum_window_index = 0; | ||||
|         } | ||||
| 
 | ||||
|         // add <em/> around searchterms
 | ||||
|         var teaser_split = []; | ||||
|         var index = weighted[max_sum_window_index][2]; | ||||
|         for (var i = max_sum_window_index; i < max_sum_window_index+window_size; i++) { | ||||
|             var word = weighted[i]; | ||||
|             if (index < word[2]) { | ||||
|                 // missing text from index to start of `word`
 | ||||
|                 teaser_split.push(body.substring(index, word[2])); | ||||
|                 index = word[2]; | ||||
|             } | ||||
|             if (word[1] == searchterm_weight) { | ||||
|                 teaser_split.push("<em>") | ||||
|             } | ||||
|             index = word[2] + word[0].length; | ||||
|             teaser_split.push(body.substring(word[2], index)); | ||||
|             if (word[1] == searchterm_weight) { | ||||
|                 teaser_split.push("</em>") | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         return teaser_split.join(''); | ||||
|     } | ||||
| 
 | ||||
|     function init(config) { | ||||
|         results_options = config.results_options; | ||||
|         search_options = config.search_options; | ||||
|         searchbar_outer = config.searchbar_outer; | ||||
|         doc_urls = config.doc_urls; | ||||
|         searchindex = elasticlunr.Index.load(config.index); | ||||
| 
 | ||||
|         // Set up events
 | ||||
|         searchicon.addEventListener('click', function(e) { searchIconClickHandler(); }, false); | ||||
|         search_wrap.addEventListener('click', function(e) { searchIconClickHandler(); }, false); | ||||
|         search_modal.addEventListener('click', function(e) { e.stopPropagation(); }, false); | ||||
|         searchbar.addEventListener('keyup', function(e) { searchbarKeyUpHandler(); }, false); | ||||
|         document.addEventListener('keydown', function(e) { globalKeyHandler(e); }, false); | ||||
|         // If the user uses the browser buttons, do the same as if a reload happened
 | ||||
|         window.onpopstate = function(e) { doSearchOrMarkFromUrl(); }; | ||||
|         // Suppress "submit" events so the page doesn't reload when the user presses Enter
 | ||||
|         document.addEventListener('submit', function(e) { e.preventDefault(); }, false); | ||||
| 
 | ||||
|         // If reloaded, do the search or mark again, depending on the current url parameters
 | ||||
|         doSearchOrMarkFromUrl(); | ||||
|     } | ||||
|      | ||||
|     function unfocusSearchbar() { | ||||
|         // hacky, but just focusing a div only works once
 | ||||
|         var tmp = document.createElement('input'); | ||||
|         tmp.setAttribute('style', 'position: absolute; opacity: 0;'); | ||||
|         searchicon.appendChild(tmp); | ||||
|         tmp.focus(); | ||||
|         tmp.remove(); | ||||
|     } | ||||
|      | ||||
|     // On reload or browser history backwards/forwards events, parse the url and do search or mark
 | ||||
|     function doSearchOrMarkFromUrl() { | ||||
|         // Check current URL for search request
 | ||||
|         var url = parseURL(window.location.href); | ||||
|         if (url.params.hasOwnProperty(URL_SEARCH_PARAM) | ||||
|             && url.params[URL_SEARCH_PARAM] != "") { | ||||
|             showSearch(true); | ||||
|             searchbar.value = decodeURIComponent( | ||||
|                 (url.params[URL_SEARCH_PARAM]+'').replace(/\+/g, '%20')); | ||||
|             searchbarKeyUpHandler(); // -> doSearch()
 | ||||
|         } else { | ||||
|             showSearch(false); | ||||
|         } | ||||
| 
 | ||||
|         if (url.params.hasOwnProperty(URL_MARK_PARAM)) { | ||||
|             var words = decodeURIComponent(url.params[URL_MARK_PARAM]).split(' '); | ||||
|             marker.mark(words, { | ||||
|                 exclude: mark_exclude | ||||
|             }); | ||||
| 
 | ||||
|             var markers = document.querySelectorAll("mark"); | ||||
|             function hide() { | ||||
|                 for (var i = 0; i < markers.length; i++) { | ||||
|                     markers[i].classList.add("fade-out"); | ||||
|                     window.setTimeout(function(e) { marker.unmark(); }, 300); | ||||
|                 } | ||||
|             } | ||||
|             for (var i = 0; i < markers.length; i++) { | ||||
|                 markers[i].addEventListener('click', hide); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     // Eventhandler for keyevents on `document`
 | ||||
|     function globalKeyHandler(e) { | ||||
|         if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea' || e.target.type === 'text' || !hasFocus() && /^(?:input|select|textarea)$/i.test(e.target.nodeName)) { return; } | ||||
| 
 | ||||
|         if (e.keyCode === ESCAPE_KEYCODE) { | ||||
|             e.preventDefault(); | ||||
|             searchbar.classList.remove("active"); | ||||
|             setSearchUrlParameters("", | ||||
|                 (searchbar.value.trim() !== "") ? "push" : "replace"); | ||||
|             if (hasFocus()) { | ||||
|                 unfocusSearchbar(); | ||||
|             } | ||||
|             showSearch(false); | ||||
|             marker.unmark(); | ||||
|         } else if (!hasFocus() && e.keyCode === SEARCH_HOTKEY_KEYCODE) { | ||||
|             e.preventDefault(); | ||||
|             showSearch(true); | ||||
|             window.scrollTo(0, 0); | ||||
|             searchbar.select(); | ||||
|         } else if (hasFocus() && e.keyCode === DOWN_KEYCODE) { | ||||
|             e.preventDefault(); | ||||
|             unfocusSearchbar(); | ||||
|             searchresults.firstElementChild.classList.add("focus"); | ||||
|         } else if (!hasFocus() && (e.keyCode === DOWN_KEYCODE | ||||
|                                 || e.keyCode === UP_KEYCODE | ||||
|                                 || e.keyCode === SELECT_KEYCODE)) { | ||||
|             // not `:focus` because browser does annoying scrolling
 | ||||
|             var focused = searchresults.querySelector("li.focus"); | ||||
|             if (!focused) return; | ||||
|             e.preventDefault(); | ||||
|             if (e.keyCode === DOWN_KEYCODE) { | ||||
|                 var next = focused.nextElementSibling; | ||||
|                 if (next) { | ||||
|                     focused.classList.remove("focus"); | ||||
|                     next.classList.add("focus"); | ||||
|                 } | ||||
|             } else if (e.keyCode === UP_KEYCODE) { | ||||
|                 focused.classList.remove("focus"); | ||||
|                 var prev = focused.previousElementSibling; | ||||
|                 if (prev) { | ||||
|                     prev.classList.add("focus"); | ||||
|                 } else { | ||||
|                     searchbar.select(); | ||||
|                 } | ||||
|             } else { // SELECT_KEYCODE
 | ||||
|                 window.location.assign(focused.querySelector('a')); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     function showSearch(yes) { | ||||
|         if (yes) { | ||||
|             search_wrap.classList.remove('hidden'); | ||||
|             searchicon.setAttribute('aria-expanded', 'true'); | ||||
|         } else { | ||||
|             search_wrap.classList.add('hidden'); | ||||
|             searchicon.setAttribute('aria-expanded', 'false'); | ||||
|             var results = searchresults.children; | ||||
|             for (var i = 0; i < results.length; i++) { | ||||
|                 results[i].classList.remove("focus"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function showResults(yes) { | ||||
|         if (yes) { | ||||
|             searchresults_outer.classList.remove('hidden'); | ||||
|         } else { | ||||
|             searchresults_outer.classList.add('hidden'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Eventhandler for search icon
 | ||||
|     function searchIconClickHandler() { | ||||
|         if (search_wrap.classList.contains('hidden')) { | ||||
|             showSearch(true); | ||||
|             window.scrollTo(0, 0); | ||||
|             searchbar.select(); | ||||
|         } else { | ||||
|             showSearch(false); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     // Eventhandler for keyevents while the searchbar is focused
 | ||||
|     function searchbarKeyUpHandler() { | ||||
|         var searchterm = searchbar.value.trim(); | ||||
|         if (searchterm != "") { | ||||
|             searchbar.classList.add("active"); | ||||
|             doSearch(searchterm); | ||||
|         } else { | ||||
|             searchbar.classList.remove("active"); | ||||
|             showResults(false); | ||||
|             removeChildren(searchresults); | ||||
|         } | ||||
| 
 | ||||
|         setSearchUrlParameters(searchterm, "push_if_new_search_else_replace"); | ||||
| 
 | ||||
|         // Remove marks
 | ||||
|         marker.unmark(); | ||||
|     } | ||||
|      | ||||
|     // Update current url with ?URL_SEARCH_PARAM= parameter, remove ?URL_MARK_PARAM and #heading-anchor .
 | ||||
|     // `action` can be one of "push", "replace", "push_if_new_search_else_replace"
 | ||||
|     // and replaces or pushes a new browser history item.
 | ||||
|     // "push_if_new_search_else_replace" pushes if there is no `?URL_SEARCH_PARAM=abc` yet.
 | ||||
|     function setSearchUrlParameters(searchterm, action) { | ||||
|         var url = parseURL(window.location.href); | ||||
|         var first_search = ! url.params.hasOwnProperty(URL_SEARCH_PARAM); | ||||
|         if (searchterm != "" || action == "push_if_new_search_else_replace") { | ||||
|             url.params[URL_SEARCH_PARAM] = searchterm; | ||||
|             delete url.params[URL_MARK_PARAM]; | ||||
|             url.hash = ""; | ||||
|         } else { | ||||
|             delete url.params[URL_MARK_PARAM]; | ||||
|             delete url.params[URL_SEARCH_PARAM]; | ||||
|         } | ||||
|         // A new search will also add a new history item, so the user can go back
 | ||||
|         // to the page prior to searching. A updated search term will only replace
 | ||||
|         // the url.
 | ||||
|         if (action == "push" || (action == "push_if_new_search_else_replace" && first_search) ) { | ||||
|             history.pushState({}, document.title, renderURL(url)); | ||||
|         } else if (action == "replace" || (action == "push_if_new_search_else_replace" && !first_search) ) { | ||||
|             history.replaceState({}, document.title, renderURL(url)); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     function doSearch(searchterm) { | ||||
| 
 | ||||
|         // Don't search the same twice
 | ||||
|         if (current_searchterm == searchterm) { return; } | ||||
|         else { current_searchterm = searchterm; } | ||||
| 
 | ||||
|         if (searchindex == null) { return; } | ||||
| 
 | ||||
|         // Do the actual search
 | ||||
|         var results = searchindex.search(searchterm, search_options); | ||||
|         var resultcount = Math.min(results.length, results_options.limit_results); | ||||
| 
 | ||||
|         // Display search metrics
 | ||||
|         searchresults_header.innerText = formatSearchMetric(resultcount, searchterm); | ||||
| 
 | ||||
|         // Clear and insert results
 | ||||
|         var searchterms  = searchterm.split(' '); | ||||
|         removeChildren(searchresults); | ||||
|         for(var i = 0; i < resultcount ; i++){ | ||||
|             var resultElem = document.createElement('li'); | ||||
|             resultElem.innerHTML = formatSearchResult(results[i], searchterms); | ||||
|             searchresults.appendChild(resultElem); | ||||
|         } | ||||
| 
 | ||||
|         // Display results
 | ||||
|         showResults(true); | ||||
|     } | ||||
| 
 | ||||
|     (async function loadSearchIndex(lang = window.lang || "en") { | ||||
|         const branch  = lang === "en" ? "master" : lang; | ||||
|         const rawUrl  = | ||||
|           `https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/refs/heads/${branch}/searchindex.js`; | ||||
|         const localJs = "/searchindex.js"; | ||||
|         const TIMEOUT_MS = 10_000; | ||||
| 
 | ||||
|         const injectScript = (src) => | ||||
|           new Promise((resolve, reject) => { | ||||
|             const s   = document.createElement("script"); | ||||
|             s.src     = src; | ||||
|             s.onload  = () => resolve(src); | ||||
|             s.onerror = (e) => reject(e); | ||||
|             document.head.appendChild(s); | ||||
|           }); | ||||
| 
 | ||||
|         try { | ||||
|           /* 1 — download raw JS from GitHub */ | ||||
|           const controller = new AbortController(); | ||||
|           const timer      = setTimeout(() => controller.abort(), TIMEOUT_MS); | ||||
| 
 | ||||
|           const res  = await fetch(rawUrl, { signal: controller.signal }); | ||||
|           clearTimeout(timer); | ||||
|           if (!res.ok) throw new Error(`HTTP ${res.status}`); | ||||
| 
 | ||||
|           /* 2 — wrap in a Blob so the browser sees application/javascript */ | ||||
|           const code     = await res.text(); | ||||
|           const blobUrl  = URL.createObjectURL( | ||||
|                             new Blob([code], { type: "application/javascript" }) | ||||
|                           ); | ||||
| 
 | ||||
|           /* 3 — execute it */ | ||||
|           await injectScript(blobUrl); | ||||
| 
 | ||||
|           /* ───────────── PATCH ───────────── | ||||
|              heavy parsing now deferred to idle time | ||||
|           */  | ||||
|           requestIdleCallback(() => init(window.search)); | ||||
|           return;  // ✔ UI remains responsive
 | ||||
|         } catch (eRemote) { | ||||
|           console.warn("Remote JS failed →", eRemote); | ||||
|         } | ||||
| 
 | ||||
|         /* ───────── fallback: local copy ───────── */ | ||||
|         try { | ||||
|           await injectScript(localJs); | ||||
| 
 | ||||
|           /* ───────────── PATCH ───────────── */ | ||||
|           requestIdleCallback(() => init(window.search)); | ||||
|           return; | ||||
|         } catch (eLocal) { | ||||
|           console.error("Local JS failed →", eLocal); | ||||
|         } | ||||
|     })(); | ||||
| 
 | ||||
|     // Exported functions
 | ||||
|     search.hasFocus = hasFocus; | ||||
| })(window.search); | ||||
							
								
								
									
										40
									
								
								theme/search-worker.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								theme/search-worker.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| /* search-worker.js ------------------------------------------------------- */ | ||||
| /* Make code written for window work in a worker: */ | ||||
| self.window = self; | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////
 | ||||
| // 1. elasticlunr.min.js : CDN first  →  local fallback
 | ||||
| ////////////////////////////////////////////////////////////////////////////
 | ||||
| try { | ||||
|   importScripts('https://cdn.jsdelivr.net/npm/elasticlunr@0.9.5/elasticlunr.min.js'); | ||||
| } catch (e) { | ||||
|   importScripts('/elasticlunr.min.js');           // ship this with your site
 | ||||
| } | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////
 | ||||
| // 2. searchindex.js : GitHub Raw first  →  local fallback
 | ||||
| //    We fetch → wrap in a Blob({type:'application/javascript'}) to bypass
 | ||||
| //    GitHub’s  text/plain + nosniff  MIME blocking.
 | ||||
| ////////////////////////////////////////////////////////////////////////////
 | ||||
| try { | ||||
|   const res  = await fetch( | ||||
|     'https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/refs/heads/master/searchindex.js', | ||||
|     {mode: 'cors'} | ||||
|   ); | ||||
|   if (!res.ok) throw new Error(res.status); | ||||
|   const blobUrl = URL.createObjectURL( | ||||
|     new Blob([await res.text()], { type:'application/javascript' }) | ||||
|   ); | ||||
|   importScripts(blobUrl);                         // correct MIME, runs once
 | ||||
| } catch (e) { | ||||
|   importScripts('/searchindex.js');               // offline fallback
 | ||||
| } | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////
 | ||||
| // 3. Build the index once and answer queries
 | ||||
| ////////////////////////////////////////////////////////////////////////////
 | ||||
| const idx = elasticlunr.Index.load(self.search.index); | ||||
| 
 | ||||
| self.onmessage = ({data: q}) => { | ||||
|   postMessage(idx.search(q, { bool:'AND', expand:true })); | ||||
| }; | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user