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:
Diffstat (limited to 'app/assets/javascripts/vue_shared/components/filtered_search_bar')
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js48
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue10
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js40
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue167
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue14
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue84
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue110
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/weight_token.vue58
8 files changed, 460 insertions, 71 deletions
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js
index 3d8afd162cb..2cb1b6a195f 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js
@@ -1,24 +1,46 @@
-/* eslint-disable @gitlab/require-i18n-strings */
import { __ } from '~/locale';
-const DEFAULT_LABEL_NO_LABEL = { value: 'No label', text: __('No label') };
-export const DEFAULT_LABEL_NONE = { value: 'None', text: __('None') };
-export const DEFAULT_LABEL_ANY = { value: 'Any', text: __('Any') };
+export const DEBOUNCE_DELAY = 200;
+export const MAX_RECENT_TOKENS_SIZE = 3;
-export const DEFAULT_LABELS = [DEFAULT_LABEL_NO_LABEL];
+export const FILTER_NONE = 'None';
+export const FILTER_ANY = 'Any';
+export const FILTER_CURRENT = 'Current';
-export const DEBOUNCE_DELAY = 200;
+export const OPERATOR_IS = '=';
+export const OPERATOR_IS_TEXT = __('is');
+export const OPERATOR_IS_NOT = '!=';
+
+export const OPERATOR_IS_ONLY = [{ value: OPERATOR_IS, description: OPERATOR_IS_TEXT }];
+
+export const DEFAULT_LABEL_NONE = { value: FILTER_NONE, text: __(FILTER_NONE) };
+export const DEFAULT_LABEL_ANY = { value: FILTER_ANY, text: __(FILTER_ANY) };
+export const DEFAULT_NONE_ANY = [DEFAULT_LABEL_NONE, DEFAULT_LABEL_ANY];
+
+export const DEFAULT_ITERATIONS = DEFAULT_NONE_ANY.concat([
+ { value: FILTER_CURRENT, text: __(FILTER_CURRENT) },
+]);
+
+export const DEFAULT_LABELS = [{ value: 'No label', text: __('No label') }]; // eslint-disable-line @gitlab/require-i18n-strings
+
+export const DEFAULT_MILESTONES = DEFAULT_NONE_ANY.concat([
+ { value: 'Upcoming', text: __('Upcoming') }, // eslint-disable-line @gitlab/require-i18n-strings
+ { value: 'Started', text: __('Started') }, // eslint-disable-line @gitlab/require-i18n-strings
+]);
export const SortDirection = {
descending: 'descending',
ascending: 'ascending',
};
-export const DEFAULT_MILESTONES = [
- DEFAULT_LABEL_NONE,
- DEFAULT_LABEL_ANY,
- { value: 'Upcoming', text: __('Upcoming') },
- { value: 'Started', text: __('Started') },
-];
+export const FILTERED_SEARCH_TERM = 'filtered-search-term';
-/* eslint-enable @gitlab/require-i18n-strings */
+export const TOKEN_TITLE_AUTHOR = __('Author');
+export const TOKEN_TITLE_ASSIGNEE = __('Assignee');
+export const TOKEN_TITLE_MILESTONE = __('Milestone');
+export const TOKEN_TITLE_LABEL = __('Label');
+export const TOKEN_TITLE_MY_REACTION = __('My-Reaction');
+export const TOKEN_TITLE_CONFIDENTIAL = __('Confidential');
+export const TOKEN_TITLE_ITERATION = __('Iteration');
+export const TOKEN_TITLE_EPIC = __('Epic');
+export const TOKEN_TITLE_WEIGHT = __('Weight');
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
index 107ced550c1..3e7feb91b27 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
@@ -93,9 +93,9 @@ export default {
sortBy.sortDirection.descending === this.initialSortBy,
)
.pop();
- selectedSortDirection = this.initialSortBy.endsWith('_desc')
- ? SortDirection.descending
- : SortDirection.ascending;
+ selectedSortDirection = Object.keys(selectedSortOption.sortDirection).find(
+ (key) => selectedSortOption.sortDirection[key] === this.initialSortBy,
+ );
}
return {
@@ -324,7 +324,9 @@ export default {
class="gl-align-self-center"
:checked="checkboxChecked"
@input="$emit('checked-input', $event)"
- />
+ >
+ <span class="gl-sr-only">{{ __('Select all') }}</span>
+ </gl-form-checkbox>
<gl-filtered-search
ref="filteredSearchInput"
v-model="filterValue"
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js
index a15cf220ee5..e5c8d29e09b 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js
@@ -1,6 +1,9 @@
-import { isEmpty } from 'lodash';
+import { isEmpty, uniqWith, isEqual } from 'lodash';
+import AccessorUtilities from '~/lib/utils/accessor';
import { queryToObject } from '~/lib/utils/url_utility';
+import { MAX_RECENT_TOKENS_SIZE } from './constants';
+
/**
* Strips enclosing quotations from a string if it has one.
*
@@ -162,3 +165,38 @@ export function urlQueryToFilter(query = '') {
return { ...memo, [filterName]: { value, operator } };
}, {});
}
+
+/**
+ * Returns array of token values from localStorage
+ * based on provided recentTokenValuesStorageKey
+ *
+ * @param {String} recentTokenValuesStorageKey
+ * @returns
+ */
+export function getRecentlyUsedTokenValues(recentTokenValuesStorageKey) {
+ let recentlyUsedTokenValues = [];
+ if (AccessorUtilities.isLocalStorageAccessSafe()) {
+ recentlyUsedTokenValues = JSON.parse(localStorage.getItem(recentTokenValuesStorageKey)) || [];
+ }
+ return recentlyUsedTokenValues;
+}
+
+/**
+ * Sets provided token value to recently used array
+ * within localStorage for provided recentTokenValuesStorageKey
+ *
+ * @param {String} recentTokenValuesStorageKey
+ * @param {Object} tokenValue
+ */
+export function setTokenValueToRecentlyUsed(recentTokenValuesStorageKey, tokenValue) {
+ const recentlyUsedTokenValues = getRecentlyUsedTokenValues(recentTokenValuesStorageKey);
+
+ recentlyUsedTokenValues.splice(0, 0, { ...tokenValue });
+
+ if (AccessorUtilities.isLocalStorageAccessSafe()) {
+ localStorage.setItem(
+ recentTokenValuesStorageKey,
+ JSON.stringify(uniqWith(recentlyUsedTokenValues, isEqual).slice(0, MAX_RECENT_TOKENS_SIZE)),
+ );
+ }
+}
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue
new file mode 100644
index 00000000000..6ebc5431012
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue
@@ -0,0 +1,167 @@
+<script>
+import {
+ GlFilteredSearchToken,
+ GlFilteredSearchSuggestion,
+ GlDropdownDivider,
+ GlDropdownSectionHeader,
+ GlLoadingIcon,
+} from '@gitlab/ui';
+
+import { DEBOUNCE_DELAY } from '../constants';
+import { getRecentlyUsedTokenValues, setTokenValueToRecentlyUsed } from '../filtered_search_utils';
+
+export default {
+ components: {
+ GlFilteredSearchToken,
+ GlFilteredSearchSuggestion,
+ GlDropdownDivider,
+ GlDropdownSectionHeader,
+ GlLoadingIcon,
+ },
+ props: {
+ tokenConfig: {
+ type: Object,
+ required: true,
+ },
+ tokenValue: {
+ type: Object,
+ required: true,
+ },
+ tokenActive: {
+ type: Boolean,
+ required: true,
+ },
+ tokensListLoading: {
+ type: Boolean,
+ required: true,
+ },
+ tokenValues: {
+ type: Array,
+ required: true,
+ },
+ fnActiveTokenValue: {
+ type: Function,
+ required: true,
+ },
+ defaultTokenValues: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ recentTokenValuesStorageKey: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ valueIdentifier: {
+ type: String,
+ required: false,
+ default: 'id',
+ },
+ fnCurrentTokenValue: {
+ type: Function,
+ required: false,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ searchKey: '',
+ recentTokenValues: this.recentTokenValuesStorageKey
+ ? getRecentlyUsedTokenValues(this.recentTokenValuesStorageKey)
+ : [],
+ loading: false,
+ };
+ },
+ computed: {
+ isRecentTokenValuesEnabled() {
+ return Boolean(this.recentTokenValuesStorageKey);
+ },
+ recentTokenIds() {
+ return this.recentTokenValues.map((tokenValue) => tokenValue.id || tokenValue.name);
+ },
+ currentTokenValue() {
+ if (this.fnCurrentTokenValue) {
+ return this.fnCurrentTokenValue(this.tokenValue.data);
+ }
+ return this.tokenValue.data.toLowerCase();
+ },
+ activeTokenValue() {
+ return this.fnActiveTokenValue(this.tokenValues, this.currentTokenValue);
+ },
+ /**
+ * Return all the tokenValues when searchKey is present
+ * otherwise return only the tokenValues which aren't
+ * present in "Recently used"
+ */
+ availableTokenValues() {
+ return this.searchKey
+ ? this.tokenValues
+ : this.tokenValues.filter(
+ (tokenValue) => !this.recentTokenIds.includes(tokenValue[this.valueIdentifier]),
+ );
+ },
+ },
+ watch: {
+ tokenActive: {
+ immediate: true,
+ handler(newValue) {
+ if (!newValue && !this.tokenValues.length) {
+ this.$emit('fetch-token-values', this.tokenValue.data);
+ }
+ },
+ },
+ },
+ methods: {
+ handleInput({ data }) {
+ this.searchKey = data;
+ setTimeout(() => {
+ if (!this.tokensListLoading) this.$emit('fetch-token-values', data);
+ }, DEBOUNCE_DELAY);
+ },
+ handleTokenValueSelected(activeTokenValue) {
+ if (this.isRecentTokenValuesEnabled) {
+ setTokenValueToRecentlyUsed(this.recentTokenValuesStorageKey, activeTokenValue);
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-filtered-search-token
+ :config="tokenConfig"
+ v-bind="{ ...this.$parent.$props, ...this.$parent.$attrs }"
+ v-on="this.$parent.$listeners"
+ @input="handleInput"
+ @select="handleTokenValueSelected(activeTokenValue)"
+ >
+ <template #view-token="viewTokenProps">
+ <slot name="view-token" :view-token-props="{ ...viewTokenProps, activeTokenValue }"></slot>
+ </template>
+ <template #view="viewTokenProps">
+ <slot name="view" :view-token-props="{ ...viewTokenProps, activeTokenValue }"></slot>
+ </template>
+ <template #suggestions>
+ <template v-if="defaultTokenValues.length">
+ <gl-filtered-search-suggestion
+ v-for="token in defaultTokenValues"
+ :key="token.value"
+ :value="token.value"
+ >
+ {{ token.text }}
+ </gl-filtered-search-suggestion>
+ <gl-dropdown-divider />
+ </template>
+ <template v-if="isRecentTokenValuesEnabled && recentTokenValues.length && !searchKey">
+ <gl-dropdown-section-header>{{ __('Recently used') }}</gl-dropdown-section-header>
+ <slot name="token-values-list" :token-values="recentTokenValues"></slot>
+ <gl-dropdown-divider />
+ </template>
+ <gl-loading-icon v-if="tokensListLoading" />
+ <template v-else>
+ <slot name="token-values-list" :token-values="availableTokenValues"></slot>
+ </template>
+ </template>
+ </gl-filtered-search-token>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
index 98190d716c9..f2f4787d80b 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
@@ -10,7 +10,7 @@ import { debounce } from 'lodash';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { __ } from '~/locale';
-import { DEFAULT_LABEL_NONE, DEFAULT_LABEL_ANY, DEBOUNCE_DELAY } from '../constants';
+import { DEBOUNCE_DELAY, DEFAULT_NONE_ANY } from '../constants';
import { stripQuotes } from '../filtered_search_utils';
export default {
@@ -33,7 +33,7 @@ export default {
data() {
return {
emojis: this.config.initialEmojis || [],
- defaultEmojis: this.config.defaultEmojis || [DEFAULT_LABEL_NONE, DEFAULT_LABEL_ANY],
+ defaultEmojis: this.config.defaultEmojis || DEFAULT_NONE_ANY,
loading: true,
};
},
@@ -47,6 +47,16 @@ export default {
);
},
},
+ watch: {
+ active: {
+ immediate: true,
+ handler(newValue) {
+ if (!newValue && !this.emojis.length) {
+ this.fetchEmojiBySearchTerm(this.value.data);
+ }
+ },
+ },
+ },
methods: {
fetchEmojiBySearchTerm(searchTerm) {
this.loading = true;
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue
index 101c7150c55..1450807b11d 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue
@@ -1,15 +1,18 @@
<script>
-import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
+import {
+ GlDropdownDivider,
+ GlFilteredSearchSuggestion,
+ GlFilteredSearchToken,
+ GlLoadingIcon,
+} from '@gitlab/ui';
import { debounce } from 'lodash';
-
import createFlash from '~/flash';
-import { isNumeric } from '~/lib/utils/number_utils';
import { __ } from '~/locale';
-import { DEBOUNCE_DELAY } from '../constants';
-import { stripQuotes } from '../filtered_search_utils';
+import { DEBOUNCE_DELAY, DEFAULT_NONE_ANY } from '../constants';
export default {
components: {
+ GlDropdownDivider,
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
GlLoadingIcon,
@@ -32,29 +35,16 @@ export default {
},
computed: {
currentValue() {
- /*
- * When the URL contains the epic_iid, we'd get: '123'
- */
- if (isNumeric(this.value.data)) {
- return parseInt(this.value.data, 10);
- }
-
- /*
- * When the token is added in current session it'd be: 'Foo::&123'
- */
- const id = this.value.data.split('::&')[1];
-
- if (id) {
- return parseInt(id, 10);
- }
-
- return this.value.data;
+ return Number(this.value.data);
+ },
+ defaultEpics() {
+ return this.config.defaultEpics || DEFAULT_NONE_ANY;
+ },
+ idProperty() {
+ return this.config.idProperty || 'id';
},
activeEpic() {
- const currentValueIsString = typeof this.currentValue === 'string';
- return this.epics.find(
- (epic) => epic[currentValueIsString ? 'title' : 'iid'] === this.currentValue,
- );
+ return this.epics.find((epic) => epic[this.idProperty] === this.currentValue);
},
},
watch: {
@@ -72,20 +62,8 @@ export default {
this.loading = true;
this.config
.fetchEpics(searchTerm)
- .then(({ data }) => {
- this.epics = data;
- })
- .catch(() => createFlash({ message: __('There was a problem fetching epics.') }))
- .finally(() => {
- this.loading = false;
- });
- },
- fetchSingleEpic(iid) {
- this.loading = true;
- this.config
- .fetchSingleEpic(iid)
- .then(({ data }) => {
- this.epics = [data];
+ .then((response) => {
+ this.epics = Array.isArray(response) ? response : response.data;
})
.catch(() => createFlash({ message: __('There was a problem fetching epics.') }))
.finally(() => {
@@ -93,17 +71,13 @@ export default {
});
},
searchEpics: debounce(function debouncedSearch({ data }) {
- if (isNumeric(data)) {
- return this.fetchSingleEpic(data);
- }
- return this.fetchEpicsBySearchTerm(data);
+ this.fetchEpicsBySearchTerm(data);
}, DEBOUNCE_DELAY),
- getEpicValue(epic) {
- return `${epic.title}::&${epic.iid}`;
+ getEpicDisplayText(epic) {
+ return `${epic.title}::&${epic[this.idProperty]}`;
},
},
- stripQuotes,
};
</script>
@@ -115,17 +89,25 @@ export default {
@input="searchEpics"
>
<template #view="{ inputValue }">
- <span>{{ activeEpic ? getEpicValue(activeEpic) : $options.stripQuotes(inputValue) }}</span>
+ {{ activeEpic ? getEpicDisplayText(activeEpic) : inputValue }}
</template>
<template #suggestions>
+ <gl-filtered-search-suggestion
+ v-for="epic in defaultEpics"
+ :key="epic.value"
+ :value="epic.value"
+ >
+ {{ epic.text }}
+ </gl-filtered-search-suggestion>
+ <gl-dropdown-divider v-if="defaultEpics.length" />
<gl-loading-icon v-if="loading" />
<template v-else>
<gl-filtered-search-suggestion
v-for="epic in epics"
- :key="epic.id"
- :value="getEpicValue(epic)"
+ :key="epic[idProperty]"
+ :value="String(epic[idProperty])"
>
- <div>{{ epic.title }}</div>
+ {{ epic.title }}
</gl-filtered-search-suggestion>
</template>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue
new file mode 100644
index 00000000000..7b6a590279a
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue
@@ -0,0 +1,110 @@
+<script>
+import {
+ GlDropdownDivider,
+ GlFilteredSearchSuggestion,
+ GlFilteredSearchToken,
+ GlLoadingIcon,
+} from '@gitlab/ui';
+import { debounce } from 'lodash';
+import createFlash from '~/flash';
+import { __ } from '~/locale';
+import { DEBOUNCE_DELAY, DEFAULT_ITERATIONS } from '../constants';
+
+export default {
+ components: {
+ GlDropdownDivider,
+ GlFilteredSearchSuggestion,
+ GlFilteredSearchToken,
+ GlLoadingIcon,
+ },
+ props: {
+ config: {
+ type: Object,
+ required: true,
+ },
+ value: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ iterations: this.config.initialIterations || [],
+ defaultIterations: this.config.defaultIterations || DEFAULT_ITERATIONS,
+ loading: true,
+ };
+ },
+ computed: {
+ currentValue() {
+ return this.value.data;
+ },
+ activeIteration() {
+ return this.iterations.find((iteration) => iteration.title === this.currentValue);
+ },
+ },
+ watch: {
+ active: {
+ immediate: true,
+ handler(newValue) {
+ if (!newValue && !this.iterations.length) {
+ this.fetchIterationBySearchTerm(this.currentValue);
+ }
+ },
+ },
+ },
+ methods: {
+ fetchIterationBySearchTerm(searchTerm) {
+ const fetchPromise = this.config.fetchPath
+ ? this.config.fetchIterations(this.config.fetchPath, searchTerm)
+ : this.config.fetchIterations(searchTerm);
+
+ this.loading = true;
+
+ fetchPromise
+ .then((response) => {
+ this.iterations = Array.isArray(response) ? response : response.data;
+ })
+ .catch(() => createFlash({ message: __('There was a problem fetching iterations.') }))
+ .finally(() => {
+ this.loading = false;
+ });
+ },
+ searchIterations: debounce(function debouncedSearch({ data }) {
+ this.fetchIterationBySearchTerm(data);
+ }, DEBOUNCE_DELAY),
+ },
+};
+</script>
+
+<template>
+ <gl-filtered-search-token
+ :config="config"
+ v-bind="{ ...$props, ...$attrs }"
+ v-on="$listeners"
+ @input="searchIterations"
+ >
+ <template #view="{ inputValue }">
+ {{ activeIteration ? activeIteration.title : inputValue }}
+ </template>
+ <template #suggestions>
+ <gl-filtered-search-suggestion
+ v-for="iteration in defaultIterations"
+ :key="iteration.value"
+ :value="iteration.value"
+ >
+ {{ iteration.text }}
+ </gl-filtered-search-suggestion>
+ <gl-dropdown-divider v-if="defaultIterations.length" />
+ <gl-loading-icon v-if="loading" />
+ <template v-else>
+ <gl-filtered-search-suggestion
+ v-for="iteration in iterations"
+ :key="iteration.title"
+ :value="iteration.title"
+ >
+ {{ iteration.title }}
+ </gl-filtered-search-suggestion>
+ </template>
+ </template>
+ </gl-filtered-search-token>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/weight_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/weight_token.vue
new file mode 100644
index 00000000000..72116f0e991
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/weight_token.vue
@@ -0,0 +1,58 @@
+<script>
+import { GlDropdownDivider, GlFilteredSearchSuggestion, GlFilteredSearchToken } from '@gitlab/ui';
+import { DEFAULT_NONE_ANY } from '../constants';
+
+export default {
+ baseWeights: ['0', '1', '2', '3', '4', '5'],
+ components: {
+ GlDropdownDivider,
+ GlFilteredSearchSuggestion,
+ GlFilteredSearchToken,
+ },
+ props: {
+ config: {
+ type: Object,
+ required: true,
+ },
+ value: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ weights: this.$options.baseWeights,
+ defaultWeights: this.config.defaultWeights || DEFAULT_NONE_ANY,
+ };
+ },
+ methods: {
+ updateWeights({ data }) {
+ const weight = parseInt(data, 10);
+ this.weights = Number.isNaN(weight) ? this.$options.baseWeights : [String(weight)];
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-filtered-search-token
+ :config="config"
+ v-bind="{ ...$props, ...$attrs }"
+ v-on="$listeners"
+ @input="updateWeights"
+ >
+ <template #suggestions>
+ <gl-filtered-search-suggestion
+ v-for="weight in defaultWeights"
+ :key="weight.value"
+ :value="weight.value"
+ >
+ {{ weight.text }}
+ </gl-filtered-search-suggestion>
+ <gl-dropdown-divider v-if="defaultWeights.length" />
+ <gl-filtered-search-suggestion v-for="weight of weights" :key="weight" :value="weight">
+ {{ weight }}
+ </gl-filtered-search-suggestion>
+ </template>
+ </gl-filtered-search-token>
+</template>