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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-07-05 15:09:46 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-07-05 15:09:46 +0300
commitf34077e88198da754b4efecd1ce1d996ce982286 (patch)
tree24a176ba93be06eee0ee912215fbeb2611ab7872 /app/assets/javascripts/header_search
parent402c915cb58cfc658ecbdad368e89fb7b3993c1e (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/header_search')
-rw-r--r--app/assets/javascripts/header_search/components/app.vue98
-rw-r--r--app/assets/javascripts/header_search/components/header_search_scoped_items.vue44
-rw-r--r--app/assets/javascripts/header_search/constants.js20
-rw-r--r--app/assets/javascripts/header_search/store/getters.js19
4 files changed, 147 insertions, 34 deletions
diff --git a/app/assets/javascripts/header_search/components/app.vue b/app/assets/javascripts/header_search/components/app.vue
index adf304aebc7..51f9ce9e00e 100644
--- a/app/assets/javascripts/header_search/components/app.vue
+++ b/app/assets/javascripts/header_search/components/app.vue
@@ -1,8 +1,15 @@
<script>
-import { GlSearchBoxByType, GlOutsideDirective as Outside } from '@gitlab/ui';
+import {
+ GlSearchBoxByType,
+ GlOutsideDirective as Outside,
+ GlIcon,
+ GlToken,
+ GlResizeObserverDirective,
+} from '@gitlab/ui';
import { mapState, mapActions, mapGetters } from 'vuex';
import { debounce } from 'lodash';
import { visitUrl } from '~/lib/utils/url_utility';
+import { truncate } from '~/lib/utils/text_utility';
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';
@@ -12,6 +19,8 @@ import {
SEARCH_INPUT_DESCRIPTION,
SEARCH_RESULTS_DESCRIPTION,
SEARCH_SHORTCUTS_MIN_CHARACTERS,
+ SCOPE_TOKEN_MAX_LENGTH,
+ INPUT_FIELD_PADDING,
} from '../constants';
import HeaderSearchAutocompleteItems from './header_search_autocomplete_items.vue';
import HeaderSearchDefaultItems from './header_search_default_items.vue';
@@ -34,14 +43,17 @@ export default {
'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'),
+ searchResultsScope: s__('GlobalSearch|in %{scope}'),
},
- directives: { Outside },
+ directives: { Outside, GlResizeObserverDirective },
components: {
GlSearchBoxByType,
HeaderSearchDefaultItems,
HeaderSearchScopedItems,
HeaderSearchAutocompleteItems,
DropdownKeyboardNavigation,
+ GlIcon,
+ GlToken,
},
data() {
return {
@@ -50,8 +62,8 @@ export default {
};
},
computed: {
- ...mapState(['search', 'loading']),
- ...mapGetters(['searchQuery', 'searchOptions', 'autocompleteGroupedSearchOptions']),
+ ...mapState(['search', 'loading', 'searchContext']),
+ ...mapGetters(['searchQuery', 'searchOptions']),
searchText: {
get() {
return this.search;
@@ -70,16 +82,17 @@ export default {
return Boolean(gon?.current_username);
},
showSearchDropdown() {
- const hasResultsUnderMinCharacters =
- this.searchText?.length === 1 ? this?.autocompleteGroupedSearchOptions?.length > 0 : true;
+ if (!this.showDropdown || !this.isLoggedIn) {
+ return false;
+ }
- return this.showDropdown && this.isLoggedIn && hasResultsUnderMinCharacters;
+ return this.searchOptions?.length > 0;
},
showDefaultItems() {
return !this.searchText;
},
- showShortcuts() {
- return this.searchText && this.searchText?.length >= SEARCH_SHORTCUTS_MIN_CHARACTERS;
+ showScopes() {
+ return this.searchText?.length > SEARCH_SHORTCUTS_MIN_CHARACTERS;
},
defaultIndex() {
if (this.showDefaultItems) {
@@ -88,11 +101,11 @@ export default {
return FIRST_DROPDOWN_INDEX;
},
+
searchInputDescribeBy() {
if (this.isLoggedIn) {
return this.$options.i18n.searchInputDescribeByWithDropdown;
}
-
return this.$options.i18n.searchInputDescribeByNoDropdown;
},
dropdownResultsDescription() {
@@ -112,8 +125,26 @@ export default {
count: this.searchOptions.length,
});
},
- headerSearchActivityDescriptor() {
- return this.showDropdown ? 'is-active' : 'is-not-active';
+ searchBarStateIndicator() {
+ const hasIcon =
+ this.searchContext?.project || this.searchContext?.group ? 'has-icon' : 'has-no-icon';
+ const isSearching = this.showScopes ? 'is-searching' : 'is-not-searching';
+ const isActive = this.showSearchDropdown ? 'is-active' : 'is-not-active';
+ return `${isActive} ${isSearching} ${hasIcon}`;
+ },
+ 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,
+ });
},
},
methods: {
@@ -127,6 +158,9 @@ export default {
this.$emit('toggleDropdown', this.showDropdown);
},
submitSearch() {
+ if (this.search?.length <= SEARCH_SHORTCUTS_MIN_CHARACTERS && this.currentFocusIndex < 0) {
+ return null;
+ }
return visitUrl(this.currentFocusedOption?.url || this.searchQuery);
},
getAutocompleteOptions: debounce(function debouncedSearch(searchTerm) {
@@ -136,8 +170,19 @@ export default {
this.fetchAutocompleteOptions();
}
}, DEFAULT_DEBOUNCE_AND_THROTTLE_MS),
+ 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`;
+ },
},
SEARCH_BOX_INDEX,
+ FIRST_DROPDOWN_INDEX,
SEARCH_INPUT_DESCRIPTION,
SEARCH_RESULTS_DESCRIPTION,
};
@@ -149,10 +194,12 @@ export default {
role="search"
:aria-label="$options.i18n.searchGitlab"
class="header-search gl-relative gl-rounded-base gl-w-full"
- :class="headerSearchActivityDescriptor"
+ :class="searchBarStateIndicator"
+ data-testid="header-search-form"
>
<gl-search-box-by-type
id="search"
+ ref="searchInputBox"
v-model="searchText"
role="searchbox"
class="gl-z-index-1"
@@ -165,7 +212,28 @@ export default {
@click="openDropdown"
@input="getAutocompleteOptions"
@keydown.enter.stop.prevent="submitSearch"
+ @keydown.esc.stop.prevent="closeDropdown"
/>
+ <gl-token
+ v-if="showScopes"
+ 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>
<span :id="$options.SEARCH_INPUT_DESCRIPTION" role="region" class="gl-sr-only">{{
searchInputDescribeBy
}}</span>
@@ -187,7 +255,7 @@ export default {
<dropdown-keyboard-navigation
v-model="currentFocusIndex"
:max="searchOptions.length - 1"
- :min="$options.SEARCH_BOX_INDEX"
+ :min="$options.FIRST_DROPDOWN_INDEX"
:default-index="defaultIndex"
@tab="closeDropdown"
/>
@@ -197,7 +265,7 @@ export default {
/>
<template v-else>
<header-search-scoped-items
- v-if="showShortcuts"
+ v-if="showScopes"
:current-focused-option="currentFocusedOption"
/>
<header-search-autocomplete-items :current-focused-option="currentFocusedOption" />
diff --git a/app/assets/javascripts/header_search/components/header_search_scoped_items.vue b/app/assets/javascripts/header_search/components/header_search_scoped_items.vue
index 34d1bd71399..f5be1bcb786 100644
--- a/app/assets/javascripts/header_search/components/header_search_scoped_items.vue
+++ b/app/assets/javascripts/header_search/components/header_search_scoped_items.vue
@@ -1,13 +1,16 @@
<script>
-import { GlDropdownItem, GlDropdownDivider } from '@gitlab/ui';
+import { GlDropdownItem, GlIcon, GlToken } from '@gitlab/ui';
import { mapState, mapGetters } from 'vuex';
-import { __, sprintf } from '~/locale';
+import { s__, sprintf } from '~/locale';
+import { truncate } from '~/lib/utils/text_utility';
+import { SCOPE_TOKEN_MAX_LENGTH } from '../constants';
export default {
name: 'HeaderSearchScopedItems',
components: {
GlDropdownItem,
- GlDropdownDivider,
+ GlIcon,
+ GlToken,
},
props: {
currentFocusedOption: {
@@ -25,12 +28,21 @@ export default {
return this.currentFocusedOption?.html_id === option.html_id;
},
ariaLabel(option) {
- return sprintf(__('%{search} %{description} %{scope}'), {
+ return sprintf(s__('GlobalSearch| %{search} %{description} %{scope}'), {
search: this.search,
- description: option.description,
+ description: option.description || option.icon,
scope: option.scope || '',
});
},
+ titleLabel(option) {
+ return sprintf(s__('GlobalSearch|in %{scope}'), {
+ search: this.search,
+ scope: option.scope || option.description,
+ });
+ },
+ getTruncatedScope(scope) {
+ return truncate(scope, SCOPE_TOKEN_MAX_LENGTH);
+ },
},
};
</script>
@@ -42,18 +54,30 @@ export default {
:id="option.html_id"
:ref="option.html_id"
:key="option.html_id"
+ class="gl-max-w-full"
:class="{ 'gl-bg-gray-50': isOptionFocused(option) }"
:aria-selected="isOptionFocused(option)"
:aria-label="ariaLabel(option)"
tabindex="-1"
:href="option.url"
+ :title="titleLabel(option)"
>
- <span aria-hidden="true">
- "<span class="gl-font-weight-bold">{{ search }}</span
- >" {{ option.description }}
- <span v-if="option.scope" class="gl-font-style-italic">{{ option.scope }}</span>
+ <span
+ ref="token-text-content"
+ class="gl-display-flex gl-justify-content-start search-text-content gl-line-height-24 gl-align-items-start gl-flex-direction-row gl-w-full"
+ >
+ <gl-icon name="search" class="gl-flex-shrink-0 gl-mr-2 gl-relative gl-pt-2" />
+ <span class="gl-flex-grow-1 gl-relative">
+ <gl-token
+ class="in-dropdown-scope-help has-icon gl-flex-shrink-0 gl-relative gl-white-space-nowrap gl-float-right gl-mr-n3!"
+ :view-only="true"
+ >
+ <gl-icon v-if="option.icon" :name="option.icon" class="gl-mr-2" />
+ <span>{{ getTruncatedScope(titleLabel(option)) }}</span>
+ </gl-token>
+ {{ search }}
+ </span>
</span>
</gl-dropdown-item>
- <gl-dropdown-divider v-if="autocompleteGroupedSearchOptions.length > 0" />
</div>
</template>
diff --git a/app/assets/javascripts/header_search/constants.js b/app/assets/javascripts/header_search/constants.js
index 045a552efb0..c9b05c3deb5 100644
--- a/app/assets/javascripts/header_search/constants.js
+++ b/app/assets/javascripts/header_search/constants.js
@@ -10,15 +10,21 @@ export const MSG_MR_IM_REVIEWER = s__("GlobalSearch|Merge requests that I'm a re
export const MSG_MR_IVE_CREATED = s__("GlobalSearch|Merge requests I've created");
-export const MSG_IN_ALL_GITLAB = s__('GlobalSearch|in all GitLab');
+export const MSG_IN_ALL_GITLAB = s__('GlobalSearch|all GitLab');
-export const MSG_IN_GROUP = s__('GlobalSearch|in group');
+export const MSG_IN_GROUP = s__('GlobalSearch|group');
-export const MSG_IN_PROJECT = s__('GlobalSearch|in project');
+export const MSG_IN_PROJECT = s__('GlobalSearch|project');
-export const GROUPS_CATEGORY = 'Groups';
+export const ICON_PROJECT = 'project';
-export const PROJECTS_CATEGORY = 'Projects';
+export const ICON_GROUP = 'group';
+
+export const ICON_SUBGROUP = 'subgroup';
+
+export const GROUPS_CATEGORY = s__('GlobalSearch|Groups');
+
+export const PROJECTS_CATEGORY = s__('GlobalSearch|Projects');
export const ISSUES_CATEGORY = 'Recent issues';
@@ -39,3 +45,7 @@ export const SEARCH_SHORTCUTS_MIN_CHARACTERS = 2;
export const SEARCH_INPUT_DESCRIPTION = 'search-input-description';
export const SEARCH_RESULTS_DESCRIPTION = 'search-results-description';
+
+export const SCOPE_TOKEN_MAX_LENGTH = 36;
+
+export const INPUT_FIELD_PADDING = 52;
diff --git a/app/assets/javascripts/header_search/store/getters.js b/app/assets/javascripts/header_search/store/getters.js
index 7d08aa859fb..da7bccd35c0 100644
--- a/app/assets/javascripts/header_search/store/getters.js
+++ b/app/assets/javascripts/header_search/store/getters.js
@@ -7,9 +7,13 @@ import {
MSG_MR_ASSIGNED_TO_ME,
MSG_MR_IM_REVIEWER,
MSG_MR_IVE_CREATED,
- MSG_IN_PROJECT,
- MSG_IN_GROUP,
+ ICON_GROUP,
+ ICON_SUBGROUP,
+ ICON_PROJECT,
MSG_IN_ALL_GITLAB,
+ PROJECTS_CATEGORY,
+ GROUPS_CATEGORY,
+ SEARCH_SHORTCUTS_MIN_CHARACTERS,
} from '../constants';
export const searchQuery = (state) => {
@@ -149,7 +153,8 @@ export const scopedSearchOptions = (state, getters) => {
options.push({
html_id: 'scoped-in-project',
scope: state.searchContext.project?.name || '',
- description: MSG_IN_PROJECT,
+ scopeCategory: PROJECTS_CATEGORY,
+ icon: ICON_PROJECT,
url: getters.projectUrl,
});
}
@@ -158,7 +163,8 @@ export const scopedSearchOptions = (state, getters) => {
options.push({
html_id: 'scoped-in-group',
scope: state.searchContext.group?.name || '',
- description: MSG_IN_GROUP,
+ scopeCategory: GROUPS_CATEGORY,
+ icon: state.searchContext.group?.full_name?.includes('/') ? ICON_SUBGROUP : ICON_GROUP,
url: getters.groupUrl,
});
}
@@ -190,6 +196,7 @@ export const autocompleteGroupedSearchOptions = (state) => {
results.push(groupedOptions[option.category]);
}
});
+
return results;
};
@@ -205,5 +212,9 @@ export const searchOptions = (state, getters) => {
[],
);
+ if (state.search?.length <= SEARCH_SHORTCUTS_MIN_CHARACTERS) {
+ return sortedAutocompleteOptions;
+ }
+
return getters.scopedSearchOptions.concat(sortedAutocompleteOptions);
};