\n \n \n {{ __('Select all') }}\n \n \n \n \n \"{{ token }}\"\n \n {{ tokenTitles[token.type] }} :{{ token.value.operator }}\n {{ tokenSymbols[token.type] }}{{ historyTokenOptionTitle(token) }}\n \n \n \n \n \n
\n\n","import { render, staticRenderFns } from \"./filtered_search_bar_root.vue?vue&type=template&id=3ec75946\"\nimport script from \"./filtered_search_bar_root.vue?vue&type=script&lang=js\"\nexport * from \"./filtered_search_bar_root.vue?vue&type=script&lang=js\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","import { extend, mergeData } from '../../vue';\nimport { NAME_INPUT_GROUP_TEXT } from '../../constants/components';\nimport { PROP_TYPE_STRING } from '../../constants/props';\nimport { makePropsConfigurable, makeProp } from '../../utils/props';\n\n// --- Props ---\n\nconst props = makePropsConfigurable({\n tag: makeProp(PROP_TYPE_STRING, 'div')\n}, NAME_INPUT_GROUP_TEXT);\n\n// --- Main component ---\n\n// @vue/component\nconst BInputGroupText = /*#__PURE__*/extend({\n name: NAME_INPUT_GROUP_TEXT,\n functional: true,\n props,\n render(h, _ref) {\n let {\n props,\n data,\n children\n } = _ref;\n return h(props.tag, mergeData(data, {\n staticClass: 'input-group-text'\n }), children);\n }\n});\n\nexport { BInputGroupText, props };\n","/**\n * This function is like `arrayIncludes` except that it accepts a comparator.\n *\n * @private\n * @param {Array} [array] The array to inspect.\n * @param {*} target The value to search for.\n * @param {Function} comparator The comparator invoked per element.\n * @returns {boolean} Returns `true` if `target` is found, else `false`.\n */\nfunction arrayIncludesWith(array, value, comparator) {\n var index = -1,\n length = array == null ? 0 : array.length;\n\n while (++index < length) {\n if (comparator(value, array[index])) {\n return true;\n }\n }\n return false;\n}\n\nmodule.exports = arrayIncludesWith;\n","import { __ } from '~/locale';\n\nclass RecentSearchesServiceError extends Error {\n constructor(message) {\n super(message || __('Recent Searches Service is unavailable'));\n this.name = 'RecentSearchesServiceError';\n }\n}\n\nexport default RecentSearchesServiceError;\n","import AccessorUtilities from '~/lib/utils/accessor';\nimport RecentSearchesServiceError from './recent_searches_service_error';\n\nclass RecentSearchesService {\n constructor(localStorageKey = 'issuable-recent-searches') {\n this.localStorageKey = localStorageKey;\n }\n\n fetch() {\n if (!RecentSearchesService.isAvailable()) {\n const error = new RecentSearchesServiceError();\n return Promise.reject(error);\n }\n\n const input = window.localStorage.getItem(this.localStorageKey);\n\n let searches = [];\n if (input && input.length > 0) {\n try {\n searches = JSON.parse(input);\n } catch (err) {\n return Promise.reject(err);\n }\n }\n\n return Promise.resolve(searches);\n }\n\n save(searches = []) {\n if (!RecentSearchesService.isAvailable()) return;\n\n window.localStorage.setItem(this.localStorageKey, JSON.stringify(searches));\n }\n\n static isAvailable() {\n return AccessorUtilities.canUseLocalStorage();\n }\n}\n\nexport default RecentSearchesService;\n","import { extend, mergeData } from '../../vue';\nimport { NAME_INPUT_GROUP_ADDON } from '../../constants/components';\nimport { PROP_TYPE_BOOLEAN, PROP_TYPE_STRING } from '../../constants/props';\nimport { makePropsConfigurable, makeProp } from '../../utils/props';\nimport { BInputGroupText } from './input-group-text';\n\n// --- Props ---\n\nconst props = makePropsConfigurable({\n append: makeProp(PROP_TYPE_BOOLEAN, false),\n id: makeProp(PROP_TYPE_STRING),\n isText: makeProp(PROP_TYPE_BOOLEAN, false),\n tag: makeProp(PROP_TYPE_STRING, 'div')\n}, NAME_INPUT_GROUP_ADDON);\n\n// --- Main component ---\n\n// @vue/component\nconst BInputGroupAddon = /*#__PURE__*/extend({\n name: NAME_INPUT_GROUP_ADDON,\n functional: true,\n props,\n render(h, _ref) {\n let {\n props,\n data,\n children\n } = _ref;\n const {\n append\n } = props;\n return h(props.tag, mergeData(data, {\n class: {\n 'input-group-append': append,\n 'input-group-prepend': !append\n },\n attrs: {\n id: props.id\n }\n }), props.isText ? [h(BInputGroupText, children)] : children);\n }\n});\n\nexport { BInputGroupAddon, props };\n","import { extend, mergeData } from '../../vue';\nimport { NAME_INPUT_GROUP_APPEND } from '../../constants/components';\nimport { omit } from '../../utils/object';\nimport { makePropsConfigurable } from '../../utils/props';\nimport { props as props$1, BInputGroupAddon } from './input-group-addon';\n\n// --- Props ---\n\nconst props = makePropsConfigurable(omit(props$1, ['append']), NAME_INPUT_GROUP_APPEND);\n\n// --- Main component ---\n\n// @vue/component\nconst BInputGroupAppend = /*#__PURE__*/extend({\n name: NAME_INPUT_GROUP_APPEND,\n functional: true,\n props,\n render(h, _ref) {\n let {\n props,\n data,\n children\n } = _ref;\n // Pass all our data down to child, and set `append` to `true`\n return h(BInputGroupAddon, mergeData(data, {\n props: {\n ...props,\n append: true\n }\n }), children);\n }\n});\n\nexport { BInputGroupAppend, props };\n","import { extend, mergeData } from '../../vue';\nimport { NAME_INPUT_GROUP_PREPEND } from '../../constants/components';\nimport { omit } from '../../utils/object';\nimport { makePropsConfigurable } from '../../utils/props';\nimport { props as props$1, BInputGroupAddon } from './input-group-addon';\n\n// --- Props ---\n\nconst props = makePropsConfigurable(omit(props$1, ['append']), NAME_INPUT_GROUP_PREPEND);\n\n// --- Main component ---\n\n// @vue/component\nconst BInputGroupPrepend = /*#__PURE__*/extend({\n name: NAME_INPUT_GROUP_PREPEND,\n functional: true,\n props,\n render(h, _ref) {\n let {\n props,\n data,\n children\n } = _ref;\n // Pass all our data down to child, and set `append` to `true`\n return h(BInputGroupAddon, mergeData(data, {\n props: {\n ...props,\n append: false\n }\n }), children);\n }\n});\n\nexport { BInputGroupPrepend, props };\n","import { extend, mergeData } from '../../vue';\nimport { NAME_INPUT_GROUP } from '../../constants/components';\nimport { PROP_TYPE_STRING } from '../../constants/props';\nimport { SLOT_NAME_PREPEND, SLOT_NAME_APPEND, SLOT_NAME_DEFAULT } from '../../constants/slots';\nimport { htmlOrText } from '../../utils/html';\nimport { hasNormalizedSlot, normalizeSlot } from '../../utils/normalize-slot';\nimport { makePropsConfigurable, makeProp } from '../../utils/props';\nimport { BInputGroupAppend } from './input-group-append';\nimport { BInputGroupPrepend } from './input-group-prepend';\nimport { BInputGroupText } from './input-group-text';\n\n// --- Props ---\n\nconst props = makePropsConfigurable({\n append: makeProp(PROP_TYPE_STRING),\n appendHtml: makeProp(PROP_TYPE_STRING),\n id: makeProp(PROP_TYPE_STRING),\n prepend: makeProp(PROP_TYPE_STRING),\n prependHtml: makeProp(PROP_TYPE_STRING),\n size: makeProp(PROP_TYPE_STRING),\n tag: makeProp(PROP_TYPE_STRING, 'div')\n}, NAME_INPUT_GROUP);\n\n// --- Main component ---\n\n// @vue/component\nconst BInputGroup = /*#__PURE__*/extend({\n name: NAME_INPUT_GROUP,\n functional: true,\n props,\n render(h, _ref) {\n let {\n props,\n data,\n slots,\n scopedSlots\n } = _ref;\n const {\n prepend,\n prependHtml,\n append,\n appendHtml,\n size\n } = props;\n const $scopedSlots = scopedSlots || {};\n const $slots = slots();\n const slotScope = {};\n let $prepend = h();\n const hasPrependSlot = hasNormalizedSlot(SLOT_NAME_PREPEND, $scopedSlots, $slots);\n if (hasPrependSlot || prepend || prependHtml) {\n $prepend = h(BInputGroupPrepend, [hasPrependSlot ? normalizeSlot(SLOT_NAME_PREPEND, slotScope, $scopedSlots, $slots) : h(BInputGroupText, {\n domProps: htmlOrText(prependHtml, prepend)\n })]);\n }\n let $append = h();\n const hasAppendSlot = hasNormalizedSlot(SLOT_NAME_APPEND, $scopedSlots, $slots);\n if (hasAppendSlot || append || appendHtml) {\n $append = h(BInputGroupAppend, [hasAppendSlot ? normalizeSlot(SLOT_NAME_APPEND, slotScope, $scopedSlots, $slots) : h(BInputGroupText, {\n domProps: htmlOrText(appendHtml, append)\n })]);\n }\n return h(props.tag, mergeData(data, {\n staticClass: 'input-group',\n class: {\n [`input-group-${size}`]: size\n },\n attrs: {\n id: props.id || null,\n role: 'group'\n }\n }), [$prepend, normalizeSlot(SLOT_NAME_DEFAULT, slotScope, $scopedSlots, $slots), $append]);\n }\n});\n\nexport { BInputGroup, props };\n","const InputGroupMixin = {\n props: {\n value: {\n type: [String, Number],\n default: ''\n }\n },\n data() {\n return {\n localValue: this.stringifyValue(this.value)\n };\n },\n watch: {\n value(newVal) {\n if (newVal !== this.localValue) {\n this.localValue = this.stringifyValue(newVal);\n }\n },\n localValue(newVal) {\n if (newVal !== this.value) {\n this.$emit('input', newVal);\n }\n }\n },\n mounted() {\n const value = this.stringifyValue(this.value);\n if (this.activeOption) {\n const activeOption = this.predefinedOptions.find(opt => opt.name === this.activeOption);\n this.localValue = activeOption.value;\n } else if (value !== this.localValue) {\n this.localValue = value;\n }\n },\n methods: {\n stringifyValue(value) {\n return value === undefined || value === null ? '' : String(value);\n }\n }\n};\n\nexport { InputGroupMixin };\n","import { BInputGroup } from '../../../../vendor/bootstrap-vue/src/components/input-group/input-group';\nimport { BInputGroupPrepend } from '../../../../vendor/bootstrap-vue/src/components/input-group/input-group-prepend';\nimport { BInputGroupAppend } from '../../../../vendor/bootstrap-vue/src/components/input-group/input-group-append';\nimport { BFormInput } from '../../../../vendor/bootstrap-vue/src/components/form-input/form-input';\nimport GlDropdown from '../../dropdown/dropdown';\nimport GlDropdownItem from '../../dropdown/dropdown_item';\nimport { InputGroupMixin } from './form_input_group_mixin';\nimport __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';\n\nvar script = {\n name: 'GlFormInputGroup',\n components: {\n BInputGroup,\n BInputGroupPrepend,\n BInputGroupAppend,\n BFormInput,\n GlDropdown,\n GlDropdownItem\n },\n mixins: [InputGroupMixin],\n props: {\n /**\n * Automatically selects the content of the input field on click.\n */\n selectOnClick: {\n type: Boolean,\n required: false,\n default: false\n },\n /**\n * Array of options. Each option should have `name` and `value` information: {name: \"Foo\", value: \"Bar\"})\n */\n predefinedOptions: {\n type: Array,\n required: false,\n default: () => [{\n value: '',\n name: ''\n }],\n validator: options => options.every(opt => Object.keys(opt).includes('name', 'value'))\n },\n label: {\n type: String,\n required: false,\n default: undefined\n },\n inputClass: {\n type: [String, Array, Object],\n required: false,\n default: ''\n }\n },\n data() {\n return {\n activeOption: this.predefinedOptions && this.predefinedOptions[0].name\n };\n },\n methods: {\n handleClick() {\n if (this.selectOnClick) {\n this.$refs.input.$el.select();\n }\n },\n updateValue(option) {\n const {\n name,\n value\n } = option;\n this.activeOption = name;\n this.localValue = value;\n }\n }\n};\n\n/* script */\nconst __vue_script__ = script;\n\n/* template */\nvar __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('b-input-group',[(_vm.activeOption || _vm.$scopedSlots.prepend)?_c('b-input-group-prepend',[_vm._t(\"prepend\"),_vm._v(\" \"),(_vm.activeOption)?_c('gl-dropdown',{attrs:{\"text\":_vm.activeOption}},_vm._l((_vm.predefinedOptions),function(option){return _c('gl-dropdown-item',{key:option.value,attrs:{\"is-check-item\":\"\",\"is-checked\":_vm.activeOption === option.name},on:{\"click\":function($event){return _vm.updateValue(option)}}},[_vm._v(\"\\n \"+_vm._s(option.name)+\"\\n \")])}),1):_vm._e()],2):_vm._e(),_vm._v(\" \"),_vm._t(\"default\",function(){return [_c('b-form-input',_vm._g(_vm._b({ref:\"input\",class:['gl-form-input', _vm.inputClass],attrs:{\"aria-label\":_vm.label},on:{\"click\":_vm.handleClick},model:{value:(_vm.localValue),callback:function ($$v) {_vm.localValue=$$v;},expression:\"localValue\"}},'b-form-input',_vm.$attrs,false),_vm.$listeners))]}),_vm._v(\" \"),(_vm.$scopedSlots.append)?_c('b-input-group-append',[_vm._t(\"append\")],2):_vm._e()],2)};\nvar __vue_staticRenderFns__ = [];\n\n /* style */\n const __vue_inject_styles__ = undefined;\n /* scoped */\n const __vue_scope_id__ = undefined;\n /* module identifier */\n const __vue_module_identifier__ = undefined;\n /* functional template */\n const __vue_is_functional_template__ = false;\n /* style inject */\n \n /* style inject SSR */\n \n /* style inject shadow dom */\n \n\n \n const __vue_component__ = __vue_normalize__(\n { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },\n __vue_inject_styles__,\n __vue_script__,\n __vue_scope_id__,\n __vue_is_functional_template__,\n __vue_module_identifier__,\n false,\n undefined,\n undefined,\n undefined\n );\n\nexport default __vue_component__;\n","var Set = require('./_Set'),\n noop = require('./noop'),\n setToArray = require('./_setToArray');\n\n/** Used as references for various `Number` constants. */\nvar INFINITY = 1 / 0;\n\n/**\n * Creates a set object of `values`.\n *\n * @private\n * @param {Array} values The values to add to the set.\n * @returns {Object} Returns the new set.\n */\nvar createSet = !(Set && (1 / setToArray(new Set([,-0]))[1]) == INFINITY) ? noop : function(values) {\n return new Set(values);\n};\n\nmodule.exports = createSet;\n","import { isEmpty, uniqWith, isEqual, isString } from 'lodash';\nimport AccessorUtilities from '~/lib/utils/accessor';\nimport { queryToObject } from '~/lib/utils/url_utility';\n\nimport { MAX_RECENT_TOKENS_SIZE, FILTERED_SEARCH_TERM } from './constants';\n\n/**\n * This method removes duplicate tokens from tokens array.\n *\n * @param {Array} tokens Array of tokens as defined by `GlFilteredSearch`\n *\n * @returns {Array} Unique array of tokens\n */\nexport const uniqueTokens = (tokens) => {\n const knownTokens = [];\n return tokens.reduce((uniques, token) => {\n if (typeof token === 'object' && token.type !== FILTERED_SEARCH_TERM) {\n const tokenString = `${token.type}${token.value.operator}${token.value.data}`;\n if (!knownTokens.includes(tokenString)) {\n uniques.push(token);\n knownTokens.push(tokenString);\n }\n } else {\n uniques.push(token);\n }\n return uniques;\n }, []);\n};\n\n/**\n * Creates a token from a type and a filter. Example returned object\n * { type: 'myType', value: { data: 'myData', operator: '= '} }\n * @param {String} type the name of the filter\n * @param {Object}\n * @param {Object.value} filter value to be returned as token data\n * @param {Object.operator} filter operator to be retuned as token operator\n * @return {Object}\n * @return {Object.type} token type\n * @return {Object.value} token value\n */\nfunction createToken(type, filter) {\n return { type, value: { data: filter.value, operator: filter.operator } };\n}\n\n/**\n * This function takes a filter object and translates it into a token array\n * @param {Object} filters\n * @param {Object.myFilterName} a single filter value or an array of filters\n * @return {Array} tokens an array of tokens created from filter values\n */\nexport function prepareTokens(filters = {}) {\n return Object.keys(filters).reduce((memo, key) => {\n const value = filters[key];\n if (!value) {\n return memo;\n }\n if (Array.isArray(value)) {\n return [...memo, ...value.map((filterValue) => createToken(key, filterValue))];\n }\n\n return [...memo, createToken(key, value)];\n }, []);\n}\n\n/**\n * This function takes a token array and translates it into a filter object\n * @param filters\n * @returns A Filter Object\n */\nexport function processFilters(filters) {\n return filters.reduce((acc, token) => {\n let type;\n let value;\n let operator;\n if (typeof token === 'string') {\n type = FILTERED_SEARCH_TERM;\n value = token;\n } else {\n type = token?.type;\n operator = token?.value?.operator;\n value = token?.value?.data;\n }\n\n if (!acc[type]) {\n acc[type] = [];\n }\n\n acc[type].push({ value, operator });\n return acc;\n }, {});\n}\n\nfunction filteredSearchQueryParam(filter) {\n return filter\n .map(({ value }) => value)\n .join(' ')\n .trim();\n}\n\n/**\n * This function takes a filter object and maps it into a query object. Example filter:\n * { myFilterName: { value: 'foo', operator: '=' }, search: [{ value: 'my' }, { value: 'search' }] }\n * gets translated into:\n * { myFilterName: 'foo', 'not[myFilterName]': null, search: 'my search' }\n * By default it supports '=' and '!=' operators. This can be extended by providing the `customOperators` option\n * @param {Object} filters\n * @param {Object} filters.myFilterName a single filter value or an array of filters\n * @param {Object} options\n * @param {Object} [options.filteredSearchTermKey] if set, 'filtered-search-term' filters are assigned to this key, 'search' is suggested\n * @param {Object} [options.customOperators] Allows to extend the supported operators, e.g.\n *\n * filterToQueryObject({foo: [{ value: '100', operator: '>' }]}, {customOperators: {operator: '>',prefix: 'gt'}})\n * returns {gt[foo]: '100'}\n * It's also possible to restrict custom operators to a given key by setting `applyOnlyToKey` string attribute.\n *\n * @return {Object} query object with both filter name and not-name with values\n */\nexport function filterToQueryObject(filters = {}, options = {}) {\n const { filteredSearchTermKey, customOperators, shouldExcludeEmpty = false } = options;\n\n return Object.keys(filters).reduce((memo, key) => {\n const filter = filters[key];\n\n if (typeof filteredSearchTermKey === 'string' && key === FILTERED_SEARCH_TERM && filter) {\n const combinedFilteredSearchTerm = filteredSearchQueryParam(filter);\n if (combinedFilteredSearchTerm === '' && shouldExcludeEmpty) {\n return memo;\n }\n\n return { ...memo, [filteredSearchTermKey]: filteredSearchQueryParam(filter) };\n }\n\n const operators = [\n { operator: '=' },\n { operator: '!=', prefix: 'not' },\n ...(customOperators ?? []),\n ];\n\n const result = {};\n\n for (const op of operators) {\n const { operator, prefix, applyOnlyToKey } = op;\n\n if (!applyOnlyToKey || applyOnlyToKey === key) {\n let value;\n if (Array.isArray(filter)) {\n value = filter.filter((item) => item.operator === operator).map((item) => item.value);\n } else {\n value = filter?.operator === operator ? filter.value : null;\n }\n\n if (isEmpty(value)) {\n value = null;\n }\n\n if (shouldExcludeEmpty && (value?.[0] === '' || value === '' || value === null)) {\n // eslint-disable-next-line no-continue\n continue;\n }\n\n if (prefix) {\n result[`${prefix}[${key}]`] = value;\n } else {\n result[key] = value;\n }\n }\n }\n\n return { ...memo, ...result };\n }, {});\n}\n\n/**\n * Extracts filter name from url name and operator, e.g.\n * e.g. input: not[my_filter]` output: {filterName: `my_filter`, operator: '!='}`\n *\n * By default it supports filter with the format `my_filter=foo` and `not[my_filter]=bar`. This can be extended with the `customOperators` option.\n * @param {String} filterName from url\n * @param {Object.customOperators} It allows to extend the supported parameter, e.g.\n * input: 'gt[filter]', { customOperators: [{ operator: '>', prefix: 'gt' }]})\n * output: '{filterName: 'filter', operator: '>'}\n * @return {Object}\n * @return {Object.filterName} extracted filter name\n * @return {Object.operator} `=` or `!=`\n */\nfunction extractNameAndOperator(filterName, customOperators) {\n const ops = [\n {\n prefix: 'not',\n operator: '!=',\n },\n ...(customOperators ?? []),\n ];\n\n const operator = ops.find(\n ({ prefix }) => filterName.startsWith(`${prefix}[`) && filterName.endsWith(']'),\n );\n\n if (!operator) {\n return { filterName, operator: '=' };\n }\n const { prefix } = operator;\n return { filterName: filterName.slice(prefix.length + 1, -1), operator: operator.operator };\n}\n\n/**\n * Gathers search term as values\n * @param {String|Array} value\n * @returns {Array} List of search terms split by word\n */\nfunction filteredSearchTermValue(value) {\n const values = Array.isArray(value) ? value : [value];\n return [{ value: values.filter((term) => term).join(' ') }];\n}\n\n/**\n * This function takes a URL query string and maps it into a filter object. Example query string:\n * '?myFilterName=foo'\n * gets translated into:\n * { myFilterName: { value: 'foo', operator: '=' } }\n * By default it only support '=' and '!=' operators. This can be extended with the customOperator option.\n * @param {String|Object} query URL query string or object, e.g. from `window.location.search` or `this.$route.query`\n * @param {Object} options\n * @param {String} [options.filteredSearchTermKey] if set, a FILTERED_SEARCH_TERM filter is created to this parameter. `'search'` is suggested\n * @param {String[]} [options.filterNamesAllowList] if set, only this list of filters names is mapped\n * @param {Object} [options.customOperator] It allows to extend the supported parameter, e.g.\n * input: 'gt[myFilter]=100', { customOperators: [{ operator: '>', prefix: 'gt' }]})\n * output: '{ myFilter: {value: '100', operator: '>'}}\n * @return {Object} filter object with filter names and their values\n */\nexport function urlQueryToFilter(\n query = '',\n { filteredSearchTermKey, filterNamesAllowList, customOperators } = {},\n) {\n const filters = isString(query) ? queryToObject(query, { gatherArrays: true }) : query;\n return Object.keys(filters).reduce((memo, key) => {\n const value = filters[key];\n if (!value) {\n return memo;\n }\n if (key === filteredSearchTermKey) {\n return {\n ...memo,\n [FILTERED_SEARCH_TERM]: filteredSearchTermValue(value),\n };\n }\n\n const { filterName, operator } = extractNameAndOperator(key, customOperators);\n if (filterNamesAllowList && !filterNamesAllowList.includes(filterName)) {\n return memo;\n }\n let previousValues = [];\n if (Array.isArray(memo[filterName])) {\n previousValues = memo[filterName];\n }\n if (Array.isArray(value)) {\n const newAdditions = value.filter(Boolean).map((item) => ({ value: item, operator }));\n return { ...memo, [filterName]: [...previousValues, ...newAdditions] };\n }\n\n return { ...memo, [filterName]: { value, operator } };\n }, {});\n}\n\n/**\n * Returns array of token values from localStorage\n * based on provided recentSuggestionsStorageKey\n *\n * @param {String} recentSuggestionsStorageKey\n * @param {Array} appliedTokens\n * @param {Function} valueIdentifier\n * @returns\n */\nexport function getRecentlyUsedSuggestions(\n recentSuggestionsStorageKey,\n appliedTokens,\n valueIdentifier,\n) {\n let recentlyUsedSuggestions = [];\n if (AccessorUtilities.canUseLocalStorage()) {\n recentlyUsedSuggestions = JSON.parse(localStorage.getItem(recentSuggestionsStorageKey)) || [];\n }\n return recentlyUsedSuggestions.filter((suggestion) => {\n return !appliedTokens?.some(\n (appliedToken) => appliedToken.value.data === valueIdentifier(suggestion),\n );\n });\n}\n\n/**\n * Sets provided token value to recently used array\n * within localStorage for provided recentSuggestionsStorageKey\n *\n * @param {String} recentSuggestionsStorageKey\n * @param {Object} tokenValue\n */\nexport function setTokenValueToRecentlyUsed(recentSuggestionsStorageKey, tokenValue) {\n const recentlyUsedSuggestions = getRecentlyUsedSuggestions(recentSuggestionsStorageKey);\n\n recentlyUsedSuggestions.splice(0, 0, { ...tokenValue });\n\n if (AccessorUtilities.canUseLocalStorage()) {\n localStorage.setItem(\n recentSuggestionsStorageKey,\n JSON.stringify(uniqWith(recentlyUsedSuggestions, isEqual).slice(0, MAX_RECENT_TOKENS_SIZE)),\n );\n }\n}\n\n/**\n * Removes `FILTERED_SEARCH_TERM` tokens with empty data\n *\n * @param filterTokens array of filtered search tokens\n * @return {Array} array of filtered search tokens\n */\nexport const filterEmptySearchTerm = (filterTokens = []) =>\n filterTokens.filter((token) => token.type === FILTERED_SEARCH_TERM && token.value.data);\n","import { uniqWith, isEqual } from 'lodash';\n\nimport { MAX_HISTORY_SIZE } from '../constants';\n\nclass RecentSearchesStore {\n constructor(initialState = {}, allowedKeys) {\n this.state = {\n isLocalStorageAvailable: true,\n recentSearches: [],\n allowedKeys,\n ...initialState,\n };\n }\n\n addRecentSearch(newSearch) {\n this.setRecentSearches([newSearch].concat(this.state.recentSearches));\n\n return this.state.recentSearches;\n }\n\n setRecentSearches(searches = []) {\n const trimmedSearches = searches.map((search) =>\n typeof search === 'string' ? search.trim() : search,\n );\n\n // Do object equality check to remove duplicates.\n this.state.recentSearches = uniqWith(trimmedSearches, isEqual).slice(0, MAX_HISTORY_SIZE);\n return this.state.recentSearches;\n }\n}\n\nexport default RecentSearchesStore;\n","var SetCache = require('./_SetCache'),\n arrayIncludes = require('./_arrayIncludes'),\n arrayIncludesWith = require('./_arrayIncludesWith'),\n cacheHas = require('./_cacheHas'),\n createSet = require('./_createSet'),\n setToArray = require('./_setToArray');\n\n/** Used as the size to enable large array optimizations. */\nvar LARGE_ARRAY_SIZE = 200;\n\n/**\n * The base implementation of `_.uniqBy` without support for iteratee shorthands.\n *\n * @private\n * @param {Array} array The array to inspect.\n * @param {Function} [iteratee] The iteratee invoked per element.\n * @param {Function} [comparator] The comparator invoked per element.\n * @returns {Array} Returns the new duplicate free array.\n */\nfunction baseUniq(array, iteratee, comparator) {\n var index = -1,\n includes = arrayIncludes,\n length = array.length,\n isCommon = true,\n result = [],\n seen = result;\n\n if (comparator) {\n isCommon = false;\n includes = arrayIncludesWith;\n }\n else if (length >= LARGE_ARRAY_SIZE) {\n var set = iteratee ? null : createSet(array);\n if (set) {\n return setToArray(set);\n }\n isCommon = false;\n includes = cacheHas;\n seen = new SetCache;\n }\n else {\n seen = iteratee ? [] : result;\n }\n outer:\n while (++index < length) {\n var value = array[index],\n computed = iteratee ? iteratee(value) : value;\n\n value = (comparator || value !== 0) ? value : 0;\n if (isCommon && computed === computed) {\n var seenIndex = seen.length;\n while (seenIndex--) {\n if (seen[seenIndex] === computed) {\n continue outer;\n }\n }\n if (iteratee) {\n seen.push(computed);\n }\n result.push(value);\n }\n else if (!includes(seen, computed, comparator)) {\n if (seen !== result) {\n seen.push(computed);\n }\n result.push(value);\n }\n }\n return result;\n}\n\nmodule.exports = baseUniq;\n","var baseRest = require('./_baseRest'),\n isIterateeCall = require('./_isIterateeCall');\n\n/**\n * Creates a function like `_.assign`.\n *\n * @private\n * @param {Function} assigner The function to assign values.\n * @returns {Function} Returns the new assigner function.\n */\nfunction createAssigner(assigner) {\n return baseRest(function(object, sources) {\n var index = -1,\n length = sources.length,\n customizer = length > 1 ? sources[length - 1] : undefined,\n guard = length > 2 ? sources[2] : undefined;\n\n customizer = (assigner.length > 3 && typeof customizer == 'function')\n ? (length--, customizer)\n : undefined;\n\n if (guard && isIterateeCall(sources[0], sources[1], guard)) {\n customizer = length < 3 ? undefined : customizer;\n length = 1;\n }\n object = Object(object);\n while (++index < length) {\n var source = sources[index];\n if (source) {\n assigner(object, source, index, customizer);\n }\n }\n return object;\n });\n}\n\nmodule.exports = createAssigner;\n","import { GlTooltipDirective } from '../../../directives/tooltip';\nimport GlButton from '../button/button';\nimport GlButtonGroup from '../button_group/button_group';\nimport GlCollapsibleListbox from '../new_dropdowns/listbox/listbox';\nimport { isOption } from '../new_dropdowns/listbox/utils';\nimport { translate } from '../../../utils/i18n';\nimport __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';\n\nvar script = {\n name: 'GlSorting',\n components: {\n GlButton,\n GlButtonGroup,\n GlCollapsibleListbox\n },\n directives: {\n GlTooltip: GlTooltipDirective\n },\n props: {\n /**\n * Text to place in the toggle button.\n */\n text: {\n type: String,\n required: false,\n default: ''\n },\n /**\n * Sort options to display in the dropdown\n */\n sortOptions: {\n type: Array,\n required: false,\n default: () => [],\n // eslint-disable-next-line unicorn/no-array-callback-reference\n validator: sortOptions => sortOptions.every(isOption)\n },\n /**\n * The value of the item currently selected in the dropdown.\n * Only to be used with the `sortOptions` prop.\n */\n sortBy: {\n type: [String, Number],\n required: false,\n default: null\n },\n /**\n * Determines the current sort order icon displayed.\n */\n isAscending: {\n type: Boolean,\n required: false,\n default: false\n },\n /**\n * The text for the tooltip and aria-label of the sort direction toggle\n * button instead of the defaults for ascending/descending.\n */\n sortDirectionToolTip: {\n type: String,\n required: false,\n default: null\n },\n /**\n * Additional class(es) to apply to the root element of the GlCollapsibleListbox.\n */\n dropdownClass: {\n type: String,\n required: false,\n default: ''\n },\n /**\n * Additional class(es) to apply to the dropdown toggle.\n */\n dropdownToggleClass: {\n type: String,\n required: false,\n default: ''\n },\n /**\n * Additional class(es) to apply to the sort direction toggle button.\n */\n sortDirectionToggleClass: {\n type: String,\n required: false,\n default: ''\n },\n /**\n * Render the dropdown toggle button as a block element\n */\n block: {\n type: Boolean,\n required: false,\n default: false\n }\n },\n computed: {\n localSortDirection() {\n return this.isAscending ? 'sort-lowest' : 'sort-highest';\n },\n sortDirectionText() {\n if (this.sortDirectionToolTip) return this.sortDirectionToolTip;\n return this.isAscending ? translate('GlSorting.sortAscending', 'Sort direction: ascending') : translate('GlSorting.sortDescending', 'Sort direction: descending');\n }\n },\n methods: {\n toggleSortDirection() {\n const newDirection = !this.isAscending;\n\n /**\n * Emitted when the sort direction button is clicked.\n *\n * The event's payload will be true if the direction has been changed to\n * ascending, or false if descending.\n *\n * @property {boolean} isAscending\n */\n this.$emit('sortDirectionChange', newDirection);\n },\n onSortByChanged(event) {\n /**\n * Emitted when the sort field is changed.\n *\n * The event's payload is the value of the selected sort field.\n *\n * Only emitted when using the `sortOptions` prop.\n *\n * @property {string|number} value\n */\n this.$emit('sortByChange', event);\n }\n }\n};\n\n/* script */\nconst __vue_script__ = script;\n\n/* template */\nvar __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-button-group',{staticClass:\"gl-sorting\"},[_c('gl-collapsible-listbox',{class:_vm.dropdownClass,attrs:{\"toggle-text\":_vm.text,\"items\":_vm.sortOptions,\"selected\":_vm.sortBy,\"toggle-class\":_vm.dropdownToggleClass,\"placement\":\"bottom-end\",\"block\":_vm.block},on:{\"select\":_vm.onSortByChanged}}),_vm._v(\" \"),_c('gl-button',{directives:[{name:\"gl-tooltip\",rawName:\"v-gl-tooltip\"}],class:['sorting-direction-button', _vm.sortDirectionToggleClass],attrs:{\"title\":_vm.sortDirectionText,\"icon\":_vm.localSortDirection,\"aria-label\":_vm.sortDirectionText},on:{\"click\":_vm.toggleSortDirection}})],1)};\nvar __vue_staticRenderFns__ = [];\n\n /* style */\n const __vue_inject_styles__ = undefined;\n /* scoped */\n const __vue_scope_id__ = undefined;\n /* module identifier */\n const __vue_module_identifier__ = undefined;\n /* functional template */\n const __vue_is_functional_template__ = false;\n /* style inject */\n \n /* style inject SSR */\n \n /* style inject shadow dom */\n \n\n \n const __vue_component__ = __vue_normalize__(\n { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },\n __vue_inject_styles__,\n __vue_script__,\n __vue_scope_id__,\n __vue_is_functional_template__,\n __vue_module_identifier__,\n false,\n undefined,\n undefined,\n undefined\n );\n\nexport default __vue_component__;\n","import {\n TOKEN_TYPE_APPROVED_BY,\n TOKEN_TYPE_MERGE_USER,\n TOKEN_TYPE_ASSIGNEE,\n TOKEN_TYPE_AUTHOR,\n TOKEN_TYPE_REVIEWER,\n} from '~/vue_shared/components/filtered_search_bar/constants';\n\nexport const USER_TOKEN_TYPES = [\n TOKEN_TYPE_AUTHOR,\n TOKEN_TYPE_ASSIGNEE,\n TOKEN_TYPE_APPROVED_BY,\n TOKEN_TYPE_MERGE_USER,\n TOKEN_TYPE_REVIEWER,\n 'attention',\n];\n\nexport const DROPDOWN_TYPE = {\n hint: 'hint',\n operator: 'operator',\n};\n\nexport const FILTER_TYPE = {\n none: 'none',\n any: 'any',\n};\n\nexport const MAX_HISTORY_SIZE = 5;\n\nexport const FILTERED_SEARCH = {\n MERGE_REQUESTS: 'merge_requests',\n ISSUES: 'issues',\n};\n","var baseUniq = require('./_baseUniq');\n\n/**\n * This method is like `_.uniq` except that it accepts `comparator` which\n * is invoked to compare elements of `array`. The order of result values is\n * determined by the order they occur in the array.The comparator is invoked\n * with two arguments: (arrVal, othVal).\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Array\n * @param {Array} array The array to inspect.\n * @param {Function} [comparator] The comparator invoked per element.\n * @returns {Array} Returns the new duplicate free array.\n * @example\n *\n * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];\n *\n * _.uniqWith(objects, _.isEqual);\n * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]\n */\nfunction uniqWith(array, comparator) {\n comparator = typeof comparator == 'function' ? comparator : undefined;\n return (array && array.length) ? baseUniq(array, undefined, comparator) : [];\n}\n\nmodule.exports = uniqWith;\n","export const RECENT_SEARCHES_STORAGE_KEY_GROUPS = 'groups';\nexport const RECENT_SEARCHES_STORAGE_KEY_PROJECTS = 'projects';\n\nexport default {\n issues: 'issue-recent-searches',\n merge_requests: 'merge-request-recent-searches',\n group_members: 'group-members-recent-searches',\n group_invited_members: 'group-invited-members-recent-searches',\n project_members: 'project-members-recent-searches',\n project_group_links: 'project-group-links-recent-searches',\n [RECENT_SEARCHES_STORAGE_KEY_GROUPS]: 'groups-recent-searches',\n [RECENT_SEARCHES_STORAGE_KEY_PROJECTS]: 'projects-recent-searches',\n};\n","import { GlTooltipDirective } from '../../../directives/tooltip';\nimport GlClearIconButton from '../../shared_components/clear_icon_button/clear_icon_button';\nimport GlButton from '../button/button';\nimport GlDisclosureDropdown from '../new_dropdowns/disclosure/disclosure_dropdown';\nimport GlDisclosureDropdownItem from '../new_dropdowns/disclosure/disclosure_dropdown_item';\nimport GlFormInput from '../form/form_input/form_input';\nimport GlFormInputGroup from '../form/form_input_group/form_input_group';\nimport __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';\n\nvar script = {\n name: 'GlSearchboxByClick',\n components: {\n GlClearIconButton,\n GlButton,\n GlFormInput,\n GlDisclosureDropdown,\n GlDisclosureDropdownItem,\n GlFormInputGroup\n },\n directives: {\n GlTooltip: GlTooltipDirective\n },\n props: {\n /**\n * If provided, used as value of search input\n */\n value: {\n required: false,\n default: '',\n // SearchBoxByClick could serve as a container for complex fields (see GlFilteredSearch)\n // so we should not force any specific type for value here\n validator: () => true\n },\n /**\n * If provided, used as history items for this component\n */\n historyItems: {\n type: Array,\n required: false,\n default: null\n },\n /**\n * If provided, used as a placeholder for this component\n */\n placeholder: {\n type: String,\n required: false,\n default: 'Search'\n },\n clearable: {\n type: Boolean,\n required: false,\n default: true\n },\n /**\n * If provided and true, disables the input and controls\n */\n disabled: {\n type: Boolean,\n required: false,\n default: false\n },\n /**\n * i18n for recent searches title within history dropdown\n */\n recentSearchesHeader: {\n type: String,\n required: false,\n default: 'Recent searches'\n },\n /**\n * i18n for clear button title\n */\n clearButtonTitle: {\n type: String,\n required: false,\n default: 'Clear'\n },\n /**\n * i18n for close button title within history dropdown\n */\n closeButtonTitle: {\n type: String,\n required: false,\n default: 'Close'\n },\n /**\n * i18n for recent searches clear text\n */\n clearRecentSearchesText: {\n type: String,\n required: false,\n default: 'Clear recent searches'\n },\n noRecentSearchesText: {\n type: String,\n required: false,\n default: \"You don't have any recent searches\"\n },\n /**\n * Container for tooltip. Valid values: DOM node, selector string or `false` for default\n */\n tooltipContainer: {\n required: false,\n default: false,\n validator: value => value === false || typeof value === 'string' || value instanceof HTMLElement\n },\n /**\n * HTML attributes to add to the search button\n */\n searchButtonAttributes: {\n type: Object,\n required: false,\n default: () => ({})\n },\n /**\n * Display search button to perform a search.\n *\n * Note: it is required to ensure accessibility for WCAG 2.1 3.2.2: On Input.\n * If the search button is hidden, a separate button should be provided for the same context.\n */\n showSearchButton: {\n type: Boolean,\n required: false,\n default: true\n }\n },\n data() {\n return {\n currentValue: null,\n isFocused: false\n };\n },\n computed: {\n inputAttributes() {\n const attributes = {\n type: 'search',\n placeholder: this.placeholder,\n ...this.$attrs\n };\n if (!attributes['aria-label']) {\n attributes['aria-label'] = attributes.placeholder;\n }\n return attributes;\n },\n hasValue() {\n return Boolean(this.currentValue);\n }\n },\n watch: {\n value: {\n handler(newValue) {\n this.currentValue = newValue;\n },\n immediate: true\n },\n currentValue(newValue) {\n if (newValue === this.value) return;\n this.$emit('input', newValue);\n }\n },\n methods: {\n search(value) {\n /**\n * Emitted when search is submitted\n * @property {*} value Search value\n */\n this.$emit('submit', value);\n },\n selectHistoryItem(item) {\n this.currentValue = item;\n\n /**\n * Emitted when item from history is selected\n * @property {*} item History item\n */\n this.$emit('history-item-selected', item);\n setTimeout(() => {\n document.activeElement.blur();\n });\n },\n clearInput() {\n this.currentValue = '';\n /**\n * Emitted when search is cleared\n */\n this.$emit('clear');\n if (this.$refs.input) {\n this.$refs.input.$el.focus();\n }\n },\n emitClearHistory() {\n /**\n * Emitted when clear history button is clicked\n */\n this.$emit('clear-history');\n }\n }\n};\n\n/* script */\nconst __vue_script__ = script;\n\n/* template */\nvar __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-form-input-group',{staticClass:\"gl-search-box-by-click\",class:{ 'gl-search-box-by-click-with-search-button': _vm.showSearchButton },scopedSlots:_vm._u([(_vm.historyItems)?{key:\"prepend\",fn:function(){return [_c('gl-disclosure-dropdown',{ref:\"historyDropdown\",staticClass:\"gl-search-box-by-click-history\",attrs:{\"icon\":\"history\",\"category\":\"tertiary\",\"toggle-text\":\"Toggle history\",\"text-sr-only\":\"\",\"fluid-width\":\"\",\"disabled\":_vm.disabled},scopedSlots:_vm._u([{key:\"header\",fn:function(){return [_c('div',{staticClass:\"gl-search-box-by-click-history-header gl-flex gl-min-h-8 gl-grow gl-items-center gl-border-b-1 gl-border-b-dropdown !gl-p-4 gl-text-sm gl-font-bold gl-border-b-solid\"},[_vm._v(\"\\n \"+_vm._s(_vm.recentSearchesHeader)+\"\\n \")])]},proxy:true},(_vm.historyItems.length)?{key:\"footer\",fn:function(){return [_c('div',{staticClass:\"gl-flex gl-flex-col gl-border-t-1 gl-border-t-dropdown gl-p-2 gl-border-t-solid\"},[_c('gl-button',{ref:\"clearHistory\",staticClass:\"!gl-justify-start\",attrs:{\"category\":\"tertiary\"},on:{\"click\":_vm.emitClearHistory}},[_vm._v(\"\\n \"+_vm._s(_vm.clearRecentSearchesText)+\"\\n \")])],1)]},proxy:true}:null],null,true)},[_vm._v(\" \"),(_vm.historyItems.length)?_vm._l((_vm.historyItems),function(item,idx){return _c('gl-disclosure-dropdown-item',{key:idx,staticClass:\"gl-search-box-by-click-history-item\",on:{\"action\":function($event){return _vm.selectHistoryItem(item)}},scopedSlots:_vm._u([{key:\"list-item\",fn:function(){return [_vm._t(\"history-item\",function(){return [_vm._v(_vm._s(item))]},{\"historyItem\":item})]},proxy:true}],null,true)})}):_c('div',{staticClass:\"gl-px-4 gl-py-2 gl-text-sm gl-text-subtle\"},[_vm._v(\"\\n \"+_vm._s(_vm.noRecentSearchesText)+\"\\n \")])],2)]},proxy:true}:null,(_vm.showSearchButton)?{key:\"append\",fn:function(){return [_c('gl-button',_vm._b({ref:\"searchButton\",staticClass:\"gl-search-box-by-click-search-button\",attrs:{\"category\":\"tertiary\",\"icon\":\"search\",\"disabled\":_vm.disabled,\"aria-label\":\"Search\",\"data-testid\":\"search-button\"},on:{\"click\":function($event){return _vm.search(_vm.currentValue)}}},'gl-button',_vm.searchButtonAttributes,false))]},proxy:true}:null],null,true)},[_vm._v(\" \"),_vm._t(\"input\",function(){return [_c('gl-form-input',_vm._b({ref:\"input\",staticClass:\"gl-search-box-by-click-input\",class:{ '!gl-rounded-base': !_vm.showSearchButton },attrs:{\"disabled\":_vm.disabled},on:{\"focus\":function($event){_vm.isFocused = true;},\"blur\":function($event){_vm.isFocused = false;}},nativeOn:{\"keydown\":function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,\"enter\",13,$event.key,\"Enter\")){ return null; }return _vm.search(_vm.currentValue)}},model:{value:(_vm.currentValue),callback:function ($$v) {_vm.currentValue=$$v;},expression:\"currentValue\"}},'gl-form-input',_vm.inputAttributes,false))]}),_vm._v(\" \"),(_vm.clearable && _vm.hasValue && !_vm.disabled)?_c('gl-clear-icon-button',{staticClass:\"gl-search-box-by-click-icon-button gl-search-box-by-click-clear-button gl-clear-icon-button\",attrs:{\"title\":_vm.clearButtonTitle,\"tooltip-container\":_vm.tooltipContainer,\"data-testid\":\"filtered-search-clear-button\"},on:{\"click\":_vm.clearInput}}):_vm._e()],2)};\nvar __vue_staticRenderFns__ = [];\n\n /* style */\n const __vue_inject_styles__ = undefined;\n /* scoped */\n const __vue_scope_id__ = undefined;\n /* module identifier */\n const __vue_module_identifier__ = undefined;\n /* functional template */\n const __vue_is_functional_template__ = false;\n /* style inject */\n \n /* style inject SSR */\n \n /* style inject shadow dom */\n \n\n \n const __vue_component__ = __vue_normalize__(\n { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },\n __vue_inject_styles__,\n __vue_script__,\n __vue_scope_id__,\n __vue_is_functional_template__,\n __vue_module_identifier__,\n false,\n undefined,\n undefined,\n undefined\n );\n\nexport default __vue_component__;\n","var assignValue = require('./_assignValue'),\n baseAssignValue = require('./_baseAssignValue');\n\n/**\n * Copies properties of `source` to `object`.\n *\n * @private\n * @param {Object} source The object to copy properties from.\n * @param {Array} props The property identifiers to copy.\n * @param {Object} [object={}] The object to copy properties to.\n * @param {Function} [customizer] The function to customize copied values.\n * @returns {Object} Returns `object`.\n */\nfunction copyObject(source, props, object, customizer) {\n var isNew = !object;\n object || (object = {});\n\n var index = -1,\n length = props.length;\n\n while (++index < length) {\n var key = props[index];\n\n var newValue = customizer\n ? customizer(object[key], source[key], key, object, source)\n : undefined;\n\n if (newValue === undefined) {\n newValue = source[key];\n }\n if (isNew) {\n baseAssignValue(object, key, newValue);\n } else {\n assignValue(object, key, newValue);\n }\n }\n return object;\n}\n\nmodule.exports = copyObject;\n"],"sourceRoot":""}