PageSourceSearch

https://tdmdal.github.io/computing-research/libs/gitbook-2.6.7/js/plugin-search.js

js tdmdal.github.io collected 2026-10-03 10:03:53 UTC 9,078 bytes, 270 lines download raw bytes

1gitbook.require(["gitbook", "lodash", "jQuery"], function(gitbook, _, $) {
2    var index = null;
3    var fuse = null;
4    var _search = {engine: 'lunr', opts: {}};
5    var $searchInput, $searchLabel, $searchForm;
6    var $highlighted = [], hi, hiOpts = { className: 'search-highlight' };
7    var collapse = false, toc_visible = [];
8
9    function init(config) {
10        // Instantiate search settings
11        _search = gitbook.storage.get("search", {
12            engine: config.search.engine || 'lunr',
13            opts: config.search.options || {},
14        });
15    };
16
17    // Save current search settings
18    function saveSearchSettings() {
19        gitbook.storage.set("search", _search);
20    }
21
22    // Use a specific index
23    function loadIndex(data) {
24        // [Yihui] In bookdown, I use a character matrix to store the chapter
25        // content, and the index is dynamically built on the client side.
26        // Gitbook prebuilds the index data instead: https://github.com/GitbookIO/plugin-search
27        // We can certainly do that via R packages V8 and jsonlite, but let's
28        // see how slow it really is before improving it. On the other hand,
29        // lunr cannot handle non-English text very well, e.g. the default
30        // tokenizer cannot deal with Chinese text, so we may want to replace
31        // lunr with a dumb simple text matching approach.
32        if (_search.engine === 'lunr') {
33          index = lunr(function () {
34            this.ref('url');
35            this.field('title', { boost: 10 });
36            this.field('body');
37          });
38          data.map(function(item) {
39            index.add({
40              url: item[0],
41              title: item[1],
42              body: item[2]
43            });
44          });
45          return;
46        }
47        fuse = new Fuse(data.map((_data => {
48            return {
49                url: _data[0],
50                title: _data[1],
51                body: _data[2]
52            };
53        })), Object.assign(
54            {
55                includeScore: true,
56                threshold: 0.1,
57                ignoreLocation: true,
58                keys: ["title", "body"]
59            },
60            _search.opts
61        ));
62    }
63
64    // Fetch the search index
65    function fetchIndex() {
66        return $.getJSON(gitbook.state.basePath+"/search_index.json")
67                .then(loadIndex);  // [Yihui] we need to use this object later
68    }
69
70    // Search for a term and return results
71    function search(q) {
72        let results = [];
73        switch (_search.engine) {
74            case 'fuse':
75                if (!fuse) return;
76                results = fuse.search(q).map(function(result) {
77                    var parts = result.item.url.split('#');
78                    return {
79                        path: parts[0],
80                        hash: parts[1]
81                    };
82                });
83                break;
84            case 'lunr':
85            default:
86                if (!index) return;
87                results = _.chain(index.search(q)).map(function(result) {
88                    var parts = result.ref.split("#");
89                    return {
90                        path: parts[0],
91                        hash: parts[1]
92                    };
93                })
94                .value();
95        }
96
97        // [Yihui] Highlight the search keyword on current page
98        $highlighted = $('.page-inner')
99          .unhighlight(hiOpts).highlight(q, hiOpts).find('span.search-highlight');
100        scrollToHighlighted(0);
101
102        return results;
103    }
104
105    // [Yihui] Scroll the chapter body to the i-th highlighted string
106    function scrollToHighlighted(d) {
107      var n = $highlighted.length;
108      hi = hi === undefined ? 0 : hi + d;
109      // navignate to the previous/next page in the search results if reached the top/bottom
110      var b = hi < 0;
111      if (d !== 0 && (b || hi >= n)) {
112        var path = currentPath(), n2 = toc_visible.length;
113        if (n2 === 0) return;
114        for (var i = b ? 0 : n2; (b && i < n2) || (!b && i >= 0); i += b ? 1 : -1) {
115          if (toc_visible.eq(i).data('path') === path) break;
116        }
117        i += b ? -1 : 1;
118        if (i < 0) i = n2 - 1;
119        if (i >= n2) i = 0;
120        var lnk = toc_visible.eq(i).find('a[href$=".html"]');
121        if (lnk.length) lnk[0].click();
122        return;
123      }
124      if (n === 0) return;
125      var $p = $highlighted.eq(hi);
126      $p[0].scrollIntoView();
127      $highlighted.css('background-color', '');
128      // an orange background color on the current item and removed later
129      $p.css('background-color', 'orange');
130      setTimeout(function() {
131        $p.css('background-color', '');
132      }, 2000);
133    }
134
135    function currentPath() {
136      var href = window.location.pathname;
137      href = href.substr(href.lastIndexOf('/') + 1);
138      return href === '' ? 'index.html' : href;
139    }
140
141    // Create search form
142    function createForm(value) {
143        if ($searchForm) $searchForm.remove();
144        if ($searchLabel) $searchLabel.remove();
145        if ($searchInput) $searchInput.remove();
146
147        $searchForm = $('<div>', {
148            'class': 'book-search',
149            'role': 'search'
150        });
151
152        $searchLabel = $('<label>', {
153            'for': 'search-box',
154            'aria-hidden': 'false',
155            'hidden': ''
156        });
157
158        $searchInput = $('<input>', {
159            'id': 'search-box',
160            'type': 'search',
161            'class': 'form-control',
162            'val': value,
163            'placeholder': 'Type to search (Enter for navigation)',
164            'title': 'Use Enter or the <Down> key to navigate to the next match, or the <Up> key to the previous match'
165        });
166
167        $searchLabel.append("Type to search");
168        $searchLabel.appendTo($searchForm);
169        $searchInput.appendTo($searchForm);
170        $searchForm.prependTo(gitbook.state.$book.find('.book-summary'));
171    }
172
173    // Return true if search is open
174    function isSearchOpen() {
175        return gitbook.state.$book.hasClass("with-search");
176    }
177
178    // Toggle the search
179    function toggleSearch(_state) {
180        if (isSearchOpen() === _state) return;
181        if (!$searchInput) return;
182
183        gitbook.state.$book.toggleClass("with-search", _state);
184
185        // If search bar is open: focus input
186        if (isSearchOpen()) {
187            gitbook.sidebar.toggle(true);
188            $searchInput.focus();
189        } else {
190            $searchInput.blur();
191            $searchInput.val("");
192            gitbook.storage.remove("keyword");
193            gitbook.sidebar.filter(null);
194            $('.page-inner').unhighlight(hiOpts);
195        }
196    }
197
198    function sidebarFilter(results) {
199        gitbook.sidebar.filter(_.pluck(results, "path"));
200        toc_visible = $('ul.summary').find('li:visible');
201    }
202
203    // Recover current search when page changed
204    function recoverSearch() {
205        var keyword = gitbook.storage.get("keyword", "");
206
207        createForm(keyword);
208
209        if (keyword.length > 0) {
210            if(!isSearchOpen()) {
211                toggleSearch(true); // [Yihui] open the search box
212            }
213            sidebarFilter(search(keyword));
214        }
215    }
216
217
218    gitbook.events.bind("start", function(e, config) {
219        // [Yihui] disable search
220        if (config.search === false) return;
221        init(config);
222        collapse = !config.toc || config.toc.collapse === 'section' ||
223          config.toc.collapse === 'subsection';
224
225        // Pre-fetch search index and create the form
226        fetchIndex()
227        // [Yihui] recover search after the page is loaded
228        .then(recoverSearch);
229
230
231        // Type in search bar
232        $(document).on("keyup", ".book-search input", function(e) {
233            var key = (e.keyCode ? e.keyCode : e.which);
234            // [Yihui] Escape -> close search box; Up/Down/Enter: previous/next highlighted
235            if (key == 27) {
236                e.preventDefault();
237                toggleSearch(false);
238            } else if (key == 38) {
239              scrollToHighlighted(-1);
240            } else if (key == 40 || key == 13) {
241              scrollToHighlighted(1);
242            }
243        }).on("input", ".book-search input", function(e) {
244            var q = $(this).val().trim();
245            if (q.length === 0) {
246                gitbook.sidebar.filter(null);
247                gitbook.storage.remove("keyword");
248                $('.page-inner').unhighlight(hiOpts);
249            } else {
250                var results = search(q);
251                sidebarFilter(results);
252                gitbook.storage.set("keyword", q);
253            }
254        });
255
256        // Create the toggle search button
257        gitbook.toolbar.createButton({
258            icon: 'fa fa-search',
259            label: 'Search',
260            position: 'left',
261            onClick: toggleSearch
262        });
263
264        // Bind keyboard to toggle search
265        gitbook.keyboard.bind(['f'], toggleSearch);
266    });
267
268    // [Yihui] do not try to recover search; always start fresh
269    // gitbook.events.bind("page.change", recoverSearch);
270});

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.