debian-mirror-gitlab/app/assets/javascripts/header_search/components/app.vue

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

309 lines
9.3 KiB
Vue
Raw Normal View History

2021-11-11 11:23:49 +05:30
<script>
2022-08-13 15:12:31 +05:30
import {
GlSearchBoxByType,
GlOutsideDirective as Outside,
GlIcon,
GlToken,
GlSafeHtmlDirective as SafeHtml,
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';
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
},
2022-08-13 15:12:31 +05:30
directives: { SafeHtml, 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-08-27 11:52:29 +05:30
this.isFocused = true;
this.$emit('expandSearchBar', true);
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');
}, 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>