PageSourceSearch

https://the-linux-interview-cookbook.netlify.app/searcher-c2a407aa.js

js the-linux-interview-cookbook.netlify.app collected 2026-10-03 11:30:04 UTC 20,094 bytes, 555 lines download raw bytes

1'use strict';
2
3/* global Mark, elasticlunr, path_to_root */
4
5window.search = window.search || {};
6(function search() {
7    // Search functionality
8    //
9    // You can use !hasFocus() to prevent keyhandling in your key
10    // event handlers while the user is typing their search.
11
12    if (!Mark || !elasticlunr) {
13        return;
14    }
15
16    // eslint-disable-next-line max-len
17    // IE 11 Compatibility from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
18    if (!String.prototype.startsWith) {
19        String.prototype.startsWith = function(search, pos) {
20            return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search;
21        };
22    }
23
24    const search_wrap = document.getElementById('mdbook-search-wrapper'),
25        searchbar_outer = document.getElementById('mdbook-searchbar-outer'),
26        searchbar = document.getElementById('mdbook-searchbar'),
27        searchresults = document.getElementById('mdbook-searchresults'),
28        searchresults_outer = document.getElementById('mdbook-searchresults-outer'),
29        searchresults_header = document.getElementById('mdbook-searchresults-header'),
30        searchicon = document.getElementById('mdbook-search-toggle'),
31        content = document.getElementById('mdbook-content'),
32
33        // SVG text elements don't render if inside a <mark> tag.
34        mark_exclude = ['text'],
35        marker = new Mark(content),
36        URL_SEARCH_PARAM = 'search',
37        URL_MARK_PARAM = 'highlight';
38
39    let current_searchterm = '',
40        doc_urls = [],
41        search_options = {
42            bool: 'AND',
43            expand: true,
44            fields: {
45                title: {boost: 1},
46                body: {boost: 1},
47                breadcrumbs: {boost: 0},
48            },
49        },
50        searchindex = null,
51        results_options = {
52            teaser_word_count: 30,
53            limit_results: 30,
54        },
55        teaser_count = 0;
56
57    function hasFocus() {
58        return searchbar === document.activeElement;
59    }
60
61    function removeChildren(elem) {
62        while (elem.firstChild) {
63            elem.removeChild(elem.firstChild);
64        }
65    }
66
67    // Helper to parse a url into its building blocks.
68    function parseURL(url) {
69        const a = document.createElement('a');
70        a.href = url;
71        return {
72            source: url,
73            protocol: a.protocol.replace(':', ''),
74            host: a.hostname,
75            port: a.port,
76            params: (function() {
77                const ret = {};
78                const seg = a.search.replace(/^\?/, '').split('&');
79                for (const part of seg) {
80                    if (!part) {
81                        continue;
82                    }
83                    const s = part.split('=');
84                    ret[s[0]] = s[1];
85                }
86                return ret;
87            })(),
88            file: (a.pathname.match(/\/([^/?#]+)$/i) || ['', ''])[1],
89            hash: a.hash.replace('#', ''),
90            path: a.pathname.replace(/^([^/])/, '/$1'),
91        };
92    }
93
94    // Helper to recreate a url string from its building blocks.
95    function renderURL(urlobject) {
96        let url = urlobject.protocol + '://' + urlobject.host;
97        if (urlobject.port !== '') {
98            url += ':' + urlobject.port;
99        }
100        url += urlobject.path;
101        let joiner = '?';
102        for (const prop in urlobject.params) {
103            if (Object.prototype.hasOwnProperty.call(urlobject.params, prop)) {
104                url += joiner + prop + '=' + urlobject.params[prop];
105                joiner = '&';
106            }
107        }
108        if (urlobject.hash !== '') {
109            url += '#' + urlobject.hash;
110        }
111        return url;
112    }
113
114    // Helper to escape html special chars for displaying the teasers
115    const escapeHTML = (function() {
116        const MAP = {
117            '&': '&amp;',
118            '<': '&lt;',
119            '>': '&gt;',
120            '"': '&#34;',
121            '\'': '&#39;',
122        };
123        const repl = function(c) {
124            return MAP[c];
125        };
126        return function(s) {
127            return s.replace(/[&<>'"]/g, repl);
128        };
129    })();
130
131    function formatSearchMetric(count, searchterm) {
132        if (count === 1) {
133            return count + ' search result for \'' + searchterm + '\':';
134        } else if (count === 0) {
135            return 'No search results for \'' + searchterm + '\'.';
136        } else {
137            return count + ' search results for \'' + searchterm + '\':';
138        }
139    }
140
141    function formatSearchResult(result, searchterms) {
142        const teaser = makeTeaser(escapeHTML(result.doc.body), searchterms);
143        teaser_count++;
144
145        // The ?URL_MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor
146        const url = doc_urls[result.ref].split('#');
147        if (url.length === 1) { // no anchor found
148            url.push('');
149        }
150
151        // encodeURIComponent escapes all chars that could allow an XSS except
152        // for '. Due to that we also manually replace ' with its url-encoded
153        // representation (%27).
154        const encoded_search = encodeURIComponent(searchterms.join(' ')).replace(/'/g, '%27');
155
156        return '<a href="' + path_to_root + url[0] + '?' + URL_MARK_PARAM + '=' + encoded_search
157            + '#' + url[1] + '" aria-details="mdbook-teaser_' + teaser_count + '">'
158            + result.doc.breadcrumbs + '</a>'
159            + '<span class="teaser" id="mdbook-teaser_' + teaser_count
160            + '" aria-label="Search Result Teaser">' + teaser + '</span>';
161    }
162
163    function makeTeaser(body, searchterms) {
164        // The strategy is as follows:
165        // First, assign a value to each word in the document:
166        //  Words that correspond to search terms (stemmer aware): 40
167        //  Normal words: 2
168        //  First word in a sentence: 8
169        // Then use a sliding window with a constant number of words and count the
170        // sum of the values of the words within the window. Then use the window that got the
171        // maximum sum. If there are multiple maximas, then get the last one.
172        // Enclose the terms in <em>.
173        const stemmed_searchterms = searchterms.map(function(w) {
174            return elasticlunr.stemmer(w.toLowerCase());
175        });
176        const searchterm_weight = 40;
177        const weighted = []; // contains elements of ["word", weight, index_in_document]
178        // split in sentences, then words
179        const sentences = body.toLowerCase().split('. ');
180        let index = 0;
181        let value = 0;
182        let searchterm_found = false;
183        for (const sentenceindex in sentences) {
184            const words = sentences[sentenceindex].split(' ');
185            value = 8;
186            for (const wordindex in words) {
187                const word = words[wordindex];
188                if (word.length > 0) {
189                    for (const searchtermindex in stemmed_searchterms) {
190                        if (elasticlunr.stemmer(word).startsWith(
191                            stemmed_searchterms[searchtermindex])
192                        ) {
193                            value = searchterm_weight;
194                            searchterm_found = true;
195                        }
196                    }
197                    weighted.push([word, value, index]);
198                    value = 2;
199                }
200                index += word.length;
201                index += 1; // ' ' or '.' if last word in sentence
202            }
203            index += 1; // because we split at a two-char boundary '. '
204        }
205
206        if (weighted.length === 0) {
207            return body;
208        }
209
210        const window_weight = [];
211        const window_size = Math.min(weighted.length, results_options.teaser_word_count);
212
213        let cur_sum = 0;
214        for (let wordindex = 0; wordindex < window_size; wordindex++) {
215            cur_sum += weighted[wordindex][1];
216        }
217        window_weight.push(cur_sum);
218        for (let wordindex = 0; wordindex < weighted.length - window_size; wordindex++) {
219            cur_sum -= weighted[wordindex][1];
220            cur_sum += weighted[wordindex + window_size][1];
221            window_weight.push(cur_sum);
222        }
223
224        let max_sum_window_index = 0;
225        if (searchterm_found) {
226            let max_sum = 0;
227            // backwards
228            for (let i = window_weight.length - 1; i >= 0; i--) {
229                if (window_weight[i] > max_sum) {
230                    max_sum = window_weight[i];
231                    max_sum_window_index = i;
232                }
233            }
234        } else {
235            max_sum_window_index = 0;
236        }
237
238        // add <em/> around searchterms
239        const teaser_split = [];
240        index = weighted[max_sum_window_index][2];
241        for (let i = max_sum_window_index; i < max_sum_window_index + window_size; i++) {
242            const word = weighted[i];
243            if (index < word[2]) {
244                // missing text from index to start of `word`
245                teaser_split.push(body.substring(index, word[2]));
246                index = word[2];
247            }
248            if (word[1] === searchterm_weight) {
249                teaser_split.push('<em>');
250            }
251            index = word[2] + word[0].length;
252            teaser_split.push(body.substring(word[2], index));
253            if (word[1] === searchterm_weight) {
254                teaser_split.push('</em>');
255            }
256        }
257
258        return teaser_split.join('');
259    }
260
261    function init(config) {
262        results_options = config.results_options;
263        search_options = config.search_options;
264        doc_urls = config.doc_urls;
265        searchindex = elasticlunr.Index.load(config.index);
266
267        searchbar_outer.classList.remove('searching');
268
269        searchbar.focus();
270
271        const searchterm = searchbar.value.trim();
272        if (searchterm !== '') {
273            searchbar.classList.add('active');
274            doSearch(searchterm);
275        }
276    }
277
278    function initSearchInteractions(config) {
279        // Set up events
280        searchicon.addEventListener('click', () => {
281            searchIconClickHandler();
282        }, false);
283        searchbar.addEventListener('keyup', () => {
284            searchbarKeyUpHandler();
285        }, false);
286        document.addEventListener('keydown', e => {
287            globalKeyHandler(e);
288        }, false);
289        // If the user uses the browser buttons, do the same as if a reload happened
290        window.onpopstate = () => {
291            doSearchOrMarkFromUrl();
292        };
293        // Suppress "submit" events so the page doesn't reload when the user presses Enter
294        document.addEventListener('submit', e => {
295            e.preventDefault();
296        }, false);
297
298        // If reloaded, do the search or mark again, depending on the current url parameters
299        doSearchOrMarkFromUrl();
300
301        // Exported functions
302        config.hasFocus = hasFocus;
303    }
304
305    initSearchInteractions(window.search);
306
307    function unfocusSearchbar() {
308        // hacky, but just focusing a div only works once
309        const tmp = document.createElement('input');
310        tmp.setAttribute('style', 'position: absolute; opacity: 0;');
311        searchicon.appendChild(tmp);
312        tmp.focus();
313        tmp.remove();
314    }
315
316    // On reload or browser history backwards/forwards events, parse the url and do search or mark
317    function doSearchOrMarkFromUrl() {
318        // Check current URL for search request
319        const url = parseURL(window.location.href);
320        if (Object.prototype.hasOwnProperty.call(url.params, URL_SEARCH_PARAM)
321            && url.params[URL_SEARCH_PARAM] !== '') {
322            showSearch(true);
323            searchbar.value = decodeURIComponent(
324                (url.params[URL_SEARCH_PARAM] + '').replace(/\+/g, '%20'));
325            searchbarKeyUpHandler(); // -> doSearch()
326        } else {
327            showSearch(false);
328        }
329
330        if (Object.prototype.hasOwnProperty.call(url.params, URL_MARK_PARAM)) {
331            const words = decodeURIComponent(url.params[URL_MARK_PARAM]).split(' ');
332            marker.mark(words, {
333                exclude: mark_exclude,
334            });
335
336            const markers = document.querySelectorAll('mark');
337            const hide = () => {
338                for (let i = 0; i < markers.length; i++) {
339                    markers[i].classList.add('fade-out');
340                    window.setTimeout(() => {
341                        marker.unmark();
342                    }, 300);
343                }
344            };
345
346            for (let i = 0; i < markers.length; i++) {
347                markers[i].addEventListener('click', hide);
348            }
349        }
350    }
351
352    // Eventhandler for keyevents on `document`
353    function globalKeyHandler(e) {
354        if (e.altKey ||
355            e.ctrlKey ||
356            e.metaKey ||
357            e.shiftKey ||
358            e.target.type === 'textarea' ||
359            e.target.type === 'text' ||
360            !hasFocus() && /^(?:input|select|textarea)$/i.test(e.target.nodeName)
361        ) {
362            return;
363        }
364
365        if (e.key === 'Escape') {
366            e.preventDefault();
367            searchbar.classList.remove('active');
368            setSearchUrlParameters('',
369                searchbar.value.trim() !== '' ? 'push' : 'replace');
370            if (hasFocus()) {
371                unfocusSearchbar();
372            }
373            showSearch(false);
374            marker.unmark();
375        } else if (!hasFocus() && (e.key === 's' || e.key === '/')) {
376            e.preventDefault();
377            showSearch(true);
378            window.scrollTo(0, 0);
379            searchbar.select();
380        } else if (hasFocus() && (e.key === 'ArrowDown'
381                               || e.key === 'Enter')) {
382            e.preventDefault();
383            const first = searchresults.firstElementChild;
384            if (first !== null) {
385                unfocusSearchbar();
386                first.classList.add('focus');
387                if (e.key === 'Enter') {
388                    window.location.assign(first.querySelector('a'));
389                }
390            }
391        } else if (!hasFocus() && (e.key === 'ArrowDown'
392                                || e.key === 'ArrowUp'
393                                || e.key === 'Enter')) {
394            // not `:focus` because browser does annoying scrolling
395            const focused = searchresults.querySelector('li.focus');
396            if (!focused) {
397                return;
398            }
399            e.preventDefault();
400            if (e.key === 'ArrowDown') {
401                const next = focused.nextElementSibling;
402                if (next) {
403                    focused.classList.remove('focus');
404                    next.classList.add('focus');
405                }
406            } else if (e.key === 'ArrowUp') {
407                focused.classList.remove('focus');
408                const prev = focused.previousElementSibling;
409                if (prev) {
410                    prev.classList.add('focus');
411                } else {
412                    searchbar.select();
413                }
414            } else { // Enter
415                window.location.assign(focused.querySelector('a'));
416            }
417        }
418    }
419
420    function loadSearchScript(url, id) {
421        if (document.getElementById(id)) {
422            return;
423        }
424        searchbar_outer.classList.add('searching');
425
426        const script = document.createElement('script');
427        script.src = url;
428        script.id = id;
429        script.onload = () => init(window.search);
430        script.onerror = error => {
431            console.error(`Failed to load \`${url}\`: ${error}`);
432        };
433        document.head.append(script);
434    }
435
436    function showSearch(yes) {
437        if (yes) {
438            loadSearchScript(
439                window.path_to_searchindex_js ||
440                path_to_root + 'searchindex-a37dc288.js',
441                'mdbook-search-index');
442            search_wrap.classList.remove('hidden');
443            searchicon.setAttribute('aria-expanded', 'true');
444        } else {
445            search_wrap.classList.add('hidden');
446            searchicon.setAttribute('aria-expanded', 'false');
447            const results = searchresults.children;
448            for (let i = 0; i < results.length; i++) {
449                results[i].classList.remove('focus');
450            }
451        }
452    }
453
454    function showResults(yes) {
455        if (yes) {
456            searchresults_outer.classList.remove('hidden');
457        } else {
458            searchresults_outer.classList.add('hidden');
459        }
460    }
461
462    // Eventhandler for search icon
463    function searchIconClickHandler() {
464        if (search_wrap.classList.contains('hidden')) {
465            showSearch(true);
466            window.scrollTo(0, 0);
467            searchbar.select();
468        } else {
469            showSearch(false);
470        }
471    }
472
473    // Eventhandler for keyevents while the searchbar is focused
474    function searchbarKeyUpHandler() {
475        const searchterm = searchbar.value.trim();
476        if (searchterm !== '') {
477            searchbar.classList.add('active');
478            doSearch(searchterm);
479        } else {
480            searchbar.classList.remove('active');
481            showResults(false);
482            removeChildren(searchresults);
483        }
484
485        setSearchUrlParameters(searchterm, 'push_if_new_search_else_replace');
486
487        // Remove marks
488        marker.unmark();
489    }
490
491    // Update current url with ?URL_SEARCH_PARAM= parameter, remove ?URL_MARK_PARAM and
492    // `#heading-anchor`. `action` can be one of "push", "replace",
493    // "push_if_new_search_else_replace" and replaces or pushes a new browser history item.
494    // "push_if_new_search_else_replace" pushes if there is no `?URL_SEARCH_PARAM=abc` yet.
495    function setSearchUrlParameters(searchterm, action) {
496        const url = parseURL(window.location.href);
497        const first_search = !Object.prototype.hasOwnProperty.call(url.params, URL_SEARCH_PARAM);
498
499        if (searchterm !== '' || action === 'push_if_new_search_else_replace') {
500            url.params[URL_SEARCH_PARAM] = searchterm;
501            delete url.params[URL_MARK_PARAM];
502            url.hash = '';
503        } else {
504            delete url.params[URL_MARK_PARAM];
505            delete url.params[URL_SEARCH_PARAM];
506        }
507        // A new search will also add a new history item, so the user can go back
508        // to the page prior to searching. A updated search term will only replace
509        // the url.
510        if (action === 'push' || action === 'push_if_new_search_else_replace' && first_search ) {
511            history.pushState({}, document.title, renderURL(url));
512        } else if (action === 'replace' ||
513            action === 'push_if_new_search_else_replace' &&
514            !first_search
515        ) {
516            history.replaceState({}, document.title, renderURL(url));
517        }
518    }
519
520    function doSearch(searchterm) {
521        // Don't search the same twice
522        if (current_searchterm === searchterm) {
523            return;
524        }
525        searchbar_outer.classList.add('searching');
526        if (searchindex === null) {
527            return;
528        }
529
530        current_searchterm = searchterm;
531
532        // Do the actual search
533        const results = searchindex.search(searchterm, search_options);
534        const resultcount = Math.min(results.length, results_options.limit_results);
535
536        // Display search metrics
537        searchresults_header.innerText = formatSearchMetric(resultcount, searchterm);
538
539        // Clear and insert results
540        const searchterms = searchterm.split(' ');
541        removeChildren(searchresults);
542        for (let i = 0; i < resultcount ; i++) {
543            const resultElem = document.createElement('li');
544            resultElem.innerHTML = formatSearchResult(results[i], searchterms);
545            searchresults.appendChild(resultElem);
546        }
547
548        // Display results
549        showResults(true);
550        searchbar_outer.classList.remove('searching');
551    }
552
553    // Exported functions
554    search.hasFocus = hasFocus;
555})(window.search);

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.