blob: f8bd5af8d70632ee18ecf073812de4c38c1ed0df [file] [log] [blame]
Hao Zhuc1450882018-10-03 17:56:26 -04001gitbook.require(["gitbook", "lodash", "jQuery"], function(gitbook, _, $) {
2 var index = null;
3 var $searchInput, $searchForm;
4 var $highlighted, hi = 0, hiOpts = { className: 'search-highlight' };
5 var collapse = false;
6
7 // Use a specific index
8 function loadIndex(data) {
9 // [Yihui] In bookdown, I use a character matrix to store the chapter
10 // content, and the index is dynamically built on the client side.
11 // Gitbook prebuilds the index data instead: https://github.com/GitbookIO/plugin-search
12 // We can certainly do that via R packages V8 and jsonlite, but let's
13 // see how slow it really is before improving it. On the other hand,
14 // lunr cannot handle non-English text very well, e.g. the default
15 // tokenizer cannot deal with Chinese text, so we may want to replace
16 // lunr with a dumb simple text matching approach.
17 index = lunr(function () {
18 this.ref('url');
19 this.field('title', { boost: 10 });
20 this.field('body');
21 });
22 data.map(function(item) {
23 index.add({
24 url: item[0],
25 title: item[1],
26 body: item[2]
27 });
28 });
29 }
30
31 // Fetch the search index
32 function fetchIndex() {
33 return $.getJSON(gitbook.state.basePath+"/search_index.json")
34 .then(loadIndex); // [Yihui] we need to use this object later
35 }
36
37 // Search for a term and return results
38 function search(q) {
39 if (!index) return;
40
41 var results = _.chain(index.search(q))
42 .map(function(result) {
43 var parts = result.ref.split("#");
44 return {
45 path: parts[0],
46 hash: parts[1]
47 };
48 })
49 .value();
50
51 // [Yihui] Highlight the search keyword on current page
52 hi = 0;
53 $highlighted = results.length === 0 ? undefined : $('.page-inner')
54 .unhighlight(hiOpts).highlight(q, hiOpts).find('span.search-highlight');
55 scrollToHighlighted();
56 toggleTOC(results.length > 0);
57
58 return results;
59 }
60
61 // [Yihui] Scroll the chapter body to the i-th highlighted string
62 function scrollToHighlighted() {
63 if (!$highlighted) return;
64 var n = $highlighted.length;
65 if (n === 0) return;
66 var $p = $highlighted.eq(hi), p = $p[0], rect = p.getBoundingClientRect();
67 if (rect.top < 0 || rect.bottom > $(window).height()) {
68 ($(window).width() >= 1240 ? $('.body-inner') : $('.book-body'))
69 .scrollTop(p.offsetTop - 100);
70 }
71 $highlighted.css('background-color', '');
72 // an orange background color on the current item and removed later
73 $p.css('background-color', 'orange');
74 setTimeout(function() {
75 $p.css('background-color', '');
76 }, 2000);
77 }
78
79 // [Yihui] Expand/collapse TOC
80 function toggleTOC(show) {
81 if (!collapse) return;
82 var toc_sub = $('ul.summary').children('li[data-level]').children('ul');
83 if (show) return toc_sub.show();
84 var href = window.location.pathname;
85 href = href.substr(href.lastIndexOf('/') + 1);
86 if (href === '') href = 'index.html';
87 var li = $('a[href^="' + href + location.hash + '"]').parent('li.chapter').first();
88 toc_sub.hide().parent().has(li).children('ul').show();
89 li.children('ul').show();
90 }
91
92 // Create search form
93 function createForm(value) {
94 if ($searchForm) $searchForm.remove();
95 if ($searchInput) $searchInput.remove();
96
97 $searchForm = $('<div>', {
98 'class': 'book-search',
99 'role': 'search'
100 });
101
102 $searchInput = $('<input>', {
103 'type': 'search',
104 'class': 'form-control',
105 'val': value,
106 'placeholder': 'Type to search'
107 });
108
109 $searchInput.appendTo($searchForm);
110 $searchForm.prependTo(gitbook.state.$book.find('.book-summary'));
111 }
112
113 // Return true if search is open
114 function isSearchOpen() {
115 return gitbook.state.$book.hasClass("with-search");
116 }
117
118 // Toggle the search
119 function toggleSearch(_state) {
120 if (isSearchOpen() === _state) return;
121 if (!$searchInput) return;
122
123 gitbook.state.$book.toggleClass("with-search", _state);
124
125 // If search bar is open: focus input
126 if (isSearchOpen()) {
127 gitbook.sidebar.toggle(true);
128 $searchInput.focus();
129 } else {
130 $searchInput.blur();
131 $searchInput.val("");
132 gitbook.storage.remove("keyword");
133 gitbook.sidebar.filter(null);
134 $('.page-inner').unhighlight(hiOpts);
135 toggleTOC(false);
136 }
137 }
138
139 // Recover current search when page changed
140 function recoverSearch() {
141 var keyword = gitbook.storage.get("keyword", "");
142
143 createForm(keyword);
144
145 if (keyword.length > 0) {
146 if(!isSearchOpen()) {
147 toggleSearch(true); // [Yihui] open the search box
148 }
149 gitbook.sidebar.filter(_.pluck(search(keyword), "path"));
150 }
151 }
152
153
154 gitbook.events.bind("start", function(e, config) {
155 // [Yihui] disable search
156 if (config.search === false) return;
157 collapse = !config.toc || config.toc.collapse === 'section' ||
158 config.toc.collapse === 'subsection';
159
160 // Pre-fetch search index and create the form
161 fetchIndex()
162 // [Yihui] recover search after the page is loaded
163 .then(recoverSearch);
164
165
166 // Type in search bar
167 $(document).on("keyup", ".book-search input", function(e) {
168 var key = (e.keyCode ? e.keyCode : e.which);
169 // [Yihui] Escape -> close search box; Up/Down: previous/next highlighted
170 if (key == 27) {
171 e.preventDefault();
172 toggleSearch(false);
173 } else if (key == 38) {
174 if (hi <= 0 && $highlighted) hi = $highlighted.length;
175 hi--;
176 scrollToHighlighted();
177 } else if (key == 40) {
178 hi++;
179 if ($highlighted && hi >= $highlighted.length) hi = 0;
180 scrollToHighlighted();
181 }
182 }).on("input", ".book-search input", function(e) {
183 var q = $(this).val().trim();
184 if (q.length === 0) {
185 gitbook.sidebar.filter(null);
186 gitbook.storage.remove("keyword");
187 $('.page-inner').unhighlight(hiOpts);
188 toggleTOC(false);
189 } else {
190 var results = search(q);
191 gitbook.sidebar.filter(
192 _.pluck(results, "path")
193 );
194 gitbook.storage.set("keyword", q);
195 }
196 });
197
198 // Create the toggle search button
199 gitbook.toolbar.createButton({
200 icon: 'fa fa-search',
201 label: 'Search',
202 position: 'left',
203 onClick: toggleSearch
204 });
205
206 // Bind keyboard to toggle search
207 gitbook.keyboard.bind(['f'], toggleSearch);
208 });
209
210 // [Yihui] do not try to recover search; always start fresh
211 // gitbook.events.bind("page.change", recoverSearch);
212});