From dd44547a1059fbcede82120785a85a703a4d6960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Weber?= Date: Thu, 17 Nov 2022 17:29:01 +0100 Subject: [PATCH] search: improve keyboard handling #387 --- layouts/partials/footer.html | 4 +- layouts/partials/search.html | 12 +++-- static/css/auto-complete.css | 4 +- static/css/ie.css | 42 ++++++++------- static/css/theme-neon.css | 9 ++-- static/css/theme.css | 50 ++++++++++++------ static/css/variant.css | 10 ++-- static/js/auto-complete.js | 38 +++++++++++-- static/js/search.js | 1 + static/js/theme.js | 100 ++++++++++++++++++++++------------- 10 files changed, 177 insertions(+), 93 deletions(-) diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html index 383c810677..cc25494770 100644 --- a/layouts/partials/footer.html +++ b/layouts/partials/footer.html @@ -2,11 +2,11 @@ {{- partial "custom-comments.html" . }} -{{- partialCached "menu.html" . .RelPermalink }} +{{- $outputFormat := partial "output-format.hugo" .Page }} +{{- partial "output-partial.hugo" (dict "base" "menu" "page" . "parameter" . "outputFormat" $outputFormat) }} -{{- $outputFormat := partial "output-format.hugo" .Page }} {{- $wantsMathJax := or (.Page.Store.Get "hasMathJax") (and (.Page.Store.Get (printf "%sIsNested" $outputFormat)) (.Page.Store.Get "nestedHasMathJax")) }} {{- if $wantsMathJax }} {{- if isset .Params "mathjaxinitialize" }} diff --git a/layouts/partials/search.html b/layouts/partials/search.html index 53c2673d8c..4605958e7a 100644 --- a/layouts/partials/search.html +++ b/layouts/partials/search.html @@ -1,9 +1,13 @@ - {{ if $link }}{{ end }} {{- $assetBusting := not .Site.Params.disableAssetsBusting }} {{- $pageBaseLang := replaceRE "([a-z]+).*" "${1}" .Page.Lang }} {{- $contentlangs := (union (slice | append .Site.Params.additionalContentLanguage) (slice $pageBaseLang)) }} diff --git a/static/css/auto-complete.css b/static/css/auto-complete.css index 082a1f571d..9ac8b94dad 100644 --- a/static/css/auto-complete.css +++ b/static/css/auto-complete.css @@ -10,7 +10,8 @@ position: absolute; display: none; z-index: 9999; - max-height: 254px; + max-height: 150px; + max-height: calc( 100vh - 150px ); overflow: hidden; overflow-y: auto; box-sizing: border-box; @@ -43,6 +44,7 @@ .autocomplete-suggestion > .context { font-size: 12px; + margin-inline-start: 1em; overflow: hidden; text-overflow: ellipsis; } diff --git a/static/css/ie.css b/static/css/ie.css index 11d923f13b..d3078eefc7 100644 --- a/static/css/ie.css +++ b/static/css/ie.css @@ -5,6 +5,9 @@ .tags { direction: ltr; } + h2, h3, h4, h5, h6 { + padding-right: 2rem; + } blockquote { border-left: 10px solid rgba( 134, 134, 134, .4 ); padding-left: 0.85rem; @@ -44,6 +47,9 @@ #body .tab-nav-button:first-child { margin-left: 9px; } + .autocomplete-suggestion > .context { + margin-left: 1em; + } /* set default colors as in variant.css for IE11 */ body { @@ -136,10 +142,6 @@ font-family: "Work Sans", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif; /* var(--MAIN-TITLES-TEXT-font) */ } - h2, h3, h4, h5, h6 { - padding-right: 2rem; - } - div.box { background-color: rgba( 128, 128, 128, 1 ); /* var(--INTERNAL-BOX-NEUTRAL-color) */ } @@ -337,14 +339,14 @@ background-color: rgba( 128, 128, 128, 1 ); /* var(--BOX-NEUTRAL-color) */ } - .btn a { + .btn > * { border-color: rgba( 128, 128, 128, 1 ); /* var(--BOX-NEUTRAL-color) */ color: rgba( 255, 255, 255, 1 ); /* var(--BOX-CAPTION-color) */ } - .btn a:hover, - .btn a:focus, - .btn a:active { + .btn > *:hover, + .btn > *:focus, + .btn > *:active { background-color: rgba( 255, 255, 255, .833 ); /* var(--BOX-BG-color) */ color: rgba( 16, 16, 16, 1 ); /* var(--BOX-NEUTRAL-TEXT-color) */ } @@ -353,7 +355,7 @@ background-color: rgba( 48, 117, 229, 1 ); /* var(--BOX-INFO-color) */ } - .btn.cstyle.info a { + .btn.cstyle.info > * { border-color: rgba( 48, 117, 229, 1 ); /* var(--BOX-INFO-color) */ } @@ -361,7 +363,7 @@ background-color: rgba( 237, 153, 9, 1 ); /* var(--BOX-NOTE-color) */ } - .btn.cstyle.note a { + .btn.cstyle.note > * { border-color: rgba( 237, 153, 9, 1 ); /* var(--BOX-NOTE-color) */ } @@ -369,7 +371,7 @@ background-color: rgba( 42, 178, 24, 1 ); /* var(--BOX-TIP-color) */ } - .btn.cstyle.tip a { + .btn.cstyle.tip > * { border-color: rgba( 42, 178, 24, 1 ); /* var(--BOX-TIP-color) */ } @@ -377,7 +379,7 @@ background-color: rgba( 224, 62, 62, 1 ); /* var(--BOX-WARNING-color) */ } - .btn.cstyle.warning a { + .btn.cstyle.warning > * { border-color: rgba( 224, 62, 62, 1 ); /* var(--BOX-WARNING-color) */ } @@ -385,7 +387,7 @@ background-color: #7dc903; /* var(--PRIMARY-color) */ } - .btn.cstyle.primary a { + .btn.cstyle.primary > * { border-color: #7dc903; /* var(--PRIMARY-color) */ } @@ -393,7 +395,7 @@ background-color: #486ac9; /* var(--SECONDARY-color) */ } - .btn.cstyle.secondary a { + .btn.cstyle.secondary > * { border-color: #486ac9; /* var(--SECONDARY-color) */ } @@ -401,7 +403,7 @@ background-color: rgba( 48, 117, 229, 1 ); /* var(--BOX-BLUE-color) */ } - .btn.cstyle.blue a { + .btn.cstyle.blue > * { border-color: rgba( 48, 117, 229, 1 ); /* var(--BOX-BLUE-color) */ } @@ -409,7 +411,7 @@ background-color: rgba( 42, 178, 24, 1 ); /* var(--BOX-GREEN-color) */ } - .btn.cstyle.green a { + .btn.cstyle.green > * { border-color: rgba( 42, 178, 24, 1 ); /* var(--BOX-GREEN-color) */ } @@ -417,7 +419,7 @@ background-color: rgba( 128, 128, 128, 1 ); /* var(--BOX-GREY-color) */ } - .btn.cstyle.grey a { + .btn.cstyle.grey > * { border-color: rgba( 128, 128, 128, 1 ); /* var(--BOX-GREY-color) */ } @@ -425,7 +427,7 @@ background-color: rgba( 237, 153, 9, 1 ); /* var(--BOX-ORANGE-color) */ } - .btn.cstyle.orange a { + .btn.cstyle.orange > * { border-color: rgba( 237, 153, 9, 1 ); /* var(--BOX-ORANGE-color) */ } @@ -433,7 +435,7 @@ background-color: rgba( 224, 62, 62, 1 ); /* var(--BOX-RED-color) */ } - .btn.cstyle.red a { + .btn.cstyle.red > * { border-color: rgba( 224, 62, 62, 1 ); /* var(--BOX-RED-color) */ } @@ -448,7 +450,7 @@ background-color: rgba( 128, 128, 128, 1 ); /* var(--BOX-NEUTRAL-color) */ } - .btn.cstyle.transparent a { + .btn.cstyle.transparent > * { color: #4a4a4a; /* var(--MAIN-TITLES-TEXT-color) */ } diff --git a/static/css/theme-neon.css b/static/css/theme-neon.css index e2d8a36c55..2020b67dc7 100644 --- a/static/css/theme-neon.css +++ b/static/css/theme-neon.css @@ -208,6 +208,7 @@ body div.box { } body .btn, +body .btn > *, body div.box > .box-label { text-shadow: 0 0 1px #fff, @@ -224,14 +225,14 @@ body .btn.cstyle.transparent { --VARIABLE-BOX-BG-color: var(--INTERNAL-BOX-BG-color); } -body .btn.cstyle.transparent a { +body .btn.cstyle.transparent > * { border-color: var(--VARIABLE-BOX-color); color: var(--VARIABLE-BOX-CAPTION-color); } -body .btn.cstyle.transparent a:hover, -body .btn.cstyle.transparent a:focus, -body .btn.cstyle.transparent a:active { +body .btn.cstyle.transparent > *:hover, +body .btn.cstyle.transparent > *:focus, +body .btn.cstyle.transparent > *:active { background-color: var(--INTERNAL-MAIN-TITLES-TEXT-color); color: var(--INTERNAL-MAIN-TEXT-color); } diff --git a/static/css/theme.css b/static/css/theme.css index 67da36bec5..e870c5d951 100644 --- a/static/css/theme.css +++ b/static/css/theme.css @@ -86,18 +86,31 @@ th { margin-top: 1rem; } -.searchbox > i { +.searchbox > :first-child { color: rgba( 255, 255, 255, .8 ); position: absolute; left: 10px; - top: 6px; +} + +.searchbox > button { + -webkit-appearance: none; + appearance: none; + background-color: transparent; + border: 0; + margin: 0; + padding: 0; + top: 4px; +} + +.searchbox > i { + top: 7px; } .searchbox span { color: rgba( 255, 255, 255, .6 ); position: absolute; right: 10px; - top: 3px; + top: 4px; cursor: pointer; } @@ -116,10 +129,7 @@ th { font-weight: 300; } -.searchbox input:-ms-input-placeholder { - color: rgba( 255, 255, 255, .4 ); -} - +.searchbox input:-ms-input-placeholder, .searchbox input::placeholder { color: rgba( 255, 255, 255, .4 ); } @@ -1204,7 +1214,7 @@ option { display: inline-block; font-size: .9rem; font-weight: 500; - line-height: 1.42857143; + line-height: 1.1; margin-bottom: 0; touch-action: manipulation; -ms-user-select: none; @@ -1212,7 +1222,16 @@ option { user-select: none; } -.btn a { +.btn > :where(button) { + -webkit-appearance: none; + appearance: none; + border-width: 0; + margin: 0; + padding: 0; +} + +.btn > * { + background-color: transparent; border-radius: 4px; border-style: solid; border-width: 1px; @@ -1223,19 +1242,18 @@ option { -webkit-user-select: none; user-select: none; white-space: nowrap; +} + +#body #body-inner .btn > *.highlight:after { background-color: transparent; } -#body #body-inner .btn a.highlight:after { - background-color: transparent; -} - -.btn a:focus { +.btn > *:focus { outline: none; } -.btn a:hover, -.btn a:focus { +.btn > *:hover, +.btn > *:focus { text-decoration: none; } diff --git a/static/css/variant.css b/static/css/variant.css index 772ea4ead6..265c4d4317 100644 --- a/static/css/variant.css +++ b/static/css/variant.css @@ -366,14 +366,14 @@ pre .copy-to-clipboard-button:hover { background-color: var(--VARIABLE-BOX-color); } -.btn a { +.btn > * { border-color: var(--VARIABLE-BOX-color); color: var(--VARIABLE-BOX-CAPTION-color); } -.btn a:hover, -.btn a:focus, -.btn a:active { +.btn > *:hover, +.btn > *:focus, +.btn > *:active { background-color: var(--VARIABLE-BOX-BG-color); color: var(--VARIABLE-BOX-TEXT-color); } @@ -388,7 +388,7 @@ pre .copy-to-clipboard-button:hover { background-color: var(--INTERNAL-BOX-NEUTRAL-color); } -.btn.cstyle.transparent a { +.btn.cstyle.transparent > * { --VARIABLE-BOX-color: var(--INTERNAL-BOX-NEUTRAL-color); --VARIABLE-BOX-TEXT-color: var(--VARIABLE-BOX-CAPTION-color); } diff --git a/static/js/auto-complete.js b/static/js/auto-complete.js index 8db4a864fe..c098944358 100644 --- a/static/js/auto-complete.js +++ b/static/js/auto-complete.js @@ -1,8 +1,13 @@ /* - JavaScript autoComplete v1.0.4 + JavaScript autoComplete v1.0.4+ #46 - positioning #75 - complete McShelby/hugo-theme-relearn#155 + - sticky dropdown on scrolling + McShelby/hugo-theme-relearn#3xx + - don't empty search input if no data-val is given + - don't delete search term but close suggestions when suggestions are open + - delete search term when suggestions are closed Copyright (c) 2014 Simon Steinberger / Pixabay GitHub: https://github.com/Pixabay/JavaScript-autoComplete License: http://www.opensource.org/licenses/mit-license.php @@ -159,21 +164,44 @@ var autoComplete = (function(){ if (!sel) { next = (key == 40) ? that.sc.querySelector('.autocomplete-suggestion') : that.sc.childNodes[that.sc.childNodes.length - 1]; // first : last next.className += ' selected'; - that.value = next.getAttribute('data-val'); + if (next.getAttribute('data-val')) that.value = next.getAttribute('data-val'); } else { next = (key == 40) ? sel.nextSibling : sel.previousSibling; if (next) { sel.className = sel.className.replace('selected', ''); next.className += ' selected'; - that.value = next.getAttribute('data-val'); + if (next.getAttribute('data-val')) that.value = next.getAttribute('data-val'); + } + else { + sel.className = sel.className.replace('selected', ''); + that.value = that.last_val; + next = 0; } - else { sel.className = sel.className.replace('selected', ''); that.value = that.last_val; next = 0; } } that.updateSC(0, next); return false; } // esc - else if (key == 27) { that.value = that.last_val; that.sc.style.display = 'none'; } + else if (key == 27) { + if( that.sc.style.display != 'none' ){ + var sel = that.sc.querySelector('.autocomplete-suggestion.selected'); + if (sel) { + e.preventDefault(); + setTimeout(function(){ + that.value = that.last_val; + that.focus(); + that.sc.style.display = 'none'; + }, 0); + } + else{ + that.value = ''; + that.sc.style.display = 'none'; + } + } + else{ + that.value = ''; + } + } // enter else if (key == 13 || key == 9) { var sel = that.sc.querySelector('.autocomplete-suggestion.selected'); diff --git a/static/js/search.js b/static/js/search.js index bd412d1506..b48043ceff 100644 --- a/static/js/search.js +++ b/static/js/search.js @@ -122,6 +122,7 @@ $(function() { /* onSelect callback fires when a search suggestion is chosen */ onSelect: function(e, term, item) { location.href = item.getAttribute('data-uri'); + e.preventDefault(); } }); diff --git a/static/js/theme.js b/static/js/theme.js index aa557a6a1e..049d795fe4 100644 --- a/static/js/theme.js +++ b/static/js/theme.js @@ -21,6 +21,12 @@ Prism.manual = true; var psc; var psm; var pst; +var elc = document.querySelector('#body-inner'); + +function documentFocus(){ + document.querySelector( '#body-inner' ).focus(); + psc && psc.scrollbarY.focus(); +} function scrollbarWidth(){ // https://davidwalsh.name/detect-scrollbar-width @@ -35,6 +41,17 @@ function scrollbarWidth(){ return scrollbarWidth; } +var scrollbarSize = scrollbarWidth(); +function adjustContentWidth(){ + var left = parseFloat( getComputedStyle( elc ).getPropertyValue( 'padding-left' ) ); + var right = left; + if( elc.scrollHeight > elc.clientHeight ){ + // if we have a scrollbar reduce the right margin by the scrollbar width + right = Math.max( 0, left - scrollbarSize ); + } + elc.style[ 'padding-right' ] = '' + right + 'px'; +} + function switchTab(tabGroup, tabId) { var tabs = jQuery(".tab-panel").has("[data-tab-group='"+tabGroup+"'][data-tab-item='"+tabId+"']"); var allTabItems = tabs.find("[data-tab-group='"+tabGroup+"']"); @@ -371,7 +388,6 @@ function initMenuScrollbar(){ return; } - var elc = document.querySelector('#body-inner'); var elm = document.querySelector('#content-wrapper'); var elt = document.querySelector('#TableOfContents'); @@ -442,16 +458,6 @@ function initMenuScrollbar(){ }); // finally, we want to adjust the contents right padding if there is a scrollbar visible - var scrollbarSize = scrollbarWidth(); - function adjustContentWidth(){ - var left = parseFloat( getComputedStyle( elc ).getPropertyValue( 'padding-left' ) ); - var right = left; - if( elc.scrollHeight > elc.clientHeight ){ - // if we have a scrollbar reduce the right margin by the scrollbar width - right = Math.max( 0, left - scrollbarSize ); - } - elc.style[ 'padding-right' ] = '' + right + 'px'; - } window.addEventListener('resize', adjustContentWidth ); adjustContentWidth(); } @@ -461,8 +467,7 @@ function sidebarEscapeHandler( event ){ var b = document.querySelector( 'body' ); b.classList.remove( 'sidebar-flyout' ); document.removeEventListener( 'keydown', sidebarEscapeHandler ); - document.querySelector( '#body-inner' ).focus(); - psc && psc.scrollbarY.focus(); + documentFocus(); } } @@ -471,8 +476,7 @@ function tocEscapeHandler( event ){ var b = document.querySelector( 'body' ); b.classList.remove( 'toc-flyout' ); document.removeEventListener( 'keydown', tocEscapeHandler ); - document.querySelector( '#body-inner' ).focus(); - psc && psc.scrollbarY.focus(); + documentFocus(); } } @@ -482,6 +486,12 @@ function sidebarShortcutHandler( event ){ } } +function searchShortcutHandler( event ){ + if( !event.shiftKey && event.altKey && event.ctrlKey && !event.metaKey && event.which == 70 /* f */ ){ + showSearch(); + } +} + function tocShortcutHandler( event ){ if( !event.shiftKey && event.altKey && event.ctrlKey && !event.metaKey && event.which == 84 /* t */ ){ showToc(); @@ -500,6 +510,22 @@ function printShortcutHandler( event ){ } } +function showSearch(){ + var s = document.querySelector( '#search-by' ); + var b = document.querySelector( 'body' ); + if( s == document.activeElement ){ + if( b.classList.contains( 'sidebar-flyout' ) ){ + showNav(); + } + documentFocus(); + } else { + if( !b.classList.contains( 'sidebar-flyout' ) ){ + showNav(); + } + s.focus(); + } +} + function showNav(){ if( !document.querySelector( '#sidebar-overlay' ) ){ // we may not have a flyout @@ -514,8 +540,7 @@ function showNav(){ } else{ document.removeEventListener( 'keydown', sidebarEscapeHandler ); - document.querySelector( '#body-inner' ).focus(); - psc && psc.scrollbarY.focus(); + documentFocus(); } } @@ -533,8 +558,7 @@ function showToc(){ } else{ document.removeEventListener( 'keydown', tocEscapeHandler ); - document.querySelector( '#body-inner' ).focus(); - psc && psc.scrollbarY.focus(); + documentFocus(); } } @@ -560,15 +584,8 @@ function initToc(){ document.addEventListener( 'keydown', editShortcutHandler ); document.addEventListener( 'keydown', printShortcutHandler ); document.addEventListener( 'keydown', sidebarShortcutHandler ); + document.addEventListener( 'keydown', searchShortcutHandler ); document.addEventListener( 'keydown', tocShortcutHandler ); - // avoid keyboard navigation for input fields - jQuery(formelements).keydown(function (e) { - if( !e.shiftKey && e.altKey && e.ctrlKey && !e.metaKey){ - if( e.which == 78 /* n */ || e.which == 84 /* t */ || e.which == 87 /* w */ || e.which == 80 /* p */ ){ - e.stopPropagation(); - } - } - }); document.querySelector( '#sidebar-overlay' ).addEventListener( 'click', showNav ); document.querySelector( '#sidebar-toggle' ).addEventListener( 'click', showNav ); @@ -582,8 +599,7 @@ function initToc(){ } // finally give initial focus to allow keyboard scrolling in FF - document.querySelector( '#body-inner' ).focus(); - psc && psc.scrollbarY.focus(); + documentFocus(); } function initSwipeHandler(){ @@ -613,8 +629,7 @@ function initSwipeHandler(){ var b = document.querySelector( 'body' ); b.classList.remove( 'sidebar-flyout' ); document.removeEventListener( 'keydown', sidebarEscapeHandler ); - document.querySelector( '#body-inner' ).focus(); - psc && psc.scrollbarY.focus(); + documentFocus(); } } return false; @@ -721,16 +736,29 @@ function unmark(){ psm && psm.update(); } +function searchInputHandler(value) { + unmark(); + if (value.length) { + sessionStorage.setItem(baseUriFull+'search-value', value); + mark(); + } +} + function initSearch() { + jQuery('[data-search-input]').on('keydown', function(event) { + if (event.key == "Escape") { + var input = jQuery(this); + input.blur(); + searchInputHandler( '' ); + documentFocus(); + } + }); jQuery('[data-search-input]').on('input', function() { var input = jQuery(this); var value = input.val(); - unmark(); - if (value.length) { - sessionStorage.setItem(baseUriFull+'search-value', value); - mark(); - } + searchInputHandler( value ); }); + jQuery('[data-search-clear]').on('click', function() { jQuery('[data-search-input]').val('').trigger('input'); unmark();