summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/gfm_auto_complete.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/gfm_auto_complete.js')
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js51
1 files changed, 43 insertions, 8 deletions
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 62948f74aaa..202f04f98f6 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -1,9 +1,12 @@
import $ from 'jquery';
import '~/lib/utils/jquery_at_who';
import { escape, template } from 'lodash';
+import { s__ } from '~/locale';
import SidebarMediator from '~/sidebar/sidebar_mediator';
+import { isUserBusy } from '~/set_status_modal/utils';
import glRegexp from './lib/utils/regexp';
import AjaxCache from './lib/utils/ajax_cache';
+import axios from '~/lib/utils/axios_utils';
import { spriteIcon } from './lib/utils/common_utils';
import * as Emoji from '~/emoji';
@@ -39,6 +42,7 @@ export function membersBeforeSave(members) {
title: sanitize(title),
search: sanitize(`${member.username} ${member.name}`),
icon: avatarIcon,
+ availability: member.availability,
};
});
}
@@ -52,6 +56,7 @@ export const defaultAutocompleteConfig = {
milestones: true,
labels: true,
snippets: true,
+ vulnerabilities: true,
};
class GfmAutoComplete {
@@ -59,6 +64,7 @@ class GfmAutoComplete {
this.dataSources = dataSources;
this.cachedData = {};
this.isLoadingData = {};
+ this.previousQuery = '';
}
setup(input, enableMap = defaultAutocompleteConfig) {
@@ -253,13 +259,17 @@ class GfmAutoComplete {
alias: 'users',
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
- const { avatarTag, username, title, icon } = value;
+ const { avatarTag, username, title, icon, availability } = value;
if (username != null) {
tmpl = GfmAutoComplete.Members.templateFunction({
avatarTag,
username,
title,
icon,
+ availabilityStatus:
+ availability && isUserBusy(availability)
+ ? `<span class="gl-text-gray-500"> ${s__('UserAvailability|(Busy)')}</span>`
+ : '',
});
}
return tmpl;
@@ -554,7 +564,7 @@ class GfmAutoComplete {
}
getDefaultCallbacks() {
- const fetchData = this.fetchData.bind(this);
+ const self = this;
return {
sorter(query, items, searchKey) {
@@ -567,7 +577,14 @@ class GfmAutoComplete {
},
filter(query, data, searchKey) {
if (GfmAutoComplete.isLoading(data)) {
- fetchData(this.$inputor, this.at);
+ self.fetchData(this.$inputor, this.at);
+ return data;
+ } else if (
+ GfmAutoComplete.isTypeWithBackendFiltering(this.at) &&
+ self.previousQuery !== query
+ ) {
+ self.fetchData(this.$inputor, this.at, query);
+ self.previousQuery = query;
return data;
}
return $.fn.atwho.default.callbacks.filter(query, data, searchKey);
@@ -615,13 +632,22 @@ class GfmAutoComplete {
};
}
- fetchData($input, at) {
+ fetchData($input, at, search) {
if (this.isLoadingData[at]) return;
this.isLoadingData[at] = true;
const dataSource = this.dataSources[GfmAutoComplete.atTypeMap[at]];
- if (this.cachedData[at]) {
+ if (GfmAutoComplete.isTypeWithBackendFiltering(at)) {
+ axios
+ .get(dataSource, { params: { search } })
+ .then(({ data }) => {
+ this.loadData($input, at, data);
+ })
+ .catch(() => {
+ this.isLoadingData[at] = false;
+ });
+ } else if (this.cachedData[at]) {
this.loadData($input, at, this.cachedData[at]);
} else if (GfmAutoComplete.atTypeMap[at] === 'emojis') {
this.loadEmojiData($input, at).catch(() => {});
@@ -707,7 +733,9 @@ class GfmAutoComplete {
// https://github.com/ichord/At.js
const atSymbolsWithBar = Object.keys(controllers)
.join('|')
- .replace(/[$]/, '\\$&');
+ .replace(/[$]/, '\\$&')
+ .replace(/([[\]:])/g, '\\$1');
+
const atSymbolsWithoutBar = Object.keys(controllers).join('');
const targetSubtext = subtext.split(GfmAutoComplete.regexSubtext).pop();
const resultantFlag = flag.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
@@ -738,9 +766,14 @@ GfmAutoComplete.atTypeMap = {
'~': 'labels',
'%': 'milestones',
'/': 'commands',
+ '[vulnerability:': 'vulnerabilities',
$: 'snippets',
};
+GfmAutoComplete.typesWithBackendFiltering = ['vulnerabilities'];
+GfmAutoComplete.isTypeWithBackendFiltering = type =>
+ GfmAutoComplete.typesWithBackendFiltering.includes(GfmAutoComplete.atTypeMap[type]);
+
function findEmoji(name) {
return Emoji.searchEmoji(name, { match: 'contains', raw: true }).sort((a, b) => {
if (a.index !== b.index) {
@@ -775,8 +808,10 @@ GfmAutoComplete.Emoji = {
};
// Team Members
GfmAutoComplete.Members = {
- templateFunction({ avatarTag, username, title, icon }) {
- return `<li>${avatarTag} ${username} <small>${escape(title)}</small> ${icon}</li>`;
+ templateFunction({ avatarTag, username, title, icon, availabilityStatus }) {
+ return `<li>${avatarTag} ${username} <small>${escape(
+ title,
+ )}${availabilityStatus}</small> ${icon}</li>`;
},
};
GfmAutoComplete.Labels = {