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>2021-09-20 16:18:24 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-09-20 16:18:24 +0300
commit0653e08efd039a5905f3fa4f6e9cef9f5d2f799c (patch)
tree4dcc884cf6d81db44adae4aa99f8ec1233a41f55 /app/assets/javascripts/header_search
parent744144d28e3e7fddc117924fef88de5d9674fe4c (diff)
Add latest changes from gitlab-org/gitlab@14-3-stable-eev14.3.0-rc42
Diffstat (limited to 'app/assets/javascripts/header_search')
-rw-r--r--app/assets/javascripts/header_search/components/app.vue83
-rw-r--r--app/assets/javascripts/header_search/components/header_search_default_items.vue42
-rw-r--r--app/assets/javascripts/header_search/components/header_search_scoped_items.vue31
-rw-r--r--app/assets/javascripts/header_search/constants.js17
-rw-r--r--app/assets/javascripts/header_search/index.js26
-rw-r--r--app/assets/javascripts/header_search/store/actions.js5
-rw-r--r--app/assets/javascripts/header_search/store/getters.js135
-rw-r--r--app/assets/javascripts/header_search/store/index.js18
-rw-r--r--app/assets/javascripts/header_search/store/mutation_types.js1
-rw-r--r--app/assets/javascripts/header_search/store/mutations.js7
-rw-r--r--app/assets/javascripts/header_search/store/state.js8
11 files changed, 373 insertions, 0 deletions
diff --git a/app/assets/javascripts/header_search/components/app.vue b/app/assets/javascripts/header_search/components/app.vue
new file mode 100644
index 00000000000..580c27f6c61
--- /dev/null
+++ b/app/assets/javascripts/header_search/components/app.vue
@@ -0,0 +1,83 @@
+<script>
+import { GlSearchBoxByType, GlOutsideDirective as Outside } from '@gitlab/ui';
+import { mapState, mapActions, mapGetters } from 'vuex';
+import { visitUrl } from '~/lib/utils/url_utility';
+import { __ } from '~/locale';
+import HeaderSearchDefaultItems from './header_search_default_items.vue';
+import HeaderSearchScopedItems from './header_search_scoped_items.vue';
+
+export default {
+ name: 'HeaderSearchApp',
+ i18n: {
+ searchPlaceholder: __('Search or jump to...'),
+ },
+ directives: { Outside },
+ components: {
+ GlSearchBoxByType,
+ HeaderSearchDefaultItems,
+ HeaderSearchScopedItems,
+ },
+ data() {
+ return {
+ showDropdown: false,
+ };
+ },
+ computed: {
+ ...mapState(['search']),
+ ...mapGetters(['searchQuery']),
+ searchText: {
+ get() {
+ return this.search;
+ },
+ set(value) {
+ this.setSearch(value);
+ },
+ },
+ showSearchDropdown() {
+ return this.showDropdown && gon?.current_username;
+ },
+ showDefaultItems() {
+ return !this.searchText;
+ },
+ },
+ methods: {
+ ...mapActions(['setSearch']),
+ openDropdown() {
+ this.showDropdown = true;
+ },
+ closeDropdown() {
+ this.showDropdown = false;
+ },
+ submitSearch() {
+ return visitUrl(this.searchQuery);
+ },
+ },
+};
+</script>
+
+<template>
+ <section v-outside="closeDropdown" class="header-search gl-relative">
+ <gl-search-box-by-type
+ v-model="searchText"
+ :debounce="500"
+ autocomplete="off"
+ :placeholder="$options.i18n.searchPlaceholder"
+ @focus="openDropdown"
+ @click="openDropdown"
+ @keydown.enter="submitSearch"
+ @keydown.esc="closeDropdown"
+ />
+ <div
+ v-if="showSearchDropdown"
+ data-testid="header-search-dropdown-menu"
+ class="header-search-dropdown-menu gl-overflow-y-auto gl-absolute gl-left-0 gl-z-index-1 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"
+ >
+ <div class="header-search-dropdown-content gl-overflow-y-auto gl-py-2">
+ <header-search-default-items v-if="showDefaultItems" />
+ <template v-else>
+ <header-search-scoped-items />
+ </template>
+ </div>
+ </div>
+ </section>
+</template>
diff --git a/app/assets/javascripts/header_search/components/header_search_default_items.vue b/app/assets/javascripts/header_search/components/header_search_default_items.vue
new file mode 100644
index 00000000000..2871937ed3a
--- /dev/null
+++ b/app/assets/javascripts/header_search/components/header_search_default_items.vue
@@ -0,0 +1,42 @@
+<script>
+import { GlDropdownItem, GlDropdownSectionHeader } from '@gitlab/ui';
+import { mapState, mapGetters } from 'vuex';
+import { __ } from '~/locale';
+
+export default {
+ name: 'HeaderSearchDefaultItems',
+ i18n: {
+ allGitLab: __('All GitLab'),
+ },
+ components: {
+ GlDropdownSectionHeader,
+ GlDropdownItem,
+ },
+ computed: {
+ ...mapState(['searchContext']),
+ ...mapGetters(['defaultSearchOptions']),
+ sectionHeader() {
+ return (
+ this.searchContext.project?.name ||
+ this.searchContext.group?.name ||
+ this.$options.i18n.allGitLab
+ );
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-dropdown-section-header>{{ sectionHeader }}</gl-dropdown-section-header>
+ <gl-dropdown-item
+ v-for="(option, index) in defaultSearchOptions"
+ :id="`default-${index}`"
+ :key="index"
+ tabindex="-1"
+ :href="option.url"
+ >
+ {{ option.title }}
+ </gl-dropdown-item>
+ </div>
+</template>
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
new file mode 100644
index 00000000000..645eba05148
--- /dev/null
+++ b/app/assets/javascripts/header_search/components/header_search_scoped_items.vue
@@ -0,0 +1,31 @@
+<script>
+import { GlDropdownItem } from '@gitlab/ui';
+import { mapState, mapGetters } from 'vuex';
+
+export default {
+ name: 'HeaderSearchScopedItems',
+ components: {
+ GlDropdownItem,
+ },
+ computed: {
+ ...mapState(['search']),
+ ...mapGetters(['scopedSearchOptions']),
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-dropdown-item
+ v-for="(option, index) in scopedSearchOptions"
+ :id="`scoped-${index}`"
+ :key="index"
+ tabindex="-1"
+ :href="option.url"
+ >
+ "<span class="gl-font-weight-bold">{{ search }}</span
+ >" {{ option.description }}
+ <span v-if="option.scope" class="gl-font-style-italic">{{ option.scope }}</span>
+ </gl-dropdown-item>
+ </div>
+</template>
diff --git a/app/assets/javascripts/header_search/constants.js b/app/assets/javascripts/header_search/constants.js
new file mode 100644
index 00000000000..fffed7bcbdb
--- /dev/null
+++ b/app/assets/javascripts/header_search/constants.js
@@ -0,0 +1,17 @@
+import { __ } from '~/locale';
+
+export const MSG_ISSUES_ASSIGNED_TO_ME = __('Issues assigned to me');
+
+export const MSG_ISSUES_IVE_CREATED = __("Issues I've created");
+
+export const MSG_MR_ASSIGNED_TO_ME = __('Merge requests assigned to me');
+
+export const MSG_MR_IM_REVIEWER = __("Merge requests that I'm a reviewer");
+
+export const MSG_MR_IVE_CREATED = __("Merge requests I've created");
+
+export const MSG_IN_ALL_GITLAB = __('in all GitLab');
+
+export const MSG_IN_GROUP = __('in group');
+
+export const MSG_IN_PROJECT = __('in project');
diff --git a/app/assets/javascripts/header_search/index.js b/app/assets/javascripts/header_search/index.js
new file mode 100644
index 00000000000..2d37ee137fc
--- /dev/null
+++ b/app/assets/javascripts/header_search/index.js
@@ -0,0 +1,26 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
+import HeaderSearchApp from './components/app.vue';
+import createStore from './store';
+
+Vue.use(Translate);
+
+export const initHeaderSearchApp = () => {
+ const el = document.getElementById('js-header-search');
+
+ if (!el) {
+ return false;
+ }
+
+ const { searchPath, issuesPath, mrPath } = el.dataset;
+ let { searchContext } = el.dataset;
+ searchContext = JSON.parse(searchContext);
+
+ return new Vue({
+ el,
+ store: createStore({ searchPath, issuesPath, mrPath, searchContext }),
+ render(createElement) {
+ return createElement(HeaderSearchApp);
+ },
+ });
+};
diff --git a/app/assets/javascripts/header_search/store/actions.js b/app/assets/javascripts/header_search/store/actions.js
new file mode 100644
index 00000000000..841aee04029
--- /dev/null
+++ b/app/assets/javascripts/header_search/store/actions.js
@@ -0,0 +1,5 @@
+import * as types from './mutation_types';
+
+export const setSearch = ({ commit }, value) => {
+ commit(types.SET_SEARCH, value);
+};
diff --git a/app/assets/javascripts/header_search/store/getters.js b/app/assets/javascripts/header_search/store/getters.js
new file mode 100644
index 00000000000..d1e1fc8ad73
--- /dev/null
+++ b/app/assets/javascripts/header_search/store/getters.js
@@ -0,0 +1,135 @@
+import { objectToQuery } from '~/lib/utils/url_utility';
+
+import {
+ MSG_ISSUES_ASSIGNED_TO_ME,
+ MSG_ISSUES_IVE_CREATED,
+ MSG_MR_ASSIGNED_TO_ME,
+ MSG_MR_IM_REVIEWER,
+ MSG_MR_IVE_CREATED,
+ MSG_IN_PROJECT,
+ MSG_IN_GROUP,
+ MSG_IN_ALL_GITLAB,
+} from '../constants';
+
+export const searchQuery = (state) => {
+ const query = {
+ search: state.search,
+ nav_source: 'navbar',
+ project_id: state.searchContext.project?.id,
+ group_id: state.searchContext.group?.id,
+ scope: state.searchContext.scope,
+ };
+
+ return `${state.searchPath}?${objectToQuery(query)}`;
+};
+
+export const scopedIssuesPath = (state) => {
+ return (
+ state.searchContext.project_metadata?.issues_path ||
+ state.searchContext.group_metadata?.issues_path ||
+ state.issuesPath
+ );
+};
+
+export const scopedMRPath = (state) => {
+ return (
+ state.searchContext.project_metadata?.mr_path ||
+ state.searchContext.group_metadata?.mr_path ||
+ state.mrPath
+ );
+};
+
+export const defaultSearchOptions = (state, getters) => {
+ const userName = gon.current_username;
+
+ return [
+ {
+ title: MSG_ISSUES_ASSIGNED_TO_ME,
+ url: `${getters.scopedIssuesPath}/?assignee_username=${userName}`,
+ },
+ {
+ title: MSG_ISSUES_IVE_CREATED,
+ url: `${getters.scopedIssuesPath}/?author_username=${userName}`,
+ },
+ {
+ title: MSG_MR_ASSIGNED_TO_ME,
+ url: `${getters.scopedMRPath}/?assignee_username=${userName}`,
+ },
+ {
+ title: MSG_MR_IM_REVIEWER,
+ url: `${getters.scopedMRPath}/?reviewer_username=${userName}`,
+ },
+ {
+ title: MSG_MR_IVE_CREATED,
+ url: `${getters.scopedMRPath}/?author_username=${userName}`,
+ },
+ ];
+};
+
+export const projectUrl = (state) => {
+ if (!state.searchContext.project || !state.searchContext.group) {
+ return null;
+ }
+
+ const query = {
+ search: state.search,
+ nav_source: 'navbar',
+ project_id: state.searchContext.project.id,
+ group_id: state.searchContext.group.id,
+ scope: state.searchContext.scope,
+ };
+
+ return `${state.searchPath}?${objectToQuery(query)}`;
+};
+
+export const groupUrl = (state) => {
+ if (!state.searchContext.group) {
+ return null;
+ }
+
+ const query = {
+ search: state.search,
+ nav_source: 'navbar',
+ group_id: state.searchContext.group.id,
+ scope: state.searchContext.scope,
+ };
+
+ return `${state.searchPath}?${objectToQuery(query)}`;
+};
+
+export const allUrl = (state) => {
+ const query = {
+ search: state.search,
+ nav_source: 'navbar',
+ scope: state.searchContext.scope,
+ };
+
+ return `${state.searchPath}?${objectToQuery(query)}`;
+};
+
+export const scopedSearchOptions = (state, getters) => {
+ const options = [];
+
+ if (state.searchContext.project) {
+ options.push({
+ scope: state.searchContext.project.name,
+ description: MSG_IN_PROJECT,
+ url: getters.projectUrl,
+ });
+ }
+
+ if (state.searchContext.group) {
+ options.push({
+ scope: state.searchContext.group.name,
+ description: MSG_IN_GROUP,
+ url: getters.groupUrl,
+ });
+ }
+
+ options.push({
+ description: MSG_IN_ALL_GITLAB,
+ url: getters.allUrl,
+ });
+
+ return options;
+};
diff --git a/app/assets/javascripts/header_search/store/index.js b/app/assets/javascripts/header_search/store/index.js
new file mode 100644
index 00000000000..8b74f8662a5
--- /dev/null
+++ b/app/assets/javascripts/header_search/store/index.js
@@ -0,0 +1,18 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import * as getters from './getters';
+import mutations from './mutations';
+import createState from './state';
+
+Vue.use(Vuex);
+
+export const getStoreConfig = ({ searchPath, issuesPath, mrPath, searchContext }) => ({
+ actions,
+ getters,
+ mutations,
+ state: createState({ searchPath, issuesPath, mrPath, searchContext }),
+});
+
+const createStore = (config) => new Vuex.Store(getStoreConfig(config));
+export default createStore;
diff --git a/app/assets/javascripts/header_search/store/mutation_types.js b/app/assets/javascripts/header_search/store/mutation_types.js
new file mode 100644
index 00000000000..0bc94ae055f
--- /dev/null
+++ b/app/assets/javascripts/header_search/store/mutation_types.js
@@ -0,0 +1 @@
+export const SET_SEARCH = 'SET_SEARCH';
diff --git a/app/assets/javascripts/header_search/store/mutations.js b/app/assets/javascripts/header_search/store/mutations.js
new file mode 100644
index 00000000000..5b1438929d4
--- /dev/null
+++ b/app/assets/javascripts/header_search/store/mutations.js
@@ -0,0 +1,7 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_SEARCH](state, value) {
+ state.search = value;
+ },
+};
diff --git a/app/assets/javascripts/header_search/store/state.js b/app/assets/javascripts/header_search/store/state.js
new file mode 100644
index 00000000000..fb2c83dbbe3
--- /dev/null
+++ b/app/assets/javascripts/header_search/store/state.js
@@ -0,0 +1,8 @@
+const createState = ({ searchPath, issuesPath, mrPath, searchContext }) => ({
+ searchPath,
+ issuesPath,
+ mrPath,
+ searchContext,
+ search: '',
+});
+export default createState;