Prettify environments feature_highlight and filtered_search modules
This commit is contained in:
parent
4d0db16f97
commit
8b090caf82
22 changed files with 452 additions and 398 deletions
|
@ -1,40 +1,40 @@
|
|||
<script>
|
||||
import tablePagination from '../../vue_shared/components/table_pagination.vue';
|
||||
import environmentTable from '../components/environments_table.vue';
|
||||
import tablePagination from '../../vue_shared/components/table_pagination.vue';
|
||||
import environmentTable from '../components/environments_table.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
environmentTable,
|
||||
tablePagination,
|
||||
export default {
|
||||
components: {
|
||||
environmentTable,
|
||||
tablePagination,
|
||||
},
|
||||
props: {
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
environments: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
pagination: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
canCreateDeployment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
canReadEnvironment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
environments: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
methods: {
|
||||
onChangePage(page) {
|
||||
this.$emit('onChangePage', page);
|
||||
},
|
||||
pagination: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
};
|
||||
canCreateDeployment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
canReadEnvironment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onChangePage(page) {
|
||||
this.$emit('onChangePage', page);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
<script>
|
||||
export default {
|
||||
name: 'EnvironmentsEmptyState',
|
||||
props: {
|
||||
newPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
canCreateEnvironment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
helpPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
export default {
|
||||
name: 'EnvironmentsEmptyState',
|
||||
props: {
|
||||
newPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
};
|
||||
canCreateEnvironment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
helpPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="blank-state-row">
|
||||
|
|
|
@ -38,7 +38,9 @@ export default {
|
|||
|
||||
computed: {
|
||||
title() {
|
||||
return this.isLastDeployment ? s__('Environments|Re-deploy to environment') : s__('Environments|Rollback environment');
|
||||
return this.isLastDeployment
|
||||
? s__('Environments|Re-deploy to environment')
|
||||
: s__('Environments|Rollback environment');
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -5,31 +5,32 @@ import Translate from '../../vue_shared/translate';
|
|||
|
||||
Vue.use(Translate);
|
||||
|
||||
export default () => new Vue({
|
||||
el: '#environments-folder-list-view',
|
||||
components: {
|
||||
environmentsFolderApp,
|
||||
},
|
||||
data() {
|
||||
const environmentsData = document.querySelector(this.$options.el).dataset;
|
||||
export default () =>
|
||||
new Vue({
|
||||
el: '#environments-folder-list-view',
|
||||
components: {
|
||||
environmentsFolderApp,
|
||||
},
|
||||
data() {
|
||||
const environmentsData = document.querySelector(this.$options.el).dataset;
|
||||
|
||||
return {
|
||||
endpoint: environmentsData.endpoint,
|
||||
folderName: environmentsData.folderName,
|
||||
cssContainerClass: environmentsData.cssClass,
|
||||
canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
|
||||
canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
|
||||
};
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('environments-folder-app', {
|
||||
props: {
|
||||
endpoint: this.endpoint,
|
||||
folderName: this.folderName,
|
||||
cssContainerClass: this.cssContainerClass,
|
||||
canCreateDeployment: this.canCreateDeployment,
|
||||
canReadEnvironment: this.canReadEnvironment,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
return {
|
||||
endpoint: environmentsData.endpoint,
|
||||
folderName: environmentsData.folderName,
|
||||
cssContainerClass: environmentsData.cssClass,
|
||||
canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
|
||||
canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
|
||||
};
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('environments-folder-app', {
|
||||
props: {
|
||||
endpoint: this.endpoint,
|
||||
folderName: this.folderName,
|
||||
cssContainerClass: this.cssContainerClass,
|
||||
canCreateDeployment: this.canCreateDeployment,
|
||||
canReadEnvironment: this.canReadEnvironment,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,46 +1,43 @@
|
|||
<script>
|
||||
import environmentsMixin from '../mixins/environments_mixin';
|
||||
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
|
||||
import StopEnvironmentModal from '../components/stop_environment_modal.vue';
|
||||
import environmentsMixin from '../mixins/environments_mixin';
|
||||
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
|
||||
import StopEnvironmentModal from '../components/stop_environment_modal.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
StopEnvironmentModal,
|
||||
},
|
||||
export default {
|
||||
components: {
|
||||
StopEnvironmentModal,
|
||||
},
|
||||
|
||||
mixins: [
|
||||
environmentsMixin,
|
||||
CIPaginationMixin,
|
||||
],
|
||||
mixins: [environmentsMixin, CIPaginationMixin],
|
||||
|
||||
props: {
|
||||
endpoint: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
folderName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
cssContainerClass: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
canCreateDeployment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
canReadEnvironment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
endpoint: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
methods: {
|
||||
successCallback(resp) {
|
||||
this.saveData(resp);
|
||||
},
|
||||
folderName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
};
|
||||
cssContainerClass: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
canCreateDeployment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
canReadEnvironment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
successCallback(resp) {
|
||||
this.saveData(resp);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div :class="cssContainerClass">
|
||||
|
|
|
@ -5,35 +5,36 @@ import Translate from '../vue_shared/translate';
|
|||
|
||||
Vue.use(Translate);
|
||||
|
||||
export default () => new Vue({
|
||||
el: '#environments-list-view',
|
||||
components: {
|
||||
environmentsComponent,
|
||||
},
|
||||
data() {
|
||||
const environmentsData = document.querySelector(this.$options.el).dataset;
|
||||
export default () =>
|
||||
new Vue({
|
||||
el: '#environments-list-view',
|
||||
components: {
|
||||
environmentsComponent,
|
||||
},
|
||||
data() {
|
||||
const environmentsData = document.querySelector(this.$options.el).dataset;
|
||||
|
||||
return {
|
||||
endpoint: environmentsData.environmentsDataEndpoint,
|
||||
newEnvironmentPath: environmentsData.newEnvironmentPath,
|
||||
helpPagePath: environmentsData.helpPagePath,
|
||||
cssContainerClass: environmentsData.cssClass,
|
||||
canCreateEnvironment: convertPermissionToBoolean(environmentsData.canCreateEnvironment),
|
||||
canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
|
||||
canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
|
||||
};
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('environments-component', {
|
||||
props: {
|
||||
endpoint: this.endpoint,
|
||||
newEnvironmentPath: this.newEnvironmentPath,
|
||||
helpPagePath: this.helpPagePath,
|
||||
cssContainerClass: this.cssContainerClass,
|
||||
canCreateEnvironment: this.canCreateEnvironment,
|
||||
canCreateDeployment: this.canCreateDeployment,
|
||||
canReadEnvironment: this.canReadEnvironment,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
return {
|
||||
endpoint: environmentsData.environmentsDataEndpoint,
|
||||
newEnvironmentPath: environmentsData.newEnvironmentPath,
|
||||
helpPagePath: environmentsData.helpPagePath,
|
||||
cssContainerClass: environmentsData.cssClass,
|
||||
canCreateEnvironment: convertPermissionToBoolean(environmentsData.canCreateEnvironment),
|
||||
canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
|
||||
canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
|
||||
};
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('environments-component', {
|
||||
props: {
|
||||
endpoint: this.endpoint,
|
||||
newEnvironmentPath: this.newEnvironmentPath,
|
||||
helpPagePath: this.helpPagePath,
|
||||
cssContainerClass: this.cssContainerClass,
|
||||
canCreateEnvironment: this.canCreateEnvironment,
|
||||
canCreateDeployment: this.canCreateDeployment,
|
||||
canReadEnvironment: this.canReadEnvironment,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -4,9 +4,7 @@
|
|||
import _ from 'underscore';
|
||||
import Visibility from 'visibilityjs';
|
||||
import Poll from '../../lib/utils/poll';
|
||||
import {
|
||||
getParameterByName,
|
||||
} from '../../lib/utils/common_utils';
|
||||
import { getParameterByName } from '../../lib/utils/common_utils';
|
||||
import { s__ } from '../../locale';
|
||||
import Flash from '../../flash';
|
||||
import eventHub from '../event_hub';
|
||||
|
@ -19,7 +17,6 @@ import tabs from '../../vue_shared/components/navigation_tabs.vue';
|
|||
import container from '../components/container.vue';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
environmentTable,
|
||||
container,
|
||||
|
@ -65,7 +62,8 @@ export default {
|
|||
updateContent(parameters) {
|
||||
this.updateInternalState(parameters);
|
||||
// fetch new data
|
||||
return this.service.fetchEnvironments(this.requestData)
|
||||
return this.service
|
||||
.fetchEnvironments(this.requestData)
|
||||
.then(response => this.successCallback(response))
|
||||
.then(() => {
|
||||
// restart polling
|
||||
|
@ -88,7 +86,8 @@ export default {
|
|||
if (!this.isMakingRequest) {
|
||||
this.isLoading = true;
|
||||
|
||||
this.service.postAction(endpoint)
|
||||
this.service
|
||||
.postAction(endpoint)
|
||||
.then(() => this.fetchEnvironments())
|
||||
.catch(() => {
|
||||
this.isLoading = false;
|
||||
|
@ -100,7 +99,8 @@ export default {
|
|||
fetchEnvironments() {
|
||||
this.isLoading = true;
|
||||
|
||||
return this.service.fetchEnvironments(this.requestData)
|
||||
return this.service
|
||||
.fetchEnvironments(this.requestData)
|
||||
.then(this.successCallback)
|
||||
.catch(this.errorCallback);
|
||||
},
|
||||
|
@ -111,7 +111,9 @@ export default {
|
|||
|
||||
stopEnvironment(environment) {
|
||||
const endpoint = environment.stop_path;
|
||||
const errorMessage = s__('Environments|An error occurred while stopping the environment, please try again');
|
||||
const errorMessage = s__(
|
||||
'Environments|An error occurred while stopping the environment, please try again',
|
||||
);
|
||||
this.postAction({ endpoint, errorMessage });
|
||||
},
|
||||
},
|
||||
|
@ -149,7 +151,7 @@ export default {
|
|||
data: this.requestData,
|
||||
successCallback: this.successCallback,
|
||||
errorCallback: this.errorCallback,
|
||||
notificationCallback: (isMakingRequest) => {
|
||||
notificationCallback: isMakingRequest => {
|
||||
this.isMakingRequest = isMakingRequest;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
import $ from 'jquery';
|
||||
import {
|
||||
getSelector,
|
||||
inserted,
|
||||
} from './feature_highlight_helper';
|
||||
import {
|
||||
togglePopover,
|
||||
mouseenter,
|
||||
debouncedMouseleave,
|
||||
} from '../shared/popover';
|
||||
import { getSelector, inserted } from './feature_highlight_helper';
|
||||
import { togglePopover, mouseenter, debouncedMouseleave } from '../shared/popover';
|
||||
|
||||
export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
|
||||
const $selector = $(getSelector(id));
|
||||
|
@ -41,8 +34,9 @@ export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
|
|||
export function findHighestPriorityFeature() {
|
||||
let priorityFeature;
|
||||
|
||||
const sortedFeatureEls = [].slice.call(document.querySelectorAll('.js-feature-highlight')).sort((a, b) =>
|
||||
(a.dataset.highlightPriority || 0) < (b.dataset.highlightPriority || 0));
|
||||
const sortedFeatureEls = [].slice
|
||||
.call(document.querySelectorAll('.js-feature-highlight'))
|
||||
.sort((a, b) => (a.dataset.highlightPriority || 0) < (b.dataset.highlightPriority || 0));
|
||||
|
||||
const [priorityFeatureEl] = sortedFeatureEls;
|
||||
if (priorityFeatureEl) {
|
||||
|
|
|
@ -8,10 +8,17 @@ import { togglePopover } from '../shared/popover';
|
|||
export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`;
|
||||
|
||||
export function dismiss(highlightId) {
|
||||
axios.post(this.attr('data-dismiss-endpoint'), {
|
||||
feature_name: highlightId,
|
||||
})
|
||||
.catch(() => Flash(__('An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again.')));
|
||||
axios
|
||||
.post(this.attr('data-dismiss-endpoint'), {
|
||||
feature_name: highlightId,
|
||||
})
|
||||
.catch(() =>
|
||||
Flash(
|
||||
__(
|
||||
'An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again.',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
togglePopover.call(this, false);
|
||||
this.hide();
|
||||
|
@ -23,8 +30,7 @@ export function inserted() {
|
|||
const $popover = $(this);
|
||||
const dismissWrapper = dismiss.bind($popover, highlightId);
|
||||
|
||||
$(`#${popoverId} .dismiss-feature-highlight`)
|
||||
.on('click', dismissWrapper);
|
||||
$(`#${popoverId} .dismiss-feature-highlight`).on('click', dismissWrapper);
|
||||
|
||||
const lazyImg = $(`#${popoverId} .feature-highlight-illustration`)[0];
|
||||
if (lazyImg) {
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
import FilteredSearchTokenKeys from './filtered_search_token_keys';
|
||||
|
||||
const tokenKeys = [{
|
||||
key: 'status',
|
||||
type: 'string',
|
||||
param: 'status',
|
||||
symbol: '',
|
||||
icon: 'messages',
|
||||
tag: 'status',
|
||||
}, {
|
||||
key: 'type',
|
||||
type: 'string',
|
||||
param: 'type',
|
||||
symbol: '',
|
||||
icon: 'cube',
|
||||
tag: 'type',
|
||||
}];
|
||||
const tokenKeys = [
|
||||
{
|
||||
key: 'status',
|
||||
type: 'string',
|
||||
param: 'status',
|
||||
symbol: '',
|
||||
icon: 'messages',
|
||||
tag: 'status',
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
type: 'string',
|
||||
param: 'type',
|
||||
symbol: '',
|
||||
icon: 'cube',
|
||||
tag: 'type',
|
||||
},
|
||||
];
|
||||
|
||||
const AdminRunnersFilteredSearchTokenKeys = new FilteredSearchTokenKeys(tokenKeys);
|
||||
|
||||
|
|
|
@ -21,9 +21,11 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
processedItems() {
|
||||
return this.items.map((item) => {
|
||||
const { tokens, searchToken }
|
||||
= FilteredSearchTokenizer.processTokens(item, this.allowedKeys);
|
||||
return this.items.map(item => {
|
||||
const { tokens, searchToken } = FilteredSearchTokenizer.processTokens(
|
||||
item,
|
||||
this.allowedKeys,
|
||||
);
|
||||
|
||||
const resultantTokens = tokens.map(token => ({
|
||||
prefix: `${token.key}:`,
|
||||
|
|
|
@ -24,8 +24,12 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
|
|||
};
|
||||
|
||||
import(/* webpackChunkName: 'emoji' */ '~/emoji')
|
||||
.then(({ glEmojiTag }) => { this.glEmojiTag = glEmojiTag; })
|
||||
.catch(() => { /* ignore error and leave emoji name in the search bar */ });
|
||||
.then(({ glEmojiTag }) => {
|
||||
this.glEmojiTag = glEmojiTag;
|
||||
})
|
||||
.catch(() => {
|
||||
/* ignore error and leave emoji name in the search bar */
|
||||
});
|
||||
|
||||
this.unbindEvents();
|
||||
this.bindEvents();
|
||||
|
@ -48,7 +52,7 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
|
|||
}
|
||||
|
||||
itemClicked(e) {
|
||||
super.itemClicked(e, (selected) => {
|
||||
super.itemClicked(e, selected => {
|
||||
const name = selected.querySelector('.js-data-value').innerText.trim();
|
||||
return DropdownUtils.getEscapedText(name);
|
||||
});
|
||||
|
@ -64,7 +68,7 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
|
|||
|
||||
// Replace empty gl-emoji tag to real content
|
||||
const dropdownItems = [...this.dropdown.querySelectorAll('.filter-dropdown-item')];
|
||||
dropdownItems.forEach((dropdownItem) => {
|
||||
dropdownItems.forEach(dropdownItem => {
|
||||
const name = dropdownItem.querySelector('.js-data-value').innerText;
|
||||
const emojiTag = this.glEmojiTag(name);
|
||||
const emojiElement = dropdownItem.querySelector('gl-emoji');
|
||||
|
@ -73,7 +77,6 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
|
|||
}
|
||||
|
||||
init() {
|
||||
this.droplab
|
||||
.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
|
||||
this.droplab.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,8 +41,10 @@ export default class DropdownHint extends FilteredSearchDropdown {
|
|||
previousInputValues.forEach((value, index) => {
|
||||
searchTerms.push(value);
|
||||
|
||||
if (index === previousInputValues.length - 1
|
||||
&& token.indexOf(value.toLowerCase()) !== -1) {
|
||||
if (
|
||||
index === previousInputValues.length - 1 &&
|
||||
token.indexOf(value.toLowerCase()) !== -1
|
||||
) {
|
||||
searchTerms.pop();
|
||||
}
|
||||
});
|
||||
|
@ -64,13 +66,12 @@ export default class DropdownHint extends FilteredSearchDropdown {
|
|||
}
|
||||
|
||||
renderContent() {
|
||||
const dropdownData = this.tokenKeys.get()
|
||||
.map(tokenKey => ({
|
||||
icon: `${gon.sprite_icons}#${tokenKey.icon}`,
|
||||
hint: tokenKey.key,
|
||||
tag: `:${tokenKey.tag}`,
|
||||
type: tokenKey.type,
|
||||
}));
|
||||
const dropdownData = this.tokenKeys.get().map(tokenKey => ({
|
||||
icon: `${gon.sprite_icons}#${tokenKey.icon}`,
|
||||
hint: tokenKey.key,
|
||||
tag: `:${tokenKey.tag}`,
|
||||
type: tokenKey.type,
|
||||
}));
|
||||
|
||||
this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config);
|
||||
this.droplab.setData(this.hookId, dropdownData);
|
||||
|
|
|
@ -29,20 +29,18 @@ export default class DropdownNonUser extends FilteredSearchDropdown {
|
|||
}
|
||||
|
||||
itemClicked(e) {
|
||||
super.itemClicked(e, (selected) => {
|
||||
super.itemClicked(e, selected => {
|
||||
const title = selected.querySelector('.js-data-value').innerText.trim();
|
||||
return `${this.symbol}${DropdownUtils.getEscapedText(title)}`;
|
||||
});
|
||||
}
|
||||
|
||||
renderContent(forceShowList = false) {
|
||||
this.droplab
|
||||
.changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
|
||||
this.droplab.changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
|
||||
super.renderContent(forceShowList);
|
||||
}
|
||||
|
||||
init() {
|
||||
this.droplab
|
||||
.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
|
||||
this.droplab.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ export default class DropdownUtils {
|
|||
|
||||
// Removes the first character if it is a quotation so that we can search
|
||||
// with multiple words
|
||||
if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) {
|
||||
if ((value[0] === '"' || value[0] === "'") && title.indexOf(' ') !== -1) {
|
||||
value = value.slice(1);
|
||||
}
|
||||
|
||||
|
@ -82,11 +82,13 @@ export default class DropdownUtils {
|
|||
// Reduce the colors to 4
|
||||
colors.length = Math.min(colors.length, 4);
|
||||
|
||||
const color = colors.map((c, i) => {
|
||||
const percentFirst = Math.floor(spacing * i);
|
||||
const percentSecond = Math.floor(spacing * (i + 1));
|
||||
return `${c} ${percentFirst}%, ${c} ${percentSecond}%`;
|
||||
}).join(', ');
|
||||
const color = colors
|
||||
.map((c, i) => {
|
||||
const percentFirst = Math.floor(spacing * i);
|
||||
const percentSecond = Math.floor(spacing * (i + 1));
|
||||
return `${c} ${percentFirst}%, ${c} ${percentSecond}%`;
|
||||
})
|
||||
.join(', ');
|
||||
|
||||
return `linear-gradient(${color})`;
|
||||
}
|
||||
|
@ -97,17 +99,16 @@ export default class DropdownUtils {
|
|||
|
||||
data.forEach(DropdownUtils.mergeDuplicateLabels.bind(null, dataMap));
|
||||
|
||||
Object.keys(dataMap)
|
||||
.forEach((key) => {
|
||||
const label = dataMap[key];
|
||||
Object.keys(dataMap).forEach(key => {
|
||||
const label = dataMap[key];
|
||||
|
||||
if (label.multipleColors) {
|
||||
label.color = DropdownUtils.duplicateLabelColor(label.multipleColors);
|
||||
label.text_color = '#000000';
|
||||
}
|
||||
if (label.multipleColors) {
|
||||
label.color = DropdownUtils.duplicateLabelColor(label.multipleColors);
|
||||
label.text_color = '#000000';
|
||||
}
|
||||
|
||||
results.push(label);
|
||||
});
|
||||
results.push(label);
|
||||
});
|
||||
|
||||
results.preprocessed = true;
|
||||
|
||||
|
@ -118,8 +119,7 @@ export default class DropdownUtils {
|
|||
const { input, allowedKeys } = config;
|
||||
const updatedItem = item;
|
||||
const searchInput = DropdownUtils.getSearchQuery(input);
|
||||
const { lastToken, tokens } =
|
||||
FilteredSearchTokenizer.processTokens(searchInput, allowedKeys);
|
||||
const { lastToken, tokens } = FilteredSearchTokenizer.processTokens(searchInput, allowedKeys);
|
||||
const lastKey = lastToken.key || lastToken || '';
|
||||
const allowMultiple = item.type === 'array';
|
||||
const itemInExistingTokens = tokens.some(t => t.key === item.hint);
|
||||
|
@ -154,7 +154,10 @@ export default class DropdownUtils {
|
|||
|
||||
static getVisualTokenValues(visualToken) {
|
||||
const tokenName = visualToken && visualToken.querySelector('.name').textContent.trim();
|
||||
let tokenValue = visualToken && visualToken.querySelector('.value') && visualToken.querySelector('.value').textContent.trim();
|
||||
let tokenValue =
|
||||
visualToken &&
|
||||
visualToken.querySelector('.value') &&
|
||||
visualToken.querySelector('.value').textContent.trim();
|
||||
if (tokenName === 'label' && tokenValue) {
|
||||
// remove leading symbol and wrapping quotes
|
||||
tokenValue = tokenValue.replace(/^~("|')?(.*)/, '$2').replace(/("|')$/, '');
|
||||
|
@ -174,7 +177,7 @@ export default class DropdownUtils {
|
|||
tokens.splice(inputIndex + 1);
|
||||
}
|
||||
|
||||
tokens.forEach((token) => {
|
||||
tokens.forEach(token => {
|
||||
if (token.classList.contains('js-visual-token')) {
|
||||
const name = token.querySelector('.name');
|
||||
const value = token.querySelector('.value');
|
||||
|
@ -194,8 +197,9 @@ export default class DropdownUtils {
|
|||
values.push(name.innerText);
|
||||
}
|
||||
} else if (token.classList.contains('input-token')) {
|
||||
const { isLastVisualTokenValid } =
|
||||
FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
const {
|
||||
isLastVisualTokenValid,
|
||||
} = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
|
||||
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
|
||||
const inputValue = input && input.value;
|
||||
|
@ -209,9 +213,7 @@ export default class DropdownUtils {
|
|||
}
|
||||
});
|
||||
|
||||
return values
|
||||
.map(value => value.trim())
|
||||
.join(' ');
|
||||
return values.map(value => value.trim()).join(' ');
|
||||
}
|
||||
|
||||
static getSearchInput(filteredSearchInput) {
|
||||
|
@ -227,7 +229,9 @@ export default class DropdownUtils {
|
|||
// Replace all spaces inside quote marks with underscores
|
||||
// (will continue to match entire string until an end quote is found if any)
|
||||
// This helps with matching the beginning & end of a token:key
|
||||
inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str => str.replace(/\s/g, '_'));
|
||||
inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str =>
|
||||
str.replace(/\s/g, '_'),
|
||||
);
|
||||
|
||||
// Get the right position for the word selected
|
||||
// Regex matches first space
|
||||
|
|
|
@ -87,10 +87,12 @@ export default class FilteredSearchDropdown {
|
|||
dispatchInputEvent() {
|
||||
// Propogate input change to FilteredSearchDropdownManager
|
||||
// so that it can determine which dropdowns to open
|
||||
this.input.dispatchEvent(new CustomEvent('input', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
}));
|
||||
this.input.dispatchEvent(
|
||||
new CustomEvent('input', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
dispatchFormSubmitEvent() {
|
||||
|
@ -114,7 +116,7 @@ export default class FilteredSearchDropdown {
|
|||
|
||||
if (!data) return;
|
||||
|
||||
const results = data.map((o) => {
|
||||
const results = data.map(o => {
|
||||
const updated = o;
|
||||
updated.droplab_hidden = false;
|
||||
return updated;
|
||||
|
|
|
@ -42,19 +42,21 @@ export default class FilteredSearchTokenKeys {
|
|||
}
|
||||
|
||||
searchByKeyParam(keyParam) {
|
||||
return this.tokenKeysWithAlternative.find((tokenKey) => {
|
||||
let tokenKeyParam = tokenKey.key;
|
||||
return (
|
||||
this.tokenKeysWithAlternative.find(tokenKey => {
|
||||
let tokenKeyParam = tokenKey.key;
|
||||
|
||||
// Replace hyphen with underscore to compare keyParam with tokenKeyParam
|
||||
// e.g. 'my-reaction' => 'my_reaction'
|
||||
tokenKeyParam = tokenKeyParam.replace('-', '_');
|
||||
// Replace hyphen with underscore to compare keyParam with tokenKeyParam
|
||||
// e.g. 'my-reaction' => 'my_reaction'
|
||||
tokenKeyParam = tokenKeyParam.replace('-', '_');
|
||||
|
||||
if (tokenKey.param) {
|
||||
tokenKeyParam += `_${tokenKey.param}`;
|
||||
}
|
||||
if (tokenKey.param) {
|
||||
tokenKeyParam += `_${tokenKey.param}`;
|
||||
}
|
||||
|
||||
return keyParam === tokenKeyParam;
|
||||
}) || null;
|
||||
return keyParam === tokenKeyParam;
|
||||
}) || null
|
||||
);
|
||||
}
|
||||
|
||||
searchByConditionUrl(url) {
|
||||
|
@ -62,8 +64,10 @@ export default class FilteredSearchTokenKeys {
|
|||
}
|
||||
|
||||
searchByConditionKeyValue(key, value) {
|
||||
return this.conditions
|
||||
.find(condition => condition.tokenKey === key && condition.value === value) || null;
|
||||
return (
|
||||
this.conditions.find(condition => condition.tokenKey === key && condition.value === value) ||
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
addExtraTokensForMergeRequests() {
|
||||
|
|
|
@ -4,41 +4,48 @@ export default class FilteredSearchTokenizer {
|
|||
static processTokens(input, allowedKeys) {
|
||||
// Regex extracts `(token):(symbol)(value)`
|
||||
// Values that start with a double quote must end in a double quote (same for single)
|
||||
const tokenRegex = new RegExp(`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, 'g');
|
||||
const tokenRegex = new RegExp(
|
||||
`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`,
|
||||
'g',
|
||||
);
|
||||
const tokens = [];
|
||||
const tokenIndexes = []; // stores key+value for simple search
|
||||
let lastToken = null;
|
||||
const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => {
|
||||
let tokenValue = v1 || v2 || v3;
|
||||
let tokenSymbol = symbol;
|
||||
let tokenIndex = '';
|
||||
const searchToken =
|
||||
input
|
||||
.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => {
|
||||
let tokenValue = v1 || v2 || v3;
|
||||
let tokenSymbol = symbol;
|
||||
let tokenIndex = '';
|
||||
|
||||
if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') {
|
||||
tokenSymbol = tokenValue;
|
||||
tokenValue = '';
|
||||
}
|
||||
if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') {
|
||||
tokenSymbol = tokenValue;
|
||||
tokenValue = '';
|
||||
}
|
||||
|
||||
tokenIndex = `${key}:${tokenValue}`;
|
||||
tokenIndex = `${key}:${tokenValue}`;
|
||||
|
||||
// Prevent adding duplicates
|
||||
if (tokenIndexes.indexOf(tokenIndex) === -1) {
|
||||
tokenIndexes.push(tokenIndex);
|
||||
// Prevent adding duplicates
|
||||
if (tokenIndexes.indexOf(tokenIndex) === -1) {
|
||||
tokenIndexes.push(tokenIndex);
|
||||
|
||||
tokens.push({
|
||||
key,
|
||||
value: tokenValue || '',
|
||||
symbol: tokenSymbol || '',
|
||||
});
|
||||
}
|
||||
tokens.push({
|
||||
key,
|
||||
value: tokenValue || '',
|
||||
symbol: tokenSymbol || '',
|
||||
});
|
||||
}
|
||||
|
||||
return '';
|
||||
}).replace(/\s{2,}/g, ' ').trim() || '';
|
||||
return '';
|
||||
})
|
||||
.replace(/\s{2,}/g, ' ')
|
||||
.trim() || '';
|
||||
|
||||
if (tokens.length > 0) {
|
||||
const last = tokens[tokens.length - 1];
|
||||
const lastString = `${last.key}:${last.symbol}${last.value}`;
|
||||
lastToken = input.lastIndexOf(lastString) ===
|
||||
input.length - lastString.length ? last : searchToken;
|
||||
lastToken =
|
||||
input.lastIndexOf(lastString) === input.length - lastString.length ? last : searchToken;
|
||||
} else {
|
||||
lastToken = searchToken;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,10 @@ export default class FilteredSearchVisualTokens {
|
|||
|
||||
return {
|
||||
lastVisualToken,
|
||||
isLastVisualTokenValid: lastVisualToken === null || lastVisualToken.className.indexOf('filtered-search-term') !== -1 || (lastVisualToken && lastVisualToken.querySelector('.value') !== null),
|
||||
isLastVisualTokenValid:
|
||||
lastVisualToken === null ||
|
||||
lastVisualToken.className.indexOf('filtered-search-term') !== -1 ||
|
||||
(lastVisualToken && lastVisualToken.querySelector('.value') !== null),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -33,7 +36,9 @@ export default class FilteredSearchVisualTokens {
|
|||
}
|
||||
|
||||
static unselectTokens() {
|
||||
const otherTokens = FilteredSearchContainer.container.querySelectorAll('.js-visual-token .selectable.selected');
|
||||
const otherTokens = FilteredSearchContainer.container.querySelectorAll(
|
||||
'.js-visual-token .selectable.selected',
|
||||
);
|
||||
[].forEach.call(otherTokens, t => t.classList.remove('selected'));
|
||||
}
|
||||
|
||||
|
@ -56,11 +61,7 @@ export default class FilteredSearchVisualTokens {
|
|||
}
|
||||
|
||||
static createVisualTokenElementHTML(options = {}) {
|
||||
const {
|
||||
canEdit = true,
|
||||
uppercaseTokenName = false,
|
||||
capitalizeTokenValue = false,
|
||||
} = options;
|
||||
const { canEdit = true, uppercaseTokenName = false, capitalizeTokenValue = false } = options;
|
||||
|
||||
return `
|
||||
<div class="${canEdit ? 'selectable' : 'hidden'}" role="button">
|
||||
|
@ -115,15 +116,20 @@ export default class FilteredSearchVisualTokens {
|
|||
|
||||
return AjaxCache.retrieve(labelsEndpoint)
|
||||
.then(FilteredSearchVisualTokens.preprocessLabel.bind(null, labelsEndpoint))
|
||||
.then((labels) => {
|
||||
const matchingLabel = (labels || []).find(label => `~${DropdownUtils.getEscapedText(label.title)}` === tokenValue);
|
||||
.then(labels => {
|
||||
const matchingLabel = (labels || []).find(
|
||||
label => `~${DropdownUtils.getEscapedText(label.title)}` === tokenValue,
|
||||
);
|
||||
|
||||
if (!matchingLabel) {
|
||||
return;
|
||||
}
|
||||
|
||||
FilteredSearchVisualTokens
|
||||
.setTokenStyle(tokenValueContainer, matchingLabel.color, matchingLabel.text_color);
|
||||
FilteredSearchVisualTokens.setTokenStyle(
|
||||
tokenValueContainer,
|
||||
matchingLabel.color,
|
||||
matchingLabel.text_color,
|
||||
);
|
||||
})
|
||||
.catch(() => new Flash('An error occurred while fetching label colors.'));
|
||||
}
|
||||
|
@ -134,39 +140,43 @@ export default class FilteredSearchVisualTokens {
|
|||
}
|
||||
|
||||
const username = tokenValue.replace(/^@/, '');
|
||||
return UsersCache.retrieve(username)
|
||||
.then((user) => {
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
return (
|
||||
UsersCache.retrieve(username)
|
||||
.then(user => {
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* eslint-disable no-param-reassign */
|
||||
tokenValueContainer.dataset.originalValue = tokenValue;
|
||||
tokenValueElement.innerHTML = `
|
||||
/* eslint-disable no-param-reassign */
|
||||
tokenValueContainer.dataset.originalValue = tokenValue;
|
||||
tokenValueElement.innerHTML = `
|
||||
<img class="avatar s20" src="${user.avatar_url}" alt="">
|
||||
${_.escape(user.name)}
|
||||
`;
|
||||
/* eslint-enable no-param-reassign */
|
||||
})
|
||||
// ignore error and leave username in the search bar
|
||||
.catch(() => { });
|
||||
/* eslint-enable no-param-reassign */
|
||||
})
|
||||
// ignore error and leave username in the search bar
|
||||
.catch(() => {})
|
||||
);
|
||||
}
|
||||
|
||||
static updateEmojiTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue) {
|
||||
const container = tokenValueContainer;
|
||||
const element = tokenValueElement;
|
||||
|
||||
return import(/* webpackChunkName: 'emoji' */ '../emoji')
|
||||
.then((Emoji) => {
|
||||
if (!Emoji.isEmojiNameValid(tokenValue)) {
|
||||
return;
|
||||
}
|
||||
return (
|
||||
import(/* webpackChunkName: 'emoji' */ '../emoji')
|
||||
.then(Emoji => {
|
||||
if (!Emoji.isEmojiNameValid(tokenValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
container.dataset.originalValue = tokenValue;
|
||||
element.innerHTML = Emoji.glEmojiTag(tokenValue);
|
||||
})
|
||||
// ignore error and leave emoji name in the search bar
|
||||
.catch(() => { });
|
||||
container.dataset.originalValue = tokenValue;
|
||||
element.innerHTML = Emoji.glEmojiTag(tokenValue);
|
||||
})
|
||||
// ignore error and leave emoji name in the search bar
|
||||
.catch(() => {})
|
||||
);
|
||||
}
|
||||
|
||||
static renderVisualTokenValue(parentElement, tokenName, tokenValue) {
|
||||
|
@ -177,24 +187,23 @@ export default class FilteredSearchVisualTokens {
|
|||
const tokenType = tokenName.toLowerCase();
|
||||
if (tokenType === 'label') {
|
||||
FilteredSearchVisualTokens.updateLabelTokenColor(tokenValueContainer, tokenValue);
|
||||
} else if ((tokenType === 'author') || (tokenType === 'assignee')) {
|
||||
} else if (tokenType === 'author' || tokenType === 'assignee') {
|
||||
FilteredSearchVisualTokens.updateUserTokenAppearance(
|
||||
tokenValueContainer, tokenValueElement, tokenValue,
|
||||
tokenValueContainer,
|
||||
tokenValueElement,
|
||||
tokenValue,
|
||||
);
|
||||
} else if (tokenType === 'my-reaction') {
|
||||
FilteredSearchVisualTokens.updateEmojiTokenAppearance(
|
||||
tokenValueContainer, tokenValueElement, tokenValue,
|
||||
tokenValueContainer,
|
||||
tokenValueElement,
|
||||
tokenValue,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static addVisualTokenElement(name, value, options = {}) {
|
||||
const {
|
||||
isSearchTerm = false,
|
||||
canEdit,
|
||||
uppercaseTokenName,
|
||||
capitalizeTokenValue,
|
||||
} = options;
|
||||
const { isSearchTerm = false, canEdit, uppercaseTokenName, capitalizeTokenValue } = options;
|
||||
const li = document.createElement('li');
|
||||
li.classList.add('js-visual-token');
|
||||
li.classList.add(isSearchTerm ? 'filtered-search-term' : 'filtered-search-token');
|
||||
|
@ -217,8 +226,10 @@ export default class FilteredSearchVisualTokens {
|
|||
}
|
||||
|
||||
static addValueToPreviousVisualTokenElement(value) {
|
||||
const { lastVisualToken, isLastVisualTokenValid } =
|
||||
FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
const {
|
||||
lastVisualToken,
|
||||
isLastVisualTokenValid,
|
||||
} = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
|
||||
if (!isLastVisualTokenValid && lastVisualToken.classList.contains('filtered-search-token')) {
|
||||
const name = FilteredSearchVisualTokens.getLastTokenPartial();
|
||||
|
@ -228,13 +239,15 @@ export default class FilteredSearchVisualTokens {
|
|||
}
|
||||
}
|
||||
|
||||
static addFilterVisualToken(tokenName, tokenValue, {
|
||||
canEdit,
|
||||
uppercaseTokenName = false,
|
||||
capitalizeTokenValue = false,
|
||||
} = {}) {
|
||||
const { lastVisualToken, isLastVisualTokenValid }
|
||||
= FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
static addFilterVisualToken(
|
||||
tokenName,
|
||||
tokenValue,
|
||||
{ canEdit, uppercaseTokenName = false, capitalizeTokenValue = false } = {},
|
||||
) {
|
||||
const {
|
||||
lastVisualToken,
|
||||
isLastVisualTokenValid,
|
||||
} = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
const { addVisualTokenElement } = FilteredSearchVisualTokens;
|
||||
|
||||
if (isLastVisualTokenValid) {
|
||||
|
@ -308,8 +321,7 @@ export default class FilteredSearchVisualTokens {
|
|||
|
||||
static tokenizeInput() {
|
||||
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
|
||||
const { isLastVisualTokenValid } =
|
||||
FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
const { isLastVisualTokenValid } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
|
||||
if (input.value) {
|
||||
if (isLastVisualTokenValid) {
|
||||
|
@ -375,8 +387,7 @@ export default class FilteredSearchVisualTokens {
|
|||
FilteredSearchVisualTokens.tokenizeInput();
|
||||
|
||||
if (!tokenContainer.lastElementChild.isEqualNode(inputLi)) {
|
||||
const { isLastVisualTokenValid } =
|
||||
FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
const { isLastVisualTokenValid } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
|
||||
if (!isLastVisualTokenValid) {
|
||||
const lastPartial = FilteredSearchVisualTokens.getLastTokenPartial();
|
||||
|
|
|
@ -1,34 +1,39 @@
|
|||
import FilteredSearchTokenKeys from './filtered_search_token_keys';
|
||||
|
||||
export const tokenKeys = [{
|
||||
key: 'author',
|
||||
type: 'string',
|
||||
param: 'username',
|
||||
symbol: '@',
|
||||
icon: 'pencil',
|
||||
tag: '@author',
|
||||
}, {
|
||||
key: 'assignee',
|
||||
type: 'string',
|
||||
param: 'username',
|
||||
symbol: '@',
|
||||
icon: 'user',
|
||||
tag: '@assignee',
|
||||
}, {
|
||||
key: 'milestone',
|
||||
type: 'string',
|
||||
param: 'title',
|
||||
symbol: '%',
|
||||
icon: 'clock',
|
||||
tag: '%milestone',
|
||||
}, {
|
||||
key: 'label',
|
||||
type: 'array',
|
||||
param: 'name[]',
|
||||
symbol: '~',
|
||||
icon: 'labels',
|
||||
tag: '~label',
|
||||
}];
|
||||
export const tokenKeys = [
|
||||
{
|
||||
key: 'author',
|
||||
type: 'string',
|
||||
param: 'username',
|
||||
symbol: '@',
|
||||
icon: 'pencil',
|
||||
tag: '@author',
|
||||
},
|
||||
{
|
||||
key: 'assignee',
|
||||
type: 'string',
|
||||
param: 'username',
|
||||
symbol: '@',
|
||||
icon: 'user',
|
||||
tag: '@assignee',
|
||||
},
|
||||
{
|
||||
key: 'milestone',
|
||||
type: 'string',
|
||||
param: 'title',
|
||||
symbol: '%',
|
||||
icon: 'clock',
|
||||
tag: '%milestone',
|
||||
},
|
||||
{
|
||||
key: 'label',
|
||||
type: 'array',
|
||||
param: 'name[]',
|
||||
symbol: '~',
|
||||
icon: 'labels',
|
||||
tag: '~label',
|
||||
},
|
||||
];
|
||||
|
||||
if (gon.current_user_id) {
|
||||
// Appending tokenkeys only logged-in
|
||||
|
@ -42,36 +47,47 @@ if (gon.current_user_id) {
|
|||
});
|
||||
}
|
||||
|
||||
export const alternativeTokenKeys = [{
|
||||
key: 'label',
|
||||
type: 'string',
|
||||
param: 'name',
|
||||
symbol: '~',
|
||||
}];
|
||||
export const alternativeTokenKeys = [
|
||||
{
|
||||
key: 'label',
|
||||
type: 'string',
|
||||
param: 'name',
|
||||
symbol: '~',
|
||||
},
|
||||
];
|
||||
|
||||
export const conditions = [{
|
||||
url: 'assignee_id=0',
|
||||
tokenKey: 'assignee',
|
||||
value: 'none',
|
||||
}, {
|
||||
url: 'milestone_title=No+Milestone',
|
||||
tokenKey: 'milestone',
|
||||
value: 'none',
|
||||
}, {
|
||||
url: 'milestone_title=%23upcoming',
|
||||
tokenKey: 'milestone',
|
||||
value: 'upcoming',
|
||||
}, {
|
||||
url: 'milestone_title=%23started',
|
||||
tokenKey: 'milestone',
|
||||
value: 'started',
|
||||
}, {
|
||||
url: 'label_name[]=No+Label',
|
||||
tokenKey: 'label',
|
||||
value: 'none',
|
||||
}];
|
||||
export const conditions = [
|
||||
{
|
||||
url: 'assignee_id=0',
|
||||
tokenKey: 'assignee',
|
||||
value: 'none',
|
||||
},
|
||||
{
|
||||
url: 'milestone_title=No+Milestone',
|
||||
tokenKey: 'milestone',
|
||||
value: 'none',
|
||||
},
|
||||
{
|
||||
url: 'milestone_title=%23upcoming',
|
||||
tokenKey: 'milestone',
|
||||
value: 'upcoming',
|
||||
},
|
||||
{
|
||||
url: 'milestone_title=%23started',
|
||||
tokenKey: 'milestone',
|
||||
value: 'started',
|
||||
},
|
||||
{
|
||||
url: 'label_name[]=No+Label',
|
||||
tokenKey: 'label',
|
||||
value: 'none',
|
||||
},
|
||||
];
|
||||
|
||||
const IssuableFilteredSearchTokenKeys =
|
||||
new FilteredSearchTokenKeys(tokenKeys, alternativeTokenKeys, conditions);
|
||||
const IssuableFilteredSearchTokenKeys = new FilteredSearchTokenKeys(
|
||||
tokenKeys,
|
||||
alternativeTokenKeys,
|
||||
conditions,
|
||||
);
|
||||
|
||||
export default IssuableFilteredSearchTokenKeys;
|
||||
|
|
|
@ -3,11 +3,7 @@ import RecentSearchesDropdownContent from './components/recent_searches_dropdown
|
|||
import eventHub from './event_hub';
|
||||
|
||||
class RecentSearchesRoot {
|
||||
constructor(
|
||||
recentSearchesStore,
|
||||
recentSearchesService,
|
||||
wrapperElement,
|
||||
) {
|
||||
constructor(recentSearchesStore, recentSearchesService, wrapperElement) {
|
||||
this.store = recentSearchesStore;
|
||||
this.service = recentSearchesService;
|
||||
this.wrapperElement = wrapperElement;
|
||||
|
@ -35,7 +31,9 @@ class RecentSearchesRoot {
|
|||
components: {
|
||||
RecentSearchesDropdownContent,
|
||||
},
|
||||
data() { return state; },
|
||||
data() {
|
||||
return state;
|
||||
},
|
||||
template: `
|
||||
<recent-searches-dropdown-content
|
||||
:items="recentSearches"
|
||||
|
@ -57,7 +55,6 @@ class RecentSearchesRoot {
|
|||
this.vm.$destroy();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default RecentSearchesRoot;
|
||||
|
|
|
@ -2,11 +2,14 @@ import _ from 'underscore';
|
|||
|
||||
class RecentSearchesStore {
|
||||
constructor(initialState = {}, allowedKeys) {
|
||||
this.state = Object.assign({
|
||||
isLocalStorageAvailable: true,
|
||||
recentSearches: [],
|
||||
allowedKeys,
|
||||
}, initialState);
|
||||
this.state = Object.assign(
|
||||
{
|
||||
isLocalStorageAvailable: true,
|
||||
recentSearches: [],
|
||||
allowedKeys,
|
||||
},
|
||||
initialState,
|
||||
);
|
||||
}
|
||||
|
||||
addRecentSearch(newSearch) {
|
||||
|
|
Loading…
Reference in a new issue