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.