2021-11-11 11:23:49 +05:30
|
|
|
<script>
|
2022-08-13 15:12:31 +05:30
|
|
|
import {
|
|
|
|
GlSearchBoxByType,
|
|
|
|
GlOutsideDirective as Outside,
|
|
|
|
GlIcon,
|
|
|
|
GlToken,
|
|
|
|
GlTooltipDirective,
|
|
|
|
GlResizeObserverDirective,
|
|
|
|
} from '@gitlab/ui';
|
2021-11-11 11:23:49 +05:30
|
|
|
import { mapState, mapActions, mapGetters } from 'vuex';
|
2022-01-26 12:08:38 +05:30
|
|
|
import { debounce } from 'lodash';
|
2021-11-11 11:23:49 +05:30
|
|
|
import { visitUrl } from '~/lib/utils/url_utility';
|
2022-08-13 15:12:31 +05:30
|
|
|
import { truncate } from '~/lib/utils/text_utility';
|
2022-01-26 12:08:38 +05:30
|
|
|
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
|
|
|
|
import { s__, sprintf } from '~/locale';
|
2022-11-25 23:54:43 +05:30
|
|
|
import Tracking from '~/tracking';
|
2022-01-26 12:08:38 +05:30
|
|
|
import DropdownKeyboardNavigation from '~/vue_shared/components/dropdown_keyboard_navigation.vue';
|
|
|
|
import {
|
|
|
|
FIRST_DROPDOWN_INDEX,
|
|
|
|
SEARCH_BOX_INDEX,
|
|
|
|
SEARCH_INPUT_DESCRIPTION,
|
|
|
|
SEARCH_RESULTS_DESCRIPTION,
|
2022-06-21 17:19:12 +05:30
|
|
|
SEARCH_SHORTCUTS_MIN_CHARACTERS,
|
2022-08-13 15:12:31 +05:30
|
|
|
SCOPE_TOKEN_MAX_LENGTH,
|
|
|
|
INPUT_FIELD_PADDING,
|
2022-08-27 11:52:29 +05:30
|
|
|
IS_SEARCHING,
|
|
|
|
IS_FOCUSED,
|
|
|
|
IS_NOT_FOCUSED,
|
2022-01-26 12:08:38 +05:30
|
|
|
} from '../constants';
|
2021-11-18 22:05:49 +05:30
|
|
|
import HeaderSearchAutocompleteItems from './header_search_autocomplete_items.vue';
|
2021-11-11 11:23:49 +05:30
|
|
|
import HeaderSearchDefaultItems from './header_search_default_items.vue';
|
|
|
|
import HeaderSearchScopedItems from './header_search_scoped_items.vue';
|
|
|
|
|
|
|
|
export default {
|
|
|
|
name: 'HeaderSearchApp',
|
|
|
|
i18n: {
|
2022-03-02 08:16:31 +05:30
|
|
|
searchGitlab: s__('GlobalSearch|Search GitLab'),
|
2022-01-26 12:08:38 +05:30
|
|
|
searchInputDescribeByNoDropdown: s__(
|
|
|
|
'GlobalSearch|Type and press the enter key to submit search.',
|
|
|
|
),
|
|
|
|
searchInputDescribeByWithDropdown: s__(
|
|
|
|
'GlobalSearch|Type for new suggestions to appear below.',
|
|
|
|
),
|
|
|
|
searchDescribedByDefault: s__(
|
|
|
|
'GlobalSearch|%{count} default results provided. Use the up and down arrow keys to navigate search results list.',
|
|
|
|
),
|
|
|
|
searchDescribedByUpdated: s__(
|
|
|
|
'GlobalSearch|Results updated. %{count} results available. Use the up and down arrow keys to navigate search results list, or ENTER to submit.',
|
|
|
|
),
|
|
|
|
searchResultsLoading: s__('GlobalSearch|Search results are loading'),
|
2022-08-13 15:12:31 +05:30
|
|
|
searchResultsScope: s__('GlobalSearch|in %{scope}'),
|
|
|
|
kbdHelp: sprintf(
|
|
|
|
s__('GlobalSearch|Use the shortcut key %{kbdOpen}/%{kbdClose} to start a search'),
|
|
|
|
{ kbdOpen: '<kbd>', kbdClose: '</kbd>' },
|
|
|
|
false,
|
|
|
|
),
|
2021-11-11 11:23:49 +05:30
|
|
|
},
|
2023-03-04 22:38:38 +05:30
|
|
|
directives: { Outside, GlTooltip: GlTooltipDirective, GlResizeObserverDirective },
|
2021-11-11 11:23:49 +05:30
|
|
|
components: {
|
|
|
|
GlSearchBoxByType,
|
|
|
|
HeaderSearchDefaultItems,
|
|
|
|
HeaderSearchScopedItems,
|
2021-11-18 22:05:49 +05:30
|
|
|
HeaderSearchAutocompleteItems,
|
2022-01-26 12:08:38 +05:30
|
|
|
DropdownKeyboardNavigation,
|
2022-08-13 15:12:31 +05:30
|
|
|
GlIcon,
|
|
|
|
GlToken,
|
2021-11-11 11:23:49 +05:30
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
showDropdown: false,
|
2022-08-27 11:52:29 +05:30
|
|
|
isFocused: false,
|
2022-01-26 12:08:38 +05:30
|
|
|
currentFocusIndex: SEARCH_BOX_INDEX,
|
2021-11-11 11:23:49 +05:30
|
|
|
};
|
|
|
|
},
|
|
|
|
computed: {
|
2022-08-13 15:12:31 +05:30
|
|
|
...mapState(['search', 'loading', 'searchContext']),
|
|
|
|
...mapGetters(['searchQuery', 'searchOptions']),
|
2021-11-11 11:23:49 +05:30
|
|
|
searchText: {
|
|
|
|
get() {
|
|
|
|
return this.search;
|
|
|
|
},
|
|
|
|
set(value) {
|
|
|
|
this.setSearch(value);
|
|
|
|
},
|
|
|
|
},
|
2022-01-26 12:08:38 +05:30
|
|
|
currentFocusedOption() {
|
|
|
|
return this.searchOptions[this.currentFocusIndex];
|
|
|
|
},
|
|
|
|
currentFocusedId() {
|
|
|
|
return this.currentFocusedOption?.html_id;
|
|
|
|
},
|
|
|
|
isLoggedIn() {
|
2022-06-21 17:19:12 +05:30
|
|
|
return Boolean(gon?.current_username);
|
2022-01-26 12:08:38 +05:30
|
|
|
},
|
2021-11-11 11:23:49 +05:30
|
|
|
showSearchDropdown() {
|
2022-08-13 15:12:31 +05:30
|
|
|
if (!this.showDropdown || !this.isLoggedIn) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return this.searchOptions?.length > 0;
|
2021-11-11 11:23:49 +05:30
|
|
|
},
|
|
|
|
showDefaultItems() {
|
|
|
|
return !this.searchText;
|
|
|
|
},
|
2022-08-27 11:52:29 +05:30
|
|
|
searchTermOverMin() {
|
2022-08-13 15:12:31 +05:30
|
|
|
return this.searchText?.length > SEARCH_SHORTCUTS_MIN_CHARACTERS;
|
2022-06-21 17:19:12 +05:30
|
|
|
},
|
2022-01-26 12:08:38 +05:30
|
|
|
defaultIndex() {
|
|
|
|
if (this.showDefaultItems) {
|
|
|
|
return SEARCH_BOX_INDEX;
|
|
|
|
}
|
|
|
|
return FIRST_DROPDOWN_INDEX;
|
|
|
|
},
|
2022-08-13 15:12:31 +05:30
|
|
|
|
2022-01-26 12:08:38 +05:30
|
|
|
searchInputDescribeBy() {
|
|
|
|
if (this.isLoggedIn) {
|
|
|
|
return this.$options.i18n.searchInputDescribeByWithDropdown;
|
|
|
|
}
|
|
|
|
return this.$options.i18n.searchInputDescribeByNoDropdown;
|
|
|
|
},
|
|
|
|
dropdownResultsDescription() {
|
|
|
|
if (!this.showSearchDropdown) {
|
|
|
|
return ''; // This allows aria-live to see register an update when the dropdown is shown
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.showDefaultItems) {
|
|
|
|
return sprintf(this.$options.i18n.searchDescribedByDefault, {
|
|
|
|
count: this.searchOptions.length,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.loading
|
|
|
|
? this.$options.i18n.searchResultsLoading
|
|
|
|
: sprintf(this.$options.i18n.searchDescribedByUpdated, {
|
|
|
|
count: this.searchOptions.length,
|
|
|
|
});
|
|
|
|
},
|
2022-08-27 11:52:29 +05:30
|
|
|
searchBarClasses() {
|
|
|
|
return {
|
|
|
|
[IS_SEARCHING]: this.searchTermOverMin,
|
|
|
|
[IS_FOCUSED]: this.isFocused,
|
|
|
|
[IS_NOT_FOCUSED]: !this.isFocused,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
showScopeHelp() {
|
|
|
|
return this.searchTermOverMin && this.isFocused;
|
2022-08-13 15:12:31 +05:30
|
|
|
},
|
|
|
|
searchBarItem() {
|
|
|
|
return this.searchOptions?.[0];
|
|
|
|
},
|
|
|
|
infieldHelpContent() {
|
|
|
|
return this.searchBarItem?.scope || this.searchBarItem?.description;
|
|
|
|
},
|
|
|
|
infieldHelpIcon() {
|
|
|
|
return this.searchBarItem?.icon;
|
|
|
|
},
|
|
|
|
scopeTokenTitle() {
|
|
|
|
return sprintf(this.$options.i18n.searchResultsScope, {
|
|
|
|
scope: this.infieldHelpContent,
|
|
|
|
});
|
2022-06-21 17:19:12 +05:30
|
|
|
},
|
2021-11-11 11:23:49 +05:30
|
|
|
},
|
|
|
|
methods: {
|
2022-01-26 12:08:38 +05:30
|
|
|
...mapActions(['setSearch', 'fetchAutocompleteOptions', 'clearAutocomplete']),
|
2021-11-11 11:23:49 +05:30
|
|
|
openDropdown() {
|
|
|
|
this.showDropdown = true;
|
2022-11-25 23:54:43 +05:30
|
|
|
|
|
|
|
// check isFocused state to avoid firing duplicate events
|
|
|
|
if (!this.isFocused) {
|
|
|
|
this.isFocused = true;
|
|
|
|
this.$emit('expandSearchBar', true);
|
|
|
|
|
|
|
|
Tracking.event(undefined, 'focus_input', {
|
|
|
|
label: 'global_search',
|
|
|
|
property: 'top_navigation',
|
|
|
|
});
|
|
|
|
}
|
2021-11-11 11:23:49 +05:30
|
|
|
},
|
|
|
|
closeDropdown() {
|
|
|
|
this.showDropdown = false;
|
2022-08-27 11:52:29 +05:30
|
|
|
},
|
|
|
|
collapseAndCloseSearchBar() {
|
|
|
|
// we need a delay on this method
|
|
|
|
// for the search bar not to remove
|
|
|
|
// the clear button from dom
|
|
|
|
// and register clicks on dropdown items
|
|
|
|
setTimeout(() => {
|
|
|
|
this.showDropdown = false;
|
|
|
|
this.isFocused = false;
|
|
|
|
this.$emit('collapseSearchBar');
|
2022-11-25 23:54:43 +05:30
|
|
|
|
|
|
|
Tracking.event(undefined, 'blur_input', {
|
|
|
|
label: 'global_search',
|
|
|
|
property: 'top_navigation',
|
|
|
|
});
|
2022-08-27 11:52:29 +05:30
|
|
|
}, 200);
|
2021-11-11 11:23:49 +05:30
|
|
|
},
|
|
|
|
submitSearch() {
|
2022-08-13 15:12:31 +05:30
|
|
|
if (this.search?.length <= SEARCH_SHORTCUTS_MIN_CHARACTERS && this.currentFocusIndex < 0) {
|
|
|
|
return null;
|
|
|
|
}
|
2022-01-26 12:08:38 +05:30
|
|
|
return visitUrl(this.currentFocusedOption?.url || this.searchQuery);
|
2021-11-11 11:23:49 +05:30
|
|
|
},
|
2022-01-26 12:08:38 +05:30
|
|
|
getAutocompleteOptions: debounce(function debouncedSearch(searchTerm) {
|
2022-08-27 11:52:29 +05:30
|
|
|
this.openDropdown();
|
2021-11-18 22:05:49 +05:30
|
|
|
if (!searchTerm) {
|
2022-01-26 12:08:38 +05:30
|
|
|
this.clearAutocomplete();
|
|
|
|
} else {
|
|
|
|
this.fetchAutocompleteOptions();
|
2021-11-18 22:05:49 +05:30
|
|
|
}
|
2022-01-26 12:08:38 +05:30
|
|
|
}, DEFAULT_DEBOUNCE_AND_THROTTLE_MS),
|
2022-08-13 15:12:31 +05:30
|
|
|
getTruncatedScope(scope) {
|
|
|
|
return truncate(scope, SCOPE_TOKEN_MAX_LENGTH);
|
|
|
|
},
|
|
|
|
observeTokenWidth({ contentRect: { width } }) {
|
|
|
|
const inputField = this.$refs?.searchInputBox?.$el?.querySelector('input');
|
|
|
|
if (!inputField) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
inputField.style.paddingRight = `${width + INPUT_FIELD_PADDING}px`;
|
|
|
|
},
|
2021-11-11 11:23:49 +05:30
|
|
|
},
|
2022-01-26 12:08:38 +05:30
|
|
|
SEARCH_BOX_INDEX,
|
2022-08-13 15:12:31 +05:30
|
|
|
FIRST_DROPDOWN_INDEX,
|
2022-01-26 12:08:38 +05:30
|
|
|
SEARCH_INPUT_DESCRIPTION,
|
|
|
|
SEARCH_RESULTS_DESCRIPTION,
|
2021-11-11 11:23:49 +05:30
|
|
|
};
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
2022-01-26 12:08:38 +05:30
|
|
|
<form
|
|
|
|
v-outside="closeDropdown"
|
|
|
|
role="search"
|
2022-03-02 08:16:31 +05:30
|
|
|
:aria-label="$options.i18n.searchGitlab"
|
2022-07-16 23:28:13 +05:30
|
|
|
class="header-search gl-relative gl-rounded-base gl-w-full"
|
2022-08-27 11:52:29 +05:30
|
|
|
:class="searchBarClasses"
|
2022-08-13 15:12:31 +05:30
|
|
|
data-testid="header-search-form"
|
2022-01-26 12:08:38 +05:30
|
|
|
>
|
2021-11-11 11:23:49 +05:30
|
|
|
<gl-search-box-by-type
|
2022-01-26 12:08:38 +05:30
|
|
|
id="search"
|
2022-08-13 15:12:31 +05:30
|
|
|
ref="searchInputBox"
|
2021-11-11 11:23:49 +05:30
|
|
|
v-model="searchText"
|
2022-01-26 12:08:38 +05:30
|
|
|
role="searchbox"
|
|
|
|
class="gl-z-index-1"
|
2022-06-21 17:19:12 +05:30
|
|
|
data-qa-selector="search_term_field"
|
2021-11-11 11:23:49 +05:30
|
|
|
autocomplete="off"
|
2022-03-02 08:16:31 +05:30
|
|
|
:placeholder="$options.i18n.searchGitlab"
|
2022-01-26 12:08:38 +05:30
|
|
|
:aria-activedescendant="currentFocusedId"
|
|
|
|
:aria-describedby="$options.SEARCH_INPUT_DESCRIPTION"
|
2021-11-11 11:23:49 +05:30
|
|
|
@focus="openDropdown"
|
|
|
|
@click="openDropdown"
|
2022-08-27 11:52:29 +05:30
|
|
|
@blur="collapseAndCloseSearchBar"
|
2021-11-18 22:05:49 +05:30
|
|
|
@input="getAutocompleteOptions"
|
2022-01-26 12:08:38 +05:30
|
|
|
@keydown.enter.stop.prevent="submitSearch"
|
2022-08-13 15:12:31 +05:30
|
|
|
@keydown.esc.stop.prevent="closeDropdown"
|
2021-11-11 11:23:49 +05:30
|
|
|
/>
|
2022-08-13 15:12:31 +05:30
|
|
|
<gl-token
|
2022-08-27 11:52:29 +05:30
|
|
|
v-if="showScopeHelp"
|
2022-08-13 15:12:31 +05:30
|
|
|
v-gl-resize-observer-directive="observeTokenWidth"
|
|
|
|
class="in-search-scope-help"
|
|
|
|
:view-only="true"
|
|
|
|
:title="scopeTokenTitle"
|
|
|
|
><gl-icon
|
|
|
|
v-if="infieldHelpIcon"
|
|
|
|
class="gl-mr-2"
|
|
|
|
:aria-label="infieldHelpContent"
|
|
|
|
:name="infieldHelpIcon"
|
|
|
|
:size="16"
|
|
|
|
/>{{
|
|
|
|
getTruncatedScope(
|
|
|
|
sprintf($options.i18n.searchResultsScope, {
|
|
|
|
scope: infieldHelpContent,
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
}}
|
|
|
|
</gl-token>
|
|
|
|
<kbd
|
2022-08-27 11:52:29 +05:30
|
|
|
v-show="!isFocused"
|
2022-08-13 15:12:31 +05:30
|
|
|
v-gl-tooltip.bottom.hover.html
|
|
|
|
class="gl-absolute gl-right-3 gl-top-0 gl-z-index-1 keyboard-shortcut-helper"
|
|
|
|
:title="$options.i18n.kbdHelp"
|
|
|
|
>/</kbd
|
|
|
|
>
|
2022-01-26 12:08:38 +05:30
|
|
|
<span :id="$options.SEARCH_INPUT_DESCRIPTION" role="region" class="gl-sr-only">{{
|
|
|
|
searchInputDescribeBy
|
|
|
|
}}</span>
|
|
|
|
<span
|
|
|
|
role="region"
|
|
|
|
:data-testid="$options.SEARCH_RESULTS_DESCRIPTION"
|
|
|
|
class="gl-sr-only"
|
|
|
|
aria-live="polite"
|
|
|
|
aria-atomic="true"
|
|
|
|
>
|
|
|
|
{{ dropdownResultsDescription }}
|
|
|
|
</span>
|
2021-11-11 11:23:49 +05:30
|
|
|
<div
|
|
|
|
v-if="showSearchDropdown"
|
|
|
|
data-testid="header-search-dropdown-menu"
|
2022-08-27 11:52:29 +05:30
|
|
|
class="header-search-dropdown-menu gl-overflow-y-auto gl-absolute gl-w-full gl-bg-white gl-border-1 gl-rounded-base gl-border-solid gl-border-gray-200 gl-shadow-x0-y2-b4-s0 gl-mt-3"
|
2021-11-11 11:23:49 +05:30
|
|
|
>
|
2022-08-27 11:52:29 +05:30
|
|
|
<div class="header-search-dropdown-content gl-py-2">
|
2022-01-26 12:08:38 +05:30
|
|
|
<dropdown-keyboard-navigation
|
|
|
|
v-model="currentFocusIndex"
|
|
|
|
:max="searchOptions.length - 1"
|
2022-08-13 15:12:31 +05:30
|
|
|
:min="$options.FIRST_DROPDOWN_INDEX"
|
2022-01-26 12:08:38 +05:30
|
|
|
:default-index="defaultIndex"
|
|
|
|
@tab="closeDropdown"
|
|
|
|
/>
|
|
|
|
<header-search-default-items
|
|
|
|
v-if="showDefaultItems"
|
|
|
|
:current-focused-option="currentFocusedOption"
|
|
|
|
/>
|
2021-11-11 11:23:49 +05:30
|
|
|
<template v-else>
|
2022-06-21 17:19:12 +05:30
|
|
|
<header-search-scoped-items
|
2022-08-27 11:52:29 +05:30
|
|
|
v-if="searchTermOverMin"
|
2022-06-21 17:19:12 +05:30
|
|
|
:current-focused-option="currentFocusedOption"
|
|
|
|
/>
|
2022-01-26 12:08:38 +05:30
|
|
|
<header-search-autocomplete-items :current-focused-option="currentFocusedOption" />
|
2021-11-11 11:23:49 +05:30
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
</div>
|
2022-01-26 12:08:38 +05:30
|
|
|
</form>
|
2021-11-11 11:23:49 +05:30
|
|
|
</template>
|