Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-docs.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSuzanne Selhorn <sselhorn@gitlab.com>2023-05-05 22:37:23 +0300
committerSuzanne Selhorn <sselhorn@gitlab.com>2023-05-05 22:37:23 +0300
commitc9f3fda122fb5ee39fe3fff546d52cae008f24da (patch)
treef7d6bbd83b516060cb02d3679a2563697369d6ff
parent0a602766667fc93591571f37f63b9bc03d568fd3 (diff)
parent3e376ccdfca9bd4d36388cdcdf865289365369ae (diff)
Merge branch 'gps-keyboard-nav' into 'main'
Add keyboard shortcuts for search Closes #1562 See merge request https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/3810 Merged-by: Suzanne Selhorn <sselhorn@gitlab.com> Approved-by: David O'Regan <doregan@gitlab.com> Co-authored-by: Sarah German <sgerman@gitlab.com>
-rw-r--r--content/assets/stylesheets/_variables.scss3
-rw-r--r--content/assets/stylesheets/stylesheet.scss13
-rw-r--r--content/frontend/search/components/google_search_form.vue89
-rw-r--r--content/frontend/search/google.js8
4 files changed, 93 insertions, 20 deletions
diff --git a/content/assets/stylesheets/_variables.scss b/content/assets/stylesheets/_variables.scss
index 23c37e82..b16a44cf 100644
--- a/content/assets/stylesheets/_variables.scss
+++ b/content/assets/stylesheets/_variables.scss
@@ -154,9 +154,6 @@ $help-gray-100: #dbdbdb;
$help-gray-200: #bfbfbf;
$help-gray-900: #303030;
-// Other colors
-$search-border: rgba(0, 0, 0, 0.25);
-
// Social colors
$youtube: #f00;
diff --git a/content/assets/stylesheets/stylesheet.scss b/content/assets/stylesheets/stylesheet.scss
index 59fd666f..b6b30bad 100644
--- a/content/assets/stylesheets/stylesheet.scss
+++ b/content/assets/stylesheets/stylesheet.scss
@@ -329,10 +329,21 @@ ol {
.gl-search-box-by-type-input-borderless:not(.form-control-plaintext):focus {
box-shadow: inset 0 0 0 2px $blue-400;
}
-.gs-results a:hover {
+.gs-wrapper kbd {
+ display: none;
+ @media (min-width: $bp-md) {
+ display: inline;
+ top: 0.25rem;
+ right: 0.3rem;
+ font-size: 0.75rem;
+ }
+}
+.gs-results a:hover,
+.gs-results a:focus {
text-decoration: none;
background-color: $gray-50;
color: $gray-700;
+ outline: none;
}
.gl-search-box-by-click {
width: $search-lg-width;
diff --git a/content/frontend/search/components/google_search_form.vue b/content/frontend/search/components/google_search_form.vue
index 6f9f6f82..1f941685 100644
--- a/content/frontend/search/components/google_search_form.vue
+++ b/content/frontend/search/components/google_search_form.vue
@@ -1,5 +1,10 @@
<script>
-import { GlSearchBoxByType, GlLink, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import {
+ GlSearchBoxByType,
+ GlLink,
+ GlSafeHtmlDirective as SafeHtml,
+ GlTooltipDirective as GlTooltip,
+} from '@gitlab/ui';
import { debounce } from 'lodash';
import { directive as clickOutside } from 'v-click-outside';
import { fetchResults, MAX_RESULTS_PER_PAGE } from '../../services/google_search_api';
@@ -12,6 +17,7 @@ export default {
directives: {
clickOutside,
SafeHtml,
+ GlTooltip,
},
props: {
borderless: {
@@ -28,6 +34,8 @@ export default {
showResultPanel: false,
submitted: false,
totalCount: 0,
+ activeLink: -1,
+ showTooltip: true,
};
},
computed: {
@@ -40,6 +48,9 @@ export default {
},
watch: {
searchQuery() {
+ this.showTooltip = this.searchQuery.length === 0;
+ this.submitted = false;
+ this.moreResultsPath = `/search/?q=${encodeURI(this.searchQuery)}`;
this.debouncedGetResults();
},
},
@@ -49,7 +60,6 @@ export default {
methods: {
async getResults() {
this.showResultPanel = false;
-
this.isLoading = true;
const response = await fetchResults(this.searchQuery, [], 1, 10);
this.isLoading = false;
@@ -59,13 +69,43 @@ export default {
? response.searchInformation.totalCount
: 0;
this.results = response.items ? response.items : [];
- this.moreResultsPath = `/search/?q=${encodeURI(this.searchQuery)}`;
this.submitted = true;
this.showResultPanel = true;
},
showAllResults() {
// Sends the user to the advanced search page if they hit Enter.
- window.location.href = this.moreResultsPath;
+ if (this.searchQuery) {
+ window.location.href = this.moreResultsPath;
+ }
+ },
+ keyboardNav(e) {
+ const isArrowUp = e.key === 'ArrowUp';
+ const isArrowDown = e.key === 'ArrowDown';
+ const searchBox = document.querySelector('input[type=search]');
+
+ if (isArrowUp || isArrowDown) {
+ const activeIndex = this.activeLink + (isArrowUp ? -1 : 1);
+
+ // If we're at the top or bottom of the list, go back to the search box.
+ if (activeIndex < 0 || activeIndex > this.results.length) {
+ this.activeLink = -1;
+ searchBox.focus();
+ // Reset the value after focus so that the cursor is at the end of the text.
+ searchBox.value = this.searchQuery;
+ return;
+ }
+ // Otherwise, select the previous or next link.
+ this.setActiveResult(document.querySelector(`[data-link-index="${activeIndex}"]`));
+ }
+ },
+ setActiveResult(result) {
+ result.focus();
+ this.activeLink = Number(result.dataset.linkIndex);
+ },
+ deactivate() {
+ this.showResultPanel = false;
+ this.activeLink = -1;
+ this.showTooltip = this.searchQuery.length === 0;
},
},
};
@@ -73,31 +113,48 @@ export default {
<template>
<div
- v-click-outside="() => (showResultPanel = false)"
+ v-click-outside="() => deactivate()"
class="gs-wrapper gl-m-auto gl-my-3 gl-md-mt-0 gl-md-mb-0"
+ @keydown.arrow-down.prevent="keyboardNav"
+ @keydown.arrow-up.prevent="keyboardNav"
+ @keydown.escape="deactivate()"
>
- <gl-search-box-by-type
- v-model="searchQuery"
- :is-loading="isLoading"
- :borderless="borderless"
- placeholder=""
- @focus="showResultPanel = true"
- @keyup.enter="showAllResults()"
- />
+ <form class="gl-relative">
+ <gl-search-box-by-type
+ v-model="searchQuery"
+ :is-loading="isLoading"
+ :borderless="borderless"
+ placeholder=""
+ autocomplete="off"
+ aria-label="Search"
+ @focus="showResultPanel = true"
+ @keydown.enter.prevent="showAllResults()"
+ />
+ <kbd
+ v-show="showTooltip && !isLoading"
+ v-gl-tooltip.bottom.hover.html
+ class="gl-absolute gl-z-index-1 gl-bg-gray-100 gl-text-gray-700"
+ title="Use the shortcut key <kbd>/</kbd> to start a search"
+ >/</kbd
+ >
+ </form>
+
<div
- v-if="showResultPanel && submitted"
+ v-show="showResultPanel && submitted"
class="gs-results gl-absolute gl-z-index-200 gl-bg-white gl-rounded gl-px-3 gl-shadow"
>
- <ul v-if="results.length" data-testid="search-results" class="gl-pl-0 gl-mb-0">
- <li v-for="result in results" :key="result.cacheId" class="gl-list-style-none">
+ <ul v-show="results.length" data-testid="search-results" class="gl-pl-0 gl-mb-3 gl-pt-3">
+ <li v-for="(result, index) in results" :key="result.cacheId" class="gl-list-style-none">
<gl-link
v-safe-html="result.formattedTitle"
:href="result.relativeLink"
+ :data-link-index="index"
class="gl-text-gray-700 gl-py-3 gl-px-2 gl-display-block gl-text-left"
/>
</li>
<li v-if="hasMoreResults" class="gl-list-style-none gl-border-t gl-my-2 gl-py-2">
<gl-link
+ :data-link-index="results.length"
data-testid="more-results"
:href="moreResultsPath"
class="gl-text-gray-700 gl-py-3 gl-pb-2 gl-px-2 gl-display-block gl-text-left"
diff --git a/content/frontend/search/google.js b/content/frontend/search/google.js
index 88e450dd..bfb30a77 100644
--- a/content/frontend/search/google.js
+++ b/content/frontend/search/google.js
@@ -26,3 +26,11 @@ document.addEventListener('DOMContentLoaded', () => {
// Lunr.js
mountVue('.js-search-form', SearchForm);
});
+
+// Keyboard shortcut: forward slash focuses on search forms
+document.addEventListener('keyup', (e) => {
+ if (e.key !== '/' || e.ctrlKey || e.metaKey) return;
+ if (/^(?:input|textarea|select|button)$/i.test(e.target.tagName)) return;
+ e.preventDefault();
+ document.querySelector('input[type="search"]').focus();
+});