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