require(["gitbook", "lodash", "jQuery"], function(gitbook, _, $) { var index = null; var $searchInput, $searchForm; var $highlighted, hi = 0, hiOpts = { className: 'search-highlight' }; var collapse = false; // Use a specific index function loadIndex(data) { // [Yihui] In bookdown, I use a character matrix to store the chapter // content, and the index is dynamically built on the client side. // Gitbook prebuilds the index data instead: https://github.com/GitbookIO/plugin-search // We can certainly do that via R packages V8 and jsonlite, but let's // see how slow it really is before improving it. On the other hand, // lunr cannot handle non-English text very well, e.g. the default // tokenizer cannot deal with Chinese text, so we may want to replace // lunr with a dumb simple text matching approach. index = lunr(function () { this.ref('url'); this.field('title', { boost: 10 }); this.field('body'); }); data.map(function(item) { index.add({ url: item[0], title: item[1], body: item[2] }); }); } // Fetch the search index function fetchIndex() { return $.getJSON(gitbook.state.basePath+"/search_index.json") .then(loadIndex); // [Yihui] we need to use this object later } // Search for a term and return results function search(q) { if (!index) return; var results = _.chain(index.search(q)) .map(function(result) { var parts = result.ref.split("#"); return { path: parts[0], hash: parts[1] }; }) .value(); // [Yihui] Highlight the search keyword on current page hi = 0; $highlighted = results.length === 0 ? undefined : $('.page-inner') .unhighlight(hiOpts).highlight(q, hiOpts).find('span.search-highlight'); scrollToHighlighted(); toggleTOC(results.length > 0); return results; } // [Yihui] Scroll the chapter body to the i-th highlighted string function scrollToHighlighted() { if (!$highlighted) return; var n = $highlighted.length; if (n === 0) return; var $p = $highlighted.eq(hi), p = $p[0], rect = p.getBoundingClientRect(); if (rect.top < 0 || rect.bottom > $(window).height()) { ($(window).width() >= 1240 ? $('.body-inner') : $('.book-body')) .scrollTop(p.offsetTop - 100); } $highlighted.css('background-color', ''); // an orange background color on the current item and removed later $p.css('background-color', 'orange'); setTimeout(function() { $p.css('background-color', ''); }, 2000); } // [Yihui] Expand/collapse TOC function toggleTOC(show) { if (!collapse) return; var toc_sub = $('ul.summary').children('li[data-level]').children('ul'); if (show) return toc_sub.show(); var href = window.location.pathname; href = href.substr(href.lastIndexOf('/') + 1); if (href === '') href = 'index.html'; var li = $('a[href^="' + href + location.hash + '"]').parent('li.chapter').first(); toc_sub.hide().parent().has(li).children('ul').show(); li.children('ul').show(); } // Create search form function createForm(value) { if ($searchForm) $searchForm.remove(); if ($searchInput) $searchInput.remove(); $searchForm = $('
', { 'class': 'book-search', 'role': 'search' }); $searchInput = $('', { 'type': 'search', 'class': 'form-control', 'val': value, 'placeholder': 'Type to search' }); $searchInput.appendTo($searchForm); $searchForm.prependTo(gitbook.state.$book.find('.book-summary')); } // Return true if search is open function isSearchOpen() { return gitbook.state.$book.hasClass("with-search"); } // Toggle the search function toggleSearch(_state) { if (isSearchOpen() === _state) return; if (!$searchInput) return; gitbook.state.$book.toggleClass("with-search", _state); // If search bar is open: focus input if (isSearchOpen()) { gitbook.sidebar.toggle(true); $searchInput.focus(); } else { $searchInput.blur(); $searchInput.val(""); gitbook.storage.remove("keyword"); gitbook.sidebar.filter(null); $('.page-inner').unhighlight(hiOpts); toggleTOC(false); } } // Recover current search when page changed function recoverSearch() { var keyword = gitbook.storage.get("keyword", ""); createForm(keyword); if (keyword.length > 0) { if(!isSearchOpen()) { toggleSearch(true); // [Yihui] open the search box } gitbook.sidebar.filter(_.pluck(search(keyword), "path")); } } gitbook.events.bind("start", function(e, config) { // [Yihui] disable search if (config.search === false) return; collapse = !config.toc || config.toc.collapse === 'section' || config.toc.collapse === 'subsection'; // Pre-fetch search index and create the form fetchIndex() // [Yihui] recover search after the page is loaded .then(recoverSearch); // Type in search bar $(document).on("keyup", ".book-search input", function(e) { var key = (e.keyCode ? e.keyCode : e.which); // [Yihui] Escape -> close search box; Up/Down: previous/next highlighted if (key == 27) { e.preventDefault(); toggleSearch(false); } else if (key == 38) { if (hi <= 0 && $highlighted) hi = $highlighted.length; hi--; scrollToHighlighted(); } else if (key == 40) { hi++; if ($highlighted && hi >= $highlighted.length) hi = 0; scrollToHighlighted(); } }).on("input", ".book-search input", function(e) { var q = $(this).val().trim(); if (q.length === 0) { gitbook.sidebar.filter(null); gitbook.storage.remove("keyword"); $('.page-inner').unhighlight(hiOpts); toggleTOC(false); } else { var results = search(q); gitbook.sidebar.filter( _.pluck(results, "path") ); gitbook.storage.set("keyword", q); } }); // Create the toggle search button gitbook.toolbar.createButton({ icon: 'fa fa-search', label: 'Search', position: 'left', onClick: toggleSearch }); // Bind keyboard to toggle search gitbook.keyboard.bind(['f'], toggleSearch); }); // [Yihui] do not try to recover search; always start fresh // gitbook.events.bind("page.change", recoverSearch); });