Skip to content

Commit 64029a2

Browse files
authored
vdoc: implement keyboard shortcuts for search navigation (#19088)
1 parent b7afe6b commit 64029a2

3 files changed

Lines changed: 98 additions & 18 deletions

File tree

cmd/tools/vdoc/theme/doc.css

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,14 @@ body {
197197
width: 1.2rem;
198198
height: 1.2rem;
199199
}
200-
.doc-nav > .heading-container > .heading > #search {
200+
.doc-nav #search {
201+
position: relative;
201202
margin: 0.6rem 1.2rem 1rem 1.2rem;
203+
display: flex;
204+
}
205+
.doc-nav #search input {
202206
border: none;
207+
width: 100%;
203208
border-radius: 0.2rem;
204209
padding: 0.5rem 1rem;
205210
outline: none;
@@ -208,17 +213,32 @@ body {
208213
color: #fff;
209214
color: var(--menu-search-text-color);
210215
}
211-
.doc-nav > .heading-container > .heading > #search::placeholder {
216+
.doc-nav #search input::placeholder {
212217
color: #edf2f7;
213218
text-transform: uppercase;
214219
font-size: 12px;
215220
font-weight: 600;
216221
}
217-
.doc-nav > .heading-container > .heading > #search:-ms-input-placeholder {
218-
color: #edf2f7;
219-
text-transform: uppercase;
220-
font-size: 12px;
221-
font-weight: 600;
222+
.doc-nav #search-keys {
223+
position: absolute;
224+
height: 100%;
225+
align-items: center;
226+
display: flex;
227+
top: 0;
228+
right: 0.75rem;
229+
opacity: 0.33;
230+
transition: opacity 0.1s;
231+
}
232+
.doc-nav #search-keys.hide {
233+
opacity: 0;
234+
}
235+
.doc-nav #search-keys kbd {
236+
padding: 2.5px 3px;
237+
margin-left: 1px;
238+
font-size: 11px;
239+
background-color: var(--menu-background-color);
240+
border: 1px solid #ffffff44;
241+
border-radius: 3px;
222242
}
223243
.doc-nav > .content {
224244
padding: 0 2rem 2rem 2rem;
@@ -278,7 +298,8 @@ body {
278298
.doc-nav .search li {
279299
line-height: 1.5;
280300
}
281-
.doc-nav > .search .result:hover {
301+
.doc-nav > .search .result:hover,
302+
.doc-nav > .search .result.selected {
282303
background-color: #00000021;
283304
background-color: var(--menu-search-result-background-hover-color);
284305
}

cmd/tools/vdoc/theme/doc.js

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ function setupMobileToggle() {
4949
const isHidden = docNav.classList.contains('hidden');
5050
docNav.classList.toggle('hidden');
5151
const search = docNav.querySelector('.search');
52-
// console.log(search);
5352
const searchHasResults = search.classList.contains('has-results');
5453
if (isHidden && searchHasResults) {
5554
search.classList.remove('mobile-hidden');
@@ -145,6 +144,72 @@ function setupSearch() {
145144
}
146145
});
147146
searchInput.addEventListener('input', onInputChange);
147+
setupSearchKeymaps();
148+
}
149+
150+
function setupSearchKeymaps() {
151+
const searchInput = document.querySelector('#search input');
152+
// Keyboard shortcut indicator
153+
const searchKeys = document.createElement('div');
154+
const modifierKeyPrefix = navigator.platform.includes('Mac') ? '⌘' : 'Ctrl';
155+
searchKeys.setAttribute('id', 'search-keys');
156+
searchKeys.innerHTML = '<kbd>' + modifierKeyPrefix + '</kbd><kbd>k</kbd>';
157+
searchInput.parentElement?.appendChild(searchKeys);
158+
searchInput.addEventListener('focus', () => searchKeys.classList.add('hide'));
159+
searchInput.addEventListener('blur', () => searchKeys.classList.remove('hide'));
160+
// Global shortcuts to focus searchInput
161+
document.addEventListener('keydown', (ev) => {
162+
if (ev.key === '/' || ((ev.ctrlKey || ev.metaKey) && ev.key === 'k')) {
163+
ev.preventDefault();
164+
searchInput.focus();
165+
}
166+
});
167+
// Shortcuts while searchInput is focused
168+
let selectedIdx = -1;
169+
function selectResult(results, newIdx) {
170+
if (selectedIdx !== -1) {
171+
results[selectedIdx].classList.remove('selected');
172+
}
173+
results[newIdx].classList.add('selected');
174+
results[newIdx].scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' });
175+
selectedIdx = newIdx;
176+
}
177+
searchInput.addEventListener('keydown', (ev) => {
178+
const searchResults = document.querySelectorAll('.search .result');
179+
switch (ev.key) {
180+
case 'Escape':
181+
searchInput.blur();
182+
break;
183+
case 'Enter':
184+
if (!searchResults.length || selectedIdx === -1) break;
185+
searchResults[selectedIdx].querySelector('a').click();
186+
break;
187+
case 'ArrowDown':
188+
ev.preventDefault();
189+
if (!searchResults.length) break;
190+
if (selectedIdx >= searchResults.length - 1) {
191+
// Cycle to first if last is selected
192+
selectResult(searchResults, 0);
193+
} else {
194+
// Select next
195+
selectResult(searchResults, selectedIdx + 1);
196+
}
197+
break;
198+
case 'ArrowUp':
199+
ev.preventDefault();
200+
if (!searchResults.length) break;
201+
if (selectedIdx <= 0) {
202+
// Cycle to last if first is selected (or select it if none is selcted yet)
203+
selectResult(searchResults, searchResults.length - 1);
204+
} else {
205+
// Select previous
206+
selectResult(searchResults, selectedIdx - 1);
207+
}
208+
break;
209+
default:
210+
selectedIdx = -1;
211+
}
212+
});
148213
}
149214

150215
function createSearchResult(data) {
@@ -194,11 +259,3 @@ function debounce(func, timeout) {
194259
timer = setTimeout(next, timeout > 0 ? timeout : 300);
195260
};
196261
}
197-
198-
document.addEventListener('keypress', (ev) => {
199-
if (ev.key == '/') {
200-
const search = document.getElementById('search');
201-
ev.preventDefault();
202-
search.focus();
203-
}
204-
});

cmd/tools/vdoc/theme/index.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@
4646
</div>
4747
{{ menu_icon }}
4848
</div>
49-
<input type="text" id="search" placeholder="Search... (beta)" autocomplete="off" />
49+
<div id="search">
50+
<input type="text" placeholder="Search... (beta)" autocomplete="off" />
51+
</div>
5052
</div>
5153
</div>
5254
<nav class="search hidden"></nav>

0 commit comments

Comments
 (0)