From fa88440e7e36ac743b1311d7da1b2b04fd66f9ef Mon Sep 17 00:00:00 2001 From: Phaiax Date: Sun, 26 Nov 2017 19:05:36 +0100 Subject: [PATCH] Search: Fix scroll-to-top on escape (unmark). Rearrange javascript and add comments --- src/theme/book.js | 187 ++++++++++++++++++++++++---------------------- 1 file changed, 98 insertions(+), 89 deletions(-) diff --git a/src/theme/book.js b/src/theme/book.js index 23bf2c8a..0bfd7bb1 100644 --- a/src/theme/book.js +++ b/src/theme/book.js @@ -2,8 +2,8 @@ $( document ).ready(function() { // Search functionality // - // Usage: call init() on startup. You can use hasFocus() to disable prevent keyhandling - // while the user is typing his search. + // Usage: call init() on startup. You can use !hasFocus() to prevent keyhandling in your key + // event handlers while the user is typing his search. var search = { searchbar : $('#searchbar'), searchbar_outer : $('#searchbar-outer'), @@ -25,10 +25,10 @@ $( document ).ready(function() { breadcrumbs: {boost: 0} } }, - mark_exclude : [], // ['.hljs'] + mark_exclude : [], current_searchterm : "", - SEARCH_PARAM : 'search', - MARK_PARAM : 'highlight', + URL_SEARCH_PARAM : 'search', + URL_MARK_PARAM : 'highlight', SEARCH_HOTKEY_KEYCODE: 83, ESCAPE_KEYCODE: 27, @@ -36,16 +36,8 @@ $( document ).ready(function() { UP_KEYCODE: 38, SELECT_KEYCODE: 13, - formatSearchMetric : function(count, searchterm) { - if (count == 1) { - return count + " search result for '" + searchterm + "':"; - } else if (count == 0) { - return "No search results for '" + searchterm + "'."; - } else { - return count + " search results for '" + searchterm + "':"; - } - } - , + + // Helper to parse a url into its building blocks. parseURL : function (url) { var a = document.createElement('a'); a.href = url; @@ -71,6 +63,7 @@ $( document ).ready(function() { }; } , + // Helper to recreate a url string from its building blocks. renderURL : function (urlobject) { var url = urlobject.protocol + "://" + urlobject.host; if (urlobject.port != "") { @@ -90,6 +83,7 @@ $( document ).ready(function() { return url; } , + // Helper to escape html special chars for displaying the teasers escapeHTML: (function() { var MAP = { '&': '&', @@ -104,18 +98,27 @@ $( document ).ready(function() { }; })() , + formatSearchMetric : function(count, searchterm) { + if (count == 1) { + return count + " search result for '" + searchterm + "':"; + } else if (count == 0) { + return "No search results for '" + searchterm + "'."; + } else { + return count + " search results for '" + searchterm + "':"; + } + } + , formatSearchResult : function (result, searchterms) { - // Show text around first occurrence of first search term. var teaser = this.makeTeaser(this.escapeHTML(result.doc.body), searchterms); - // The ?MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor + // The ?URL_MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor var url = result.ref.split("#"); - if (url.length == 1) { + if (url.length == 1) { // no anchor found url.push(""); } return $('
  • ' + result.doc.breadcrumbs + '' // doc.title + '' + '' + '' + teaser + '' @@ -216,55 +219,6 @@ $( document ).ready(function() { return teaser_split.join(''); } , - doSearch : function (searchterm) { - - // Don't search the same twice - if (this.current_searchterm == searchterm) { return; } - else { this.current_searchterm = searchterm; } - - if (this.searchindex == null) { return; } - - // Do the actual search - var results = this.searchindex.search(searchterm, this.searchoptions); - var resultcount = Math.min(results.length, this.searchoptions.limit_results); - - // Display search metrics - this.searchresults_header.text(this.formatSearchMetric(resultcount, searchterm)); - - // Clear and insert results - var searchterms = searchterm.split(' '); - this.searchresults.empty(); - for(var i = 0; i < resultcount ; i++){ - this.searchresults.append(this.formatSearchResult(results[i], searchterms)); - } - - // Display and scroll to results - this.searchresults_outer.slideDown(); - // this.searchicon.scrollTop(0); - } - , - doSearchOrMarkFromUrl : function () { - // Check current URL for search request - var url = this.parseURL(window.location.href); - if (url.params.hasOwnProperty(this.SEARCH_PARAM) - && url.params[this.SEARCH_PARAM] != "") { - this.searchbar_outer.slideDown(); - this.searchbar[0].value = decodeURIComponent( - (url.params[this.SEARCH_PARAM]+'').replace(/\+/g, '%20')); - this.searchbarKeyUpHandler(); - } else { - this.searchbar_outer.slideUp(); - } - - if (url.params.hasOwnProperty(this.MARK_PARAM)) { - var words = url.params[this.MARK_PARAM].split(' '); - var header = $('#' + url.hash); - this.content.mark(words, { - exclude : this.mark_exclude - }); - } - } - , init : function () { var this_ = this; @@ -296,6 +250,38 @@ $( document ).ready(function() { return this.searchbar.is(':focus'); } , + unfocusSearchbar : function () { + // hacky, but just focusing a div only works once + var tmp = $(''); + tmp.insertAfter(this.searchicon); + tmp.focus(); + tmp.remove(); + } + , + // On reload or browser history backwards/forwards events, parse the url and do search or mark + doSearchOrMarkFromUrl : function () { + // Check current URL for search request + var url = this.parseURL(window.location.href); + if (url.params.hasOwnProperty(this.URL_SEARCH_PARAM) + && url.params[this.URL_SEARCH_PARAM] != "") { + this.searchbar_outer.slideDown(); + this.searchbar[0].value = decodeURIComponent( + (url.params[this.URL_SEARCH_PARAM]+'').replace(/\+/g, '%20')); + this.searchbarKeyUpHandler(); // -> doSearch() + } else { + this.searchbar_outer.slideUp(); + } + + if (url.params.hasOwnProperty(this.URL_MARK_PARAM)) { + var words = url.params[this.URL_MARK_PARAM].split(' '); + var header = $('#' + url.hash); + this.content.mark(words, { + exclude : this.mark_exclude + }); + } + } + , + // Eventhandler for keyevents on `document` globalKeyHandler : function (e) { if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } @@ -304,8 +290,10 @@ $( document ).ready(function() { this.searchbar.removeClass("active"); // this.searchbar[0].value = ""; this.setSearchUrlParameters("", - (this.searchbar[0].value.trim() != 0) ? "push" : "replace"); - this.unfocusSearchbar(); + (this.searchbar[0].value.trim() != "") ? "push" : "replace"); + if (this.hasFocus()) { + this.unfocusSearchbar(); + } this.searchbar_outer.slideUp(); this.content.unmark(); return; @@ -349,21 +337,13 @@ $( document ).ready(function() { } } , - unfocusSearchbar : function () { - // hacky, but just focusing a div only works once - var tmp = $(''); - tmp.insertAfter(this.searchicon); - tmp.focus(); - tmp.remove(); - } - , + // Eventhandler for search icon searchIconClickHandler : function () { this.searchbar_outer.slideToggle(); this.searchbar.focus(); - // TODO: - // If invisible, clear URL search parameter } , + // Eventhandler for keyevents while the searchbar is focused searchbarKeyUpHandler : function () { var searchterm = this.searchbar[0].value.trim(); if (searchterm != "") { @@ -375,32 +355,61 @@ $( document ).ready(function() { this.searchresults.empty(); } - this.setSearchUrlParameters(searchterm, "if_begin_search"); + this.setSearchUrlParameters(searchterm, "push_if_new_search_else_replace"); // Remove marks this.content.unmark(); } , + // Update current url with ?URL_SEARCH_PARAM= parameter, remove ?URL_MARK_PARAM and #heading-anchor . + // `action` can be one of "push", "replace", "push_if_new_search_else_replace" + // and replaces or pushes a new browser history item. + // "push_if_new_search_else_replace" pushes if there is no `?URL_SEARCH_PARAM=abc` yet. setSearchUrlParameters : function(searchterm, action) { - // Update url with ?SEARCH_PARAM= parameter, remove ?MARK_PARAM and #heading-anchor var url = this.parseURL(window.location.href); - var first_search = ! url.params.hasOwnProperty(this.SEARCH_PARAM); - if (searchterm != "" || action == "if_begin_search") { - url.params[this.SEARCH_PARAM] = searchterm; - delete url.params[this.MARK_PARAM]; + var first_search = ! url.params.hasOwnProperty(this.URL_SEARCH_PARAM); + if (searchterm != "" || action == "push_if_new_search_else_replace") { + url.params[this.URL_SEARCH_PARAM] = searchterm; + delete url.params[this.URL_MARK_PARAM]; url.hash = ""; } else { - delete url.params[this.SEARCH_PARAM]; + delete url.params[this.URL_SEARCH_PARAM]; } // A new search will also add a new history item, so the user can go back // to the page prior to searching. A updated search term will only replace // the url. - if (action == "push" || (action == "if_begin_search" && first_search) ) { + if (action == "push" || (action == "push_if_new_search_else_replace" && first_search) ) { history.pushState({}, document.title, this.renderURL(url)); - } else if (action == "replace" || (action == "if_begin_search" && !first_search) ) { + } else if (action == "replace" || (action == "push_if_new_search_else_replace" && !first_search) ) { history.replaceState({}, document.title, this.renderURL(url)); } + } + , + doSearch : function (searchterm) { + // Don't search the same twice + if (this.current_searchterm == searchterm) { return; } + else { this.current_searchterm = searchterm; } + + if (this.searchindex == null) { return; } + + // Do the actual search + var results = this.searchindex.search(searchterm, this.searchoptions); + var resultcount = Math.min(results.length, this.searchoptions.limit_results); + + // Display search metrics + this.searchresults_header.text(this.formatSearchMetric(resultcount, searchterm)); + + // Clear and insert results + var searchterms = searchterm.split(' '); + this.searchresults.empty(); + for(var i = 0; i < resultcount ; i++){ + this.searchresults.append(this.formatSearchResult(results[i], searchterms)); + } + + // Display and scroll to results + this.searchresults_outer.slideDown(); + // this.searchicon.scrollTop(0); } };