diff --git a/static/js/search.js b/static/js/search.js index 66ea2f10b0..c5ff45f365 100644 --- a/static/js/search.js +++ b/static/js/search.js @@ -2,7 +2,12 @@ window.relearn = window.relearn || {}; window.relearn.runInitialSearch = function(){ if( window.relearn.isSearchInit && window.relearn.isLunrInit ){ - searchDetail(); + var input = document.querySelector('#search-by-detail'); + if( !input ){ + return; + } + var value = input.value; + searchDetail( value ); } } @@ -40,24 +45,55 @@ function initLunrIndex( index ){ } function triggerSearch(){ - searchDetail(); var input = document.querySelector('#search-by-detail'); if( !input ){ return; } var value = input.value; + searchDetail( value ); + + // add a new entry to the history after the user + // changed the term; this does not reload the page + // but will add to the history and update the address bar URL var url = new URL( window.location ); - var oldValue = url.searchParams.get('search-by'); + var oldValue = url.searchParams.get( 'search-by' ); if( value != oldValue ){ - url.searchParams.set('search-by', value); - window.history.pushState(url.toString(), '', url); + var state = window.history.state || {}; + state = Object.assign( {}, ( typeof state === 'object' ) ? state : {} ); + url.searchParams.set( 'search-by', value ); + state.search = url.toString(); + // with normal pages, this is handled by the 'pagehide' event, but this + // doesn't fire in case of pushState, so we have to do the same thing + // here, too + state.contentScrollTop = elc.scrollTop; + window.history.pushState( state, '', url ); } } -window.addEventListener('popstate', function ( event ) { +window.addEventListener( 'popstate', function ( event ){ // restart search if browsed thru history - if (event.state && event.state.indexOf('search.html?search-by=') >= 0) { - window.location.reload(); + if( event.state ){ + var state = window.history.state || {}; + state = Object.assign( {}, ( typeof state === 'object' ) ? state : {} ); + if( state.search ) { + var url = new URL( state.search ); + if( url.searchParams.has('search-by') ){ + var search = url.searchParams.get( 'search-by' ); + + // we have to insert the old search term into the inputs + var inputs = document.querySelectorAll( 'input.search-by' ); + inputs.forEach( function( e ){ + e.value = search; + var event = document.createEvent( 'Event' ); + event.initEvent( 'input', false, false ); + e.dispatchEvent( event ); + }); + + // recreate the last search results and eventually + // restore the previous scrolling position + searchDetail( search ); + } + } } }); @@ -161,12 +197,7 @@ function resolvePlaceholders( s, args ) { }); }; -function searchDetail() { - var input = document.querySelector('#search-by-detail'); - if( !input ){ - return; - } - var value = input.value; +function searchDetail( value ) { var results = document.querySelector('#searchresults'); var hint = document.querySelector('.searchhint'); hint.innerText = ''; @@ -201,14 +232,32 @@ function searchDetail() { } input.focus(); setTimeout( adjustContentWidth, 0 ); + + // if we are initiating search because of a browser history + // operation, we have to restore the scrolling postion the + // user previously has used; if this search isn't initiated + // by a browser history operation, it simply does nothing + var state = window.history.state || {}; + state = Object.assign( {}, ( typeof state === 'object' ) ? state : {} ); + if( state.contentScrollTop ){ + window.setTimeout( function(){ + elc.scrollTop = state.contentScrollTop; + }, 10 ); + return; + } } initLunrJson(); initLunrJs(); function startSearch(){ - var url = new URL( window.location ); - window.history.replaceState(url.toString(), '', url); + var input = document.querySelector('#search-by-detail'); + if( input ){ + var state = window.history.state || {}; + state = Object.assign( {}, ( typeof state === 'object' ) ? state : {} ); + state.search = window.location.toString(); + window.history.replaceState( state, '', window.location ); + } var searchList = new autoComplete({ /* selector for the search box element */ diff --git a/static/js/theme.js b/static/js/theme.js index 7d29dd4b85..4bd8c1b44e 100644 --- a/static/js/theme.js +++ b/static/js/theme.js @@ -815,31 +815,69 @@ function initHistory() { } } -function scrollToActiveMenu() { - window.setTimeout(function(){ +function initScrollPositionSaver(){ + function savePosition( event ){ + var state = window.history.state || {}; + state = Object.assign( {}, ( typeof state === 'object' ) ? state : {} ); + state.contentScrollTop = elc.scrollTop; + window.history.replaceState( state, '', window.location ); + }; + window.addEventListener( 'pagehide', savePosition ); +} + +function scrollToPositions() { + // show active menu entry + window.setTimeout( function(){ var e = document.querySelector( '#sidebar ul.topics li.active a' ); if( e && e.scrollIntoView ){ e.scrollIntoView({ block: 'center', }); } - }, 10); -} + }, 10 ); -function scrollToFragment() { - if( !window.location.hash || window.location.hash.length <= 1 ){ + // scroll the content to point of interest; + // if we have a scroll position saved, the user was here + // before in his history stack and we want to reposition + // to the position he was when he left the page; + // otherwise if he used page search before, we want to position + // to its last outcome; + // otherwise he may want to see a specific fragment + + var state = window.history.state || {}; + state = ( typeof state === 'object') ? state : {}; + if( state.contentScrollTop !== undefined ){ + window.setTimeout( function(){ + elc.scrollTop = state.contentScrollTop; + }, 10 ); + return; + } + + var searchValue = sessionStorage.getItem( baseUriFull+'search-value' ); + var found = elementContains( searchValue, elc ); + var searchedElem = found.length && found[ 0 ]; + if( searchedElem ){ + searchedElem.scrollIntoView( true ); + var scrolledY = window.scrollY; + if( scrolledY ){ + window.scroll( 0, scrolledY - 125 ); + } + return; + } + + if( window.location.hash && window.location.hash.length > 1 ){ + window.setTimeout( function(){ + try{ + var e = document.querySelector( window.location.hash ); + if( e && e.scrollIntoView ){ + e.scrollIntoView({ + block: 'start', + }); + } + } catch( e ){} + }, 10 ); return; } - window.setTimeout(function(){ - try{ - var e = document.querySelector( window.location.hash ); - if( e && e.scrollIntoView ){ - e.scrollIntoView({ - block: 'start', - }); - } - } catch( e ){} - }, 10); } function mark() { @@ -1064,25 +1102,15 @@ function initSearch() { } mark(); - // set initial search value on page load + // set initial search value for inputs on page load if( sessionStorage.getItem( baseUriFull+'search-value' ) ){ - var searchValue = sessionStorage.getItem( baseUriFull+'search-value' ); + var search = sessionStorage.getItem( baseUriFull+'search-value' ); inputs.forEach( function( e ){ - e.value = searchValue; + e.value = search; var event = document.createEvent( 'Event' ); event.initEvent( 'input', false, false ); e.dispatchEvent( event ); }); - - var found = elementContains( searchValue, document.querySelector( '#body-inner' ) ); - var searchedElem = found.length && found[ 0 ]; - if( searchedElem ){ - searchedElem.scrollIntoView( true ); - var scrolledY = window.scrollY; - if( scrolledY ){ - window.scroll( 0, scrolledY - 125 ); - } - } } window.relearn.isSearchInit = true; @@ -1094,8 +1122,6 @@ ready( function(){ initMermaid(); initSwagger(); initMenuScrollbar(); - scrollToActiveMenu(); - scrollToFragment(); initToc(); initAnchorClipboard(); initCodeClipboard(); @@ -1104,6 +1130,8 @@ ready( function(){ initHistory(); initSearch(); initImage(); + initScrollPositionSaver(); + scrollToPositions(); }); function useMermaid( config ){