diff --git a/cmd/tools/vdoc/theme/doc.css b/cmd/tools/vdoc/theme/doc.css index 151aa3872b..1df1fbfd85 100644 --- a/cmd/tools/vdoc/theme/doc.css +++ b/cmd/tools/vdoc/theme/doc.css @@ -197,9 +197,14 @@ body { width: 1.2rem; height: 1.2rem; } -.doc-nav > .heading-container > .heading > #search { +.doc-nav #search { + position: relative; margin: 0.6rem 1.2rem 1rem 1.2rem; + display: flex; +} +.doc-nav #search input { border: none; + width: 100%; border-radius: 0.2rem; padding: 0.5rem 1rem; outline: none; @@ -208,17 +213,32 @@ body { color: #fff; color: var(--menu-search-text-color); } -.doc-nav > .heading-container > .heading > #search::placeholder { +.doc-nav #search input::placeholder { color: #edf2f7; text-transform: uppercase; font-size: 12px; font-weight: 600; } -.doc-nav > .heading-container > .heading > #search:-ms-input-placeholder { - color: #edf2f7; - text-transform: uppercase; - font-size: 12px; - font-weight: 600; +.doc-nav #search-keys { + position: absolute; + height: 100%; + align-items: center; + display: flex; + top: 0; + right: 0.75rem; + opacity: 0.33; + transition: opacity 0.1s; +} +.doc-nav #search-keys.hide { + opacity: 0; +} +.doc-nav #search-keys kbd { + padding: 2.5px 3px; + margin-left: 1px; + font-size: 11px; + background-color: var(--menu-background-color); + border: 1px solid #ffffff44; + border-radius: 3px; } .doc-nav > .content { padding: 0 2rem 2rem 2rem; @@ -278,7 +298,8 @@ body { .doc-nav .search li { line-height: 1.5; } -.doc-nav > .search .result:hover { +.doc-nav > .search .result:hover, +.doc-nav > .search .result.selected { background-color: #00000021; background-color: var(--menu-search-result-background-hover-color); } diff --git a/cmd/tools/vdoc/theme/doc.js b/cmd/tools/vdoc/theme/doc.js index 118cec6a01..79480d76df 100644 --- a/cmd/tools/vdoc/theme/doc.js +++ b/cmd/tools/vdoc/theme/doc.js @@ -145,6 +145,72 @@ function setupSearch() { } }); searchInput.addEventListener('input', onInputChange); + setupSearchKeymaps(); +} + +function setupSearchKeymaps() { + const searchInput = document.querySelector('#search input'); + // Keyboard shortcut indicator + const searchKeys = document.createElement('div'); + const modifierKeyPrefix = navigator.platform.includes('Mac') ? '⌘' : 'Ctrl'; + searchKeys.setAttribute('id', 'search-keys'); + searchKeys.innerHTML = '' + modifierKeyPrefix + 'k'; + searchInput.parentElement?.appendChild(searchKeys); + searchInput.addEventListener('focus', () => searchKeys.classList.add('hide')); + searchInput.addEventListener('blur', () => searchKeys.classList.remove('hide')); + // Global shortcuts to focus searchInput + document.addEventListener('keydown', (ev) => { + if (ev.key === '/' || ((ev.ctrlKey || ev.metaKey) && ev.key === 'k')) { + ev.preventDefault(); + searchInput.focus(); + } + }); + // Shortcuts while searchInput is focused + let selectedIdx = -1; + function selectResult(results, newIdx) { + if (selectedIdx !== -1) { + results[selectedIdx].classList.remove('selected'); + } + results[newIdx].classList.add('selected'); + results[newIdx].scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' }); + selectedIdx = newIdx; + } + searchInput.addEventListener('keydown', (ev) => { + const searchResults = document.querySelectorAll('.search .result'); + switch (ev.key) { + case 'Escape': + searchInput.blur(); + break; + case 'Enter': + if (!searchResults.length || selectedIdx === -1) break; + searchResults[selectedIdx].querySelector('a').click(); + break; + case 'ArrowDown': + ev.preventDefault(); + if (!searchResults.length) break; + if (selectedIdx >= searchResults.length - 1) { + // Cycle to first if last is selected + selectResult(searchResults, 0); + } else { + // Select next + selectResult(searchResults, selectedIdx + 1); + } + break; + case 'ArrowUp': + ev.preventDefault(); + if (!searchResults.length) break; + if (selectedIdx <= 0) { + // Cycle to last if first is selected (or select it if none is selcted yet) + selectResult(searchResults, searchResults.length - 1); + } else { + // Select previous + selectResult(searchResults, selectedIdx - 1); + } + break; + default: + selectedIdx = -1; + } + }); } function createSearchResult(data) { @@ -194,11 +260,3 @@ function debounce(func, timeout) { timer = setTimeout(next, timeout > 0 ? timeout : 300); }; } - -document.addEventListener('keypress', (ev) => { - if (ev.key == '/') { - const search = document.getElementById('search'); - ev.preventDefault(); - search.focus(); - } -}); diff --git a/cmd/tools/vdoc/theme/index.html b/cmd/tools/vdoc/theme/index.html index afa74077ee..9b9b3ef47d 100644 --- a/cmd/tools/vdoc/theme/index.html +++ b/cmd/tools/vdoc/theme/index.html @@ -46,7 +46,9 @@ {{ menu_icon }} - +