diff --git a/app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown.js b/app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown.js
index a7dad506e6d832b09abb5450ab73193c545a2e4c..3f14a8a8a2652d25df25201865055e64b9767ee5 100644
--- a/app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown.js
+++ b/app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown.js
@@ -94,6 +94,7 @@ export class GitLabDropdown {
dataType: this.options.dataType,
beforeSend: this.toggleLoading.bind(this),
success: (data) => {
+ this.dropdown.trigger('done.remote.loading.gl.dropdown');
this.fullData = data;
this.parseData(this.fullData);
this.focusTextInput();
@@ -220,7 +221,12 @@ export class GitLabDropdown {
}
toggleLoading() {
- return $('.dropdown-menu', this.dropdown).toggleClass(LOADING_CLASS);
+ const menu = this.dropdown[0].querySelector('.dropdown-menu');
+ const isLoading = menu.classList.contains(LOADING_CLASS);
+
+ this.dropdown.trigger(`toggle.${isLoading ? 'off' : 'on'}.loading.gl.dropdown`);
+
+ menu.classList.toggle(LOADING_CLASS);
}
togglePage() {
diff --git a/app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown_filter.js b/app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown_filter.js
index 2cb9e9a56a3e88f9191a1c5f3203fad03d0e282c..271347feebd9bf20f94a5e5aa37f64cbea89a67f 100644
--- a/app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown_filter.js
+++ b/app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown_filter.js
@@ -17,9 +17,11 @@ export class GitLabDropdownFilter {
const $inputContainer = this.input.parent();
const $clearButton = $inputContainer.find('.js-dropdown-input-clear');
const filterRemoteDebounced = debounce(() => {
+ options.instance.dropdown.trigger('filtering.gl.dropdown');
$inputContainer.parent().addClass('is-loading');
return this.options.query(this.input.val(), (data) => {
+ options.instance.dropdown.trigger('done.filtering.gl.dropdown');
$inputContainer.parent().removeClass('is-loading');
return this.options.callback(data);
});
diff --git a/app/assets/javascripts/deprecated_jquery_dropdown/index.js b/app/assets/javascripts/deprecated_jquery_dropdown/index.js
index 6a3d20261929eed2a3e4acc105da5346ee62b93e..38236707e06e3d734964a765502458143a82ddf8 100644
--- a/app/assets/javascripts/deprecated_jquery_dropdown/index.js
+++ b/app/assets/javascripts/deprecated_jquery_dropdown/index.js
@@ -5,7 +5,10 @@ export default function initDeprecatedJQueryDropdown($el, opts) {
// eslint-disable-next-line func-names
return $el.each(function () {
if (!$.data(this, 'deprecatedJQueryDropdown')) {
- $.data(this, 'deprecatedJQueryDropdown', new GitLabDropdown(this, opts));
+ const instance = new GitLabDropdown(this, opts);
+
+ $.data(this, 'deprecatedJQueryDropdown', instance);
+ this.GitLabDropdownInstance = instance;
}
});
}
diff --git a/app/assets/javascripts/issuable/issuable_context.js b/app/assets/javascripts/issuable/issuable_context.js
index 8c2e2a5df67a9b94f42e722fdf82dcf8b3dab96f..ef49bd29a40b547c03a1ef3e1cf88f36209f7620 100644
--- a/app/assets/javascripts/issuable/issuable_context.js
+++ b/app/assets/javascripts/issuable/issuable_context.js
@@ -8,6 +8,29 @@ export default class IssuableContext {
this.userSelect = new UsersSelect(currentUser);
this.reviewersSelect = new UsersSelect(currentUser, '.js-reviewer-search');
+ this.reviewersSelect.dropdowns.forEach((glDropdownInstance) => {
+ const jQueryWrapper = glDropdownInstance.dropdown;
+ const domElement = jQueryWrapper[0];
+ const content = domElement.querySelector('.dropdown-content');
+ const loader = domElement.querySelector('.dropdown-loading');
+ const spinner = loader.querySelector('.gl-spinner-container');
+ const realParent = loader.parentNode;
+
+ domElement.classList.add('non-blocking-loader');
+ spinner.classList.remove('gl-mt-7');
+ spinner.classList.add('gl-mt-2');
+
+ jQueryWrapper.on('shown.bs.dropdown', () => {
+ glDropdownInstance.filterInput.focus();
+ });
+ jQueryWrapper.on('toggle.on.loading.gl.dropdown filtering.gl.dropdown', () => {
+ content.appendChild(loader);
+ });
+ jQueryWrapper.on('done.remote.loading.gl.dropdown done.filtering.gl.dropdown', () => {
+ realParent.appendChild(loader);
+ });
+ });
+
$('.issuable-sidebar .inline-update').on('change', 'select', function onClickSelect() {
return $(this).submit();
});
diff --git a/app/assets/javascripts/users_select/index.js b/app/assets/javascripts/users_select/index.js
index ab707e7e69c6fec65fbd27f3e867ee147fbe03d0..1b36f02226dacec88cfd1a738a401996623d5a81 100644
--- a/app/assets/javascripts/users_select/index.js
+++ b/app/assets/javascripts/users_select/index.js
@@ -32,45 +32,46 @@ function UsersSelect(currentUser, els, options = {}) {
const { handleClick, states } = options;
- $els.each((i, dropdown) => {
- const userSelect = this;
- const $dropdown = $(dropdown);
- const options = {
- states,
- projectId: $dropdown.data('projectId'),
- groupId: $dropdown.data('groupId'),
- showCurrentUser: $dropdown.data('currentUser'),
- todoFilter: $dropdown.data('todoFilter'),
- todoStateFilter: $dropdown.data('todoStateFilter'),
- iid: $dropdown.data('iid'),
- issuableType: $dropdown.data('issuableType'),
- targetBranch: $dropdown.data('targetBranch'),
- authorId: $dropdown.data('authorId'),
- showSuggested: $dropdown.data('showSuggested'),
- };
- const showNullUser = $dropdown.data('nullUser');
- const defaultNullUser = $dropdown.data('nullUserDefault');
- const showMenuAbove = $dropdown.data('showMenuAbove');
- const showAnyUser = $dropdown.data('anyUser');
- const firstUser = $dropdown.data('firstUser');
- const defaultLabel = $dropdown.data('defaultLabel');
- const issueURL = $dropdown.data('issueUpdate');
- const $selectbox = $dropdown.closest('.selectbox');
- const $assignToMeLink = $selectbox.next('.assign-to-me-link');
- let $block = $selectbox.closest('.block');
- const abilityName = $dropdown.data('abilityName');
- let $value = $block.find('.value');
- const $collapsedSidebar = $block.find('.sidebar-collapsed-user');
- const $loading = $block.find('.block-loading').addClass('gl-display-none');
- const selectedIdDefault = defaultNullUser && showNullUser ? 0 : null;
- let selectedId = $dropdown.data('selected');
- let assignTo;
- let assigneeTemplate;
- let collapsedAssigneeTemplate;
-
- const suggestedReviewersHelpPath = $dropdown.data('suggestedReviewersHelpPath');
- const suggestedReviewersHeaderTemplate = template(
- `
+ this.dropdowns = $els
+ .map((i, dropdown) => {
+ const userSelect = this;
+ const $dropdown = $(dropdown);
+ const options = {
+ states,
+ projectId: $dropdown.data('projectId'),
+ groupId: $dropdown.data('groupId'),
+ showCurrentUser: $dropdown.data('currentUser'),
+ todoFilter: $dropdown.data('todoFilter'),
+ todoStateFilter: $dropdown.data('todoStateFilter'),
+ iid: $dropdown.data('iid'),
+ issuableType: $dropdown.data('issuableType'),
+ targetBranch: $dropdown.data('targetBranch'),
+ authorId: $dropdown.data('authorId'),
+ showSuggested: $dropdown.data('showSuggested'),
+ };
+ const showNullUser = $dropdown.data('nullUser');
+ const defaultNullUser = $dropdown.data('nullUserDefault');
+ const showMenuAbove = $dropdown.data('showMenuAbove');
+ const showAnyUser = $dropdown.data('anyUser');
+ const firstUser = $dropdown.data('firstUser');
+ const defaultLabel = $dropdown.data('defaultLabel');
+ const issueURL = $dropdown.data('issueUpdate');
+ const $selectbox = $dropdown.closest('.selectbox');
+ const $assignToMeLink = $selectbox.next('.assign-to-me-link');
+ let $block = $selectbox.closest('.block');
+ const abilityName = $dropdown.data('abilityName');
+ let $value = $block.find('.value');
+ const $collapsedSidebar = $block.find('.sidebar-collapsed-user');
+ const $loading = $block.find('.block-loading').addClass('gl-display-none');
+ const selectedIdDefault = defaultNullUser && showNullUser ? 0 : null;
+ let selectedId = $dropdown.data('selected');
+ let assignTo;
+ let assigneeTemplate;
+ let collapsedAssigneeTemplate;
+
+ const suggestedReviewersHelpPath = $dropdown.data('suggestedReviewersHelpPath');
+ const suggestedReviewersHeaderTemplate = template(
+ `
`,
- );
+ );
- if (selectedId === undefined) {
- selectedId = selectedIdDefault;
- }
+ if (selectedId === undefined) {
+ selectedId = selectedIdDefault;
+ }
- const assignYourself = function () {
- const unassignedSelected = $dropdown
- .closest('.selectbox')
- .find(`input[name='${$dropdown.data('fieldName')}'][value=0]`);
+ const assignYourself = function () {
+ const unassignedSelected = $dropdown
+ .closest('.selectbox')
+ .find(`input[name='${$dropdown.data('fieldName')}'][value=0]`);
- if (unassignedSelected) {
- unassignedSelected.remove();
- }
+ if (unassignedSelected) {
+ unassignedSelected.remove();
+ }
- // Save current selected user to the DOM
- const currentUserInfo = $dropdown.data('currentUserInfo') || {};
- const currentUser = userSelect.currentUser || {};
- const fieldName = $dropdown.data('fieldName');
- const userName = currentUserInfo.name;
- const userId = currentUserInfo.id || currentUser.id;
+ // Save current selected user to the DOM
+ const currentUserInfo = $dropdown.data('currentUserInfo') || {};
+ const currentUser = userSelect.currentUser || {};
+ const fieldName = $dropdown.data('fieldName');
+ const userName = currentUserInfo.name;
+ const userId = currentUserInfo.id || currentUser.id;
- const inputHtmlString = template(`
+ const inputHtmlString = template(`
`)({ fieldName, userName, userId });
- if ($selectbox) {
- $dropdown.parent().before(inputHtmlString);
- } else {
- $dropdown.after(inputHtmlString);
+ if ($selectbox) {
+ $dropdown.parent().before(inputHtmlString);
+ } else {
+ $dropdown.after(inputHtmlString);
+ }
+ };
+
+ if ($block[0]) {
+ $block[0].addEventListener('assignYourself', assignYourself);
}
- };
- if ($block[0]) {
- $block[0].addEventListener('assignYourself', assignYourself);
- }
+ const getSelectedUserInputs = function () {
+ return $selectbox.find(`input[name="${$dropdown.data('fieldName')}"]`);
+ };
- const getSelectedUserInputs = function () {
- return $selectbox.find(`input[name="${$dropdown.data('fieldName')}"]`);
- };
-
- const getSelected = function () {
- return getSelectedUserInputs()
- .map((index, input) => parseInt(input.value, 10))
- .get();
- };
-
- const checkMaxSelect = function () {
- const maxSelect = $dropdown.data('maxSelect');
- if (maxSelect) {
- const selected = getSelected();
-
- if (selected.length > maxSelect) {
- const firstSelectedId = selected[0];
- const firstSelected = $dropdown
- .closest('.selectbox')
- .find(`input[name='${$dropdown.data('fieldName')}'][value=${firstSelectedId}]`);
-
- firstSelected.remove();
-
- if ($dropdown.hasClass(elsClassName)) {
- emitSidebarEvent('sidebar.removeReviewer', {
- id: firstSelectedId,
- });
- } else {
- emitSidebarEvent('sidebar.removeAssignee', {
- id: firstSelectedId,
- });
+ const getSelected = function () {
+ return getSelectedUserInputs()
+ .map((index, input) => parseInt(input.value, 10))
+ .get();
+ };
+
+ const checkMaxSelect = function () {
+ const maxSelect = $dropdown.data('maxSelect');
+ if (maxSelect) {
+ const selected = getSelected();
+
+ if (selected.length > maxSelect) {
+ const firstSelectedId = selected[0];
+ const firstSelected = $dropdown
+ .closest('.selectbox')
+ .find(`input[name='${$dropdown.data('fieldName')}'][value=${firstSelectedId}]`);
+
+ firstSelected.remove();
+
+ if ($dropdown.hasClass(elsClassName)) {
+ emitSidebarEvent('sidebar.removeReviewer', {
+ id: firstSelectedId,
+ });
+ } else {
+ emitSidebarEvent('sidebar.removeAssignee', {
+ id: firstSelectedId,
+ });
+ }
}
}
- }
- };
-
- const getMultiSelectDropdownTitle = function (selectedUser, isSelected) {
- const selectedUsers = getSelected().filter((u) => u !== 0);
-
- const firstUser = getSelectedUserInputs()
- .map((index, input) => ({
- name: input.dataset.meta,
- value: parseInt(input.value, 10),
- }))
- .filter((u) => u.id !== 0)
- .get(0);
-
- if (selectedUsers.length === 0) {
- return s__('UsersSelect|Unassigned');
- } else if (selectedUsers.length === 1) {
- return firstUser.name;
- } else if (isSelected) {
- const otherSelected = selectedUsers.filter((s) => s !== selectedUser.id);
+ };
+
+ const getMultiSelectDropdownTitle = function (selectedUser, isSelected) {
+ const selectedUsers = getSelected().filter((u) => u !== 0);
+
+ const firstUser = getSelectedUserInputs()
+ .map((index, input) => ({
+ name: input.dataset.meta,
+ value: parseInt(input.value, 10),
+ }))
+ .filter((u) => u.id !== 0)
+ .get(0);
+
+ if (selectedUsers.length === 0) {
+ return s__('UsersSelect|Unassigned');
+ } else if (selectedUsers.length === 1) {
+ return firstUser.name;
+ } else if (isSelected) {
+ const otherSelected = selectedUsers.filter((s) => s !== selectedUser.id);
+ return sprintf(s__('UsersSelect|%{name} + %{length} more'), {
+ name: selectedUser.name,
+ length: otherSelected.length,
+ });
+ }
return sprintf(s__('UsersSelect|%{name} + %{length} more'), {
- name: selectedUser.name,
- length: otherSelected.length,
+ name: firstUser.name,
+ length: selectedUsers.length - 1,
});
- }
- return sprintf(s__('UsersSelect|%{name} + %{length} more'), {
- name: firstUser.name,
- length: selectedUsers.length - 1,
- });
- };
-
- $assignToMeLink.on('click', (e) => {
- e.preventDefault();
- $(e.currentTarget).hide();
-
- if ($dropdown.data('multiSelect')) {
- assignYourself();
- checkMaxSelect();
-
- const currentUserInfo = $dropdown.data('currentUserInfo');
- $dropdown
- .find('.dropdown-toggle-text')
- .text(getMultiSelectDropdownTitle(currentUserInfo))
- .removeClass('is-default');
- } else {
- const $input = $(`input[name="${$dropdown.data('fieldName')}"]`);
- $input.val(gon.current_user_id);
- selectedId = $input.val();
- $dropdown
- .find('.dropdown-toggle-text')
- .text(gon.current_user_fullname)
- .removeClass('is-default');
- }
- });
-
- $block.on('click', '.js-assign-yourself', (e) => {
- e.preventDefault();
- return assignTo(userSelect.currentUser.id);
- });
-
- assignTo = function (selected) {
- const data = {};
- data[abilityName] = {};
- data[abilityName].assignee_id = selected != null ? selected : null;
- $loading.removeClass('gl-display-none');
- $dropdown.trigger('loading.gl.dropdown');
-
- return axios.put(issueURL, data).then(({ data }) => {
- let user = {};
- let tooltipTitle;
- $dropdown.trigger('loaded.gl.dropdown');
- $loading.addClass('gl-display-none');
- if (data.assignee) {
- user = {
- name: data.assignee.name,
- username: data.assignee.username,
- avatar: data.assignee.avatar_url,
- };
- tooltipTitle = escape(user.name);
+ };
+
+ $assignToMeLink.on('click', (e) => {
+ e.preventDefault();
+ $(e.currentTarget).hide();
+
+ if ($dropdown.data('multiSelect')) {
+ assignYourself();
+ checkMaxSelect();
+
+ const currentUserInfo = $dropdown.data('currentUserInfo');
+ $dropdown
+ .find('.dropdown-toggle-text')
+ .text(getMultiSelectDropdownTitle(currentUserInfo))
+ .removeClass('is-default');
} else {
- user = {
- name: s__('UsersSelect|Unassigned'),
- username: '',
- avatar: '',
- };
- tooltipTitle = s__('UsersSelect|Assignee');
+ const $input = $(`input[name="${$dropdown.data('fieldName')}"]`);
+ $input.val(gon.current_user_id);
+ selectedId = $input.val();
+ $dropdown
+ .find('.dropdown-toggle-text')
+ .text(gon.current_user_fullname)
+ .removeClass('is-default');
}
- $value.html(assigneeTemplate(user));
- $collapsedSidebar.attr('title', tooltipTitle);
- fixTitle($collapsedSidebar);
+ });
- return $collapsedSidebar.html(collapsedAssigneeTemplate(user));
+ $block.on('click', '.js-assign-yourself', (e) => {
+ e.preventDefault();
+ return assignTo(userSelect.currentUser.id);
});
- };
- collapsedAssigneeTemplate = template(
- `<% if( avatar ) { %>
<% } else { %> ${spriteIcon(
- 'user',
- )} <% } %>`,
- );
- assigneeTemplate = template(
- `<% if (username) { %>
<% if( avatar ) { %>
<% } %> <%- name %> @<%- username %> <% } else { %>
+
+ assignTo = function (selected) {
+ const data = {};
+ data[abilityName] = {};
+ data[abilityName].assignee_id = selected != null ? selected : null;
+ $loading.removeClass('gl-display-none');
+ $dropdown.trigger('loading.gl.dropdown');
+
+ return axios.put(issueURL, data).then(({ data }) => {
+ let user = {};
+ let tooltipTitle;
+ $dropdown.trigger('loaded.gl.dropdown');
+ $loading.addClass('gl-display-none');
+ if (data.assignee) {
+ user = {
+ name: data.assignee.name,
+ username: data.assignee.username,
+ avatar: data.assignee.avatar_url,
+ };
+ tooltipTitle = escape(user.name);
+ } else {
+ user = {
+ name: s__('UsersSelect|Unassigned'),
+ username: '',
+ avatar: '',
+ };
+ tooltipTitle = s__('UsersSelect|Assignee');
+ }
+ $value.html(assigneeTemplate(user));
+ $collapsedSidebar.attr('title', tooltipTitle);
+ fixTitle($collapsedSidebar);
+
+ return $collapsedSidebar.html(collapsedAssigneeTemplate(user));
+ });
+ };
+ collapsedAssigneeTemplate = template(
+ `<% if( avatar ) { %>
<% } else { %> ${spriteIcon(
+ 'user',
+ )} <% } %>`,
+ );
+ assigneeTemplate = template(
+ `<% if (username) { %> <% if( avatar ) { %>
<% } %> <%- name %> @<%- username %> <% } else { %>
${sprintf(s__('UsersSelect|No assignee - %{openingTag} assign yourself %{closingTag}'), {
openingTag: '',
closingTag: '',
})} <% } %>`,
- );
- return initDeprecatedJQueryDropdown($dropdown, {
- showMenuAbove,
- data(term, callback) {
- return userSelect.users(term, options, (users) => {
- // GitLabDropdownFilter returns this.instance
- // GitLabDropdownRemote returns this.options.instance
- const deprecatedJQueryDropdown = this.instance || this.options.instance;
- deprecatedJQueryDropdown.options.processData(term, users, callback);
- });
- },
- processData(term, dataArg, callback) {
- // Sometimes the `dataArg` can contain special dropdown items like
- // dividers which we don't want to consider here.
- const data = dataArg.filter((x) => !x.type);
-
- let users = data;
-
- // Only show assigned user list when there is no search term
- if ($dropdown.hasClass('js-multiselect') && term.length === 0) {
- const selectedInputs = getSelectedUserInputs();
-
- // Potential duplicate entries when dealing with issue board
- // because issue board is also managed by vue
- const selectedUsers = uniqBy(selectedInputs, (a) => a.value)
- .filter((input) => {
- const userId = parseInt(input.value, 10);
- const inUsersArray = users.find((u) => u.id === userId);
-
- return !inUsersArray && userId !== 0;
- })
- .map((input) => {
- const userId = parseInt(input.value, 10);
- const { avatarUrl, avatar_url, name, username, canMerge } = input.dataset;
- return {
- avatar_url: avatarUrl || avatar_url || gon.default_avatar_url,
- id: userId,
- name,
- username,
- can_merge: parseBoolean(canMerge),
- };
- });
+ );
+
+ return initDeprecatedJQueryDropdown($dropdown, {
+ showMenuAbove,
+ data(term, callback) {
+ return userSelect.users(term, options, (users) => {
+ // GitLabDropdownFilter returns this.instance
+ // GitLabDropdownRemote returns this.options.instance
+ const deprecatedJQueryDropdown = this.instance || this.options.instance;
+ deprecatedJQueryDropdown.options.processData(term, users, callback);
+ });
+ },
+ processData(term, dataArg, callback) {
+ // Sometimes the `dataArg` can contain special dropdown items like
+ // dividers which we don't want to consider here.
+ const data = dataArg.filter((x) => !x.type);
+
+ let users = data;
+
+ // Only show assigned user list when there is no search term
+ if ($dropdown.hasClass('js-multiselect') && term.length === 0) {
+ const selectedInputs = getSelectedUserInputs();
+
+ // Potential duplicate entries when dealing with issue board
+ // because issue board is also managed by vue
+ const selectedUsers = uniqBy(selectedInputs, (a) => a.value)
+ .filter((input) => {
+ const userId = parseInt(input.value, 10);
+ const inUsersArray = users.find((u) => u.id === userId);
+
+ return !inUsersArray && userId !== 0;
+ })
+ .map((input) => {
+ const userId = parseInt(input.value, 10);
+ const { avatarUrl, avatar_url, name, username, canMerge } = input.dataset;
+ return {
+ avatar_url: avatarUrl || avatar_url || gon.default_avatar_url,
+ id: userId,
+ name,
+ username,
+ can_merge: parseBoolean(canMerge),
+ };
+ });
- users = data.concat(selectedUsers);
- }
+ users = data.concat(selectedUsers);
+ }
- let anyUser;
- let index;
- let len;
- let name;
- let obj;
- let showDivider;
- if (term.length === 0) {
- showDivider = 0;
- if (firstUser) {
- // Move current user to the front of the list
- for (index = 0, len = users.length; index < len; index += 1) {
- obj = users[index];
- if (obj.username === firstUser) {
- users.splice(index, 1);
- users.unshift(obj);
- break;
+ let anyUser;
+ let index;
+ let len;
+ let name;
+ let obj;
+ let showDivider;
+ if (term.length === 0) {
+ showDivider = 0;
+ if (firstUser) {
+ // Move current user to the front of the list
+ for (index = 0, len = users.length; index < len; index += 1) {
+ obj = users[index];
+ if (obj.username === firstUser) {
+ users.splice(index, 1);
+ users.unshift(obj);
+ break;
+ }
}
}
- }
- if (showNullUser) {
- showDivider += 1;
- users.unshift({
- beforeDivider: true,
- name: s__('UsersSelect|Unassigned'),
- id: 0,
- });
- }
- if (showAnyUser) {
- showDivider += 1;
- name = showAnyUser;
- if (name === true) {
- name = s__('UsersSelect|Any User');
+ if (showNullUser) {
+ showDivider += 1;
+ users.unshift({
+ beforeDivider: true,
+ name: s__('UsersSelect|Unassigned'),
+ id: 0,
+ });
}
- anyUser = {
- beforeDivider: true,
- name,
- id: null,
- };
- users.unshift(anyUser);
- }
-
- if (showDivider) {
- users.splice(showDivider, 0, { type: 'divider' });
- }
-
- if ($dropdown.hasClass('js-multiselect')) {
- const selected = getSelected().filter((i) => i !== 0);
-
- if ($dropdown.data('showSuggested')) {
- const suggested = this.suggestedUsers(users);
- if (suggested.length) {
- users = users.filter(
- (u) => !u.suggested || (u.suggested && selected.indexOf(u.id) !== -1),
- );
- users.splice(showDivider + 1, 0, ...suggested);
+ if (showAnyUser) {
+ showDivider += 1;
+ name = showAnyUser;
+ if (name === true) {
+ name = s__('UsersSelect|Any User');
}
+ anyUser = {
+ beforeDivider: true,
+ name,
+ id: null,
+ };
+ users.unshift(anyUser);
}
- if (selected.length > 0) {
- if ($dropdown.data('dropdownHeader')) {
- showDivider += 1;
- users.splice(showDivider, 0, {
- type: 'header',
- content: $dropdown.data('dropdownHeader'),
- });
+ if (showDivider) {
+ users.splice(showDivider, 0, { type: 'divider' });
+ }
+
+ if ($dropdown.hasClass('js-multiselect')) {
+ const selected = getSelected().filter((i) => i !== 0);
+
+ if ($dropdown.data('showSuggested')) {
+ const suggested = this.suggestedUsers(users);
+ if (suggested.length) {
+ users = users.filter(
+ (u) => !u.suggested || (u.suggested && selected.indexOf(u.id) !== -1),
+ );
+ users.splice(showDivider + 1, 0, ...suggested);
+ }
}
- const selectedUsers = users
- .filter((u) => selected.indexOf(u.id) !== -1)
- .sort((a, b) => a.name > b.name);
+ if (selected.length > 0) {
+ if ($dropdown.data('dropdownHeader')) {
+ showDivider += 1;
+ users.splice(showDivider, 0, {
+ type: 'header',
+ content: $dropdown.data('dropdownHeader'),
+ });
+ }
- users = users.filter((u) => selected.indexOf(u.id) === -1);
+ const selectedUsers = users
+ .filter((u) => selected.indexOf(u.id) !== -1)
+ .sort((a, b) => a.name > b.name);
- selectedUsers.forEach((selectedUser) => {
- showDivider += 1;
- users.splice(showDivider, 0, selectedUser);
- });
+ users = users.filter((u) => selected.indexOf(u.id) === -1);
- users.splice(showDivider + 1, 0, { type: 'divider' });
+ selectedUsers.forEach((selectedUser) => {
+ showDivider += 1;
+ users.splice(showDivider, 0, selectedUser);
+ });
+
+ users.splice(showDivider + 1, 0, { type: 'divider' });
+ }
}
}
- }
- callback(users);
- if (showMenuAbove) {
- $dropdown.data('deprecatedJQueryDropdown').positionMenuAbove();
- }
- },
- suggestedUsers(users) {
- const selected = getSelected().filter((i) => i !== 0);
- const suggestedUsers = users.filter((u) => u.suggested && selected.indexOf(u.id) === -1);
-
- if (!suggestedUsers.length) return [];
-
- const items = [
- {
- type: 'header',
- content: suggestedReviewersHeaderTemplate({
- header: $dropdown.data('suggestedReviewersHeader'),
- }),
- },
- ...suggestedUsers,
- { type: 'header', content: $dropdown.data('allMembersHeader') },
- ];
- return items;
- },
- filterable: true,
- filterRemote: true,
- search: {
- fields: ['name', 'username'],
- },
- selectable: true,
- fieldName: $dropdown.data('fieldName'),
- toggleLabel(selected, el, deprecatedJQueryDropdown) {
- const inputValue = deprecatedJQueryDropdown.filterInput.val();
-
- if (this.multiSelect && inputValue === '') {
- // Remove non-users from the fullData array
- const users = deprecatedJQueryDropdown.filteredFullData();
- const callback = deprecatedJQueryDropdown.parseData.bind(deprecatedJQueryDropdown);
-
- // Update the data model
- this.processData(inputValue, users, callback);
- }
+ callback(users);
+ if (showMenuAbove) {
+ $dropdown.data('deprecatedJQueryDropdown').positionMenuAbove();
+ }
+ },
+ suggestedUsers(users) {
+ const selected = getSelected().filter((i) => i !== 0);
+ const suggestedUsers = users.filter((u) => u.suggested && selected.indexOf(u.id) === -1);
+
+ if (!suggestedUsers.length) return [];
+
+ const items = [
+ {
+ type: 'header',
+ content: suggestedReviewersHeaderTemplate({
+ header: $dropdown.data('suggestedReviewersHeader'),
+ }),
+ },
+ ...suggestedUsers,
+ { type: 'header', content: $dropdown.data('allMembersHeader') },
+ ];
+ return items;
+ },
+ filterable: true,
+ filterRemote: true,
+ search: {
+ fields: ['name', 'username'],
+ },
+ selectable: true,
+ fieldName: $dropdown.data('fieldName'),
+ toggleLabel(selected, el, deprecatedJQueryDropdown) {
+ const inputValue = deprecatedJQueryDropdown.filterInput.val();
+
+ if (this.multiSelect && inputValue === '') {
+ // Remove non-users from the fullData array
+ const users = deprecatedJQueryDropdown.filteredFullData();
+ const callback = deprecatedJQueryDropdown.parseData.bind(deprecatedJQueryDropdown);
+
+ // Update the data model
+ this.processData(inputValue, users, callback);
+ }
- if (this.multiSelect) {
- return getMultiSelectDropdownTitle(selected, $(el).hasClass('is-active'));
- }
+ if (this.multiSelect) {
+ return getMultiSelectDropdownTitle(selected, $(el).hasClass('is-active'));
+ }
- if (selected && 'id' in selected && $(el).hasClass('is-active')) {
- $dropdown.find('.dropdown-toggle-text').removeClass('is-default');
- if (selected.text) {
- return selected.text;
+ if (selected && 'id' in selected && $(el).hasClass('is-active')) {
+ $dropdown.find('.dropdown-toggle-text').removeClass('is-default');
+ if (selected.text) {
+ return selected.text;
+ }
+ return selected.name;
}
- return selected.name;
- }
- $dropdown.find('.dropdown-toggle-text').addClass('is-default');
- return defaultLabel;
- },
- defaultLabel,
- hidden() {
- if ($dropdown.hasClass('js-multiselect')) {
- if ($dropdown.hasClass(elsClassName)) {
- if (!$dropdown.closest('.merge-request-form').length) {
- $dropdown.data('deprecatedJQueryDropdown').clearMenu();
- $dropdown.closest('.selectbox').children('input[type="hidden"]').remove();
+ $dropdown.find('.dropdown-toggle-text').addClass('is-default');
+ return defaultLabel;
+ },
+ defaultLabel,
+ hidden() {
+ if ($dropdown.hasClass('js-multiselect')) {
+ if ($dropdown.hasClass(elsClassName)) {
+ if (!$dropdown.closest('.merge-request-form').length) {
+ $dropdown.data('deprecatedJQueryDropdown').clearMenu();
+ $dropdown.closest('.selectbox').children('input[type="hidden"]').remove();
+ }
+ emitSidebarEvent('sidebar.saveReviewers');
+ } else {
+ emitSidebarEvent('sidebar.saveAssignees');
}
- emitSidebarEvent('sidebar.saveReviewers');
- } else {
- emitSidebarEvent('sidebar.saveAssignees');
}
- }
- if (!$dropdown.data('alwaysShowSelectbox')) {
- $selectbox.hide();
+ if (!$dropdown.data('alwaysShowSelectbox')) {
+ $selectbox.hide();
- // Recalculate where .value is because vue might have changed it
- $block = $selectbox.closest('.block');
- $value = $block.find('.value');
- // display:block overrides the hide-collapse rule
- $value.css('display', '');
- }
+ // Recalculate where .value is because vue might have changed it
+ $block = $selectbox.closest('.block');
+ $value = $block.find('.value');
+ // display:block overrides the hide-collapse rule
+ $value.css('display', '');
+ }
- $('.dropdown-input-field', $block).val('');
- },
- multiSelect: $dropdown.hasClass('js-multiselect'),
- inputMeta: $dropdown.data('inputMeta'),
- clicked(options) {
- const { $el, e, isMarking } = options;
- const user = options.selectedObj;
+ $('.dropdown-input-field', $block).val('');
+ },
+ multiSelect: $dropdown.hasClass('js-multiselect'),
+ inputMeta: $dropdown.data('inputMeta'),
+ clicked(options) {
+ const { $el, e, isMarking } = options;
+ const user = options.selectedObj;
- dispose($el);
+ dispose($el);
- if ($dropdown.hasClass('js-multiselect')) {
- const isActive = $el.hasClass('is-active');
- const previouslySelected = $dropdown
- .closest('.selectbox')
- .find(`input[name='${$dropdown.data('fieldName')}'][value!=0]`);
+ if ($dropdown.hasClass('js-multiselect')) {
+ const isActive = $el.hasClass('is-active');
+ const previouslySelected = $dropdown
+ .closest('.selectbox')
+ .find(`input[name='${$dropdown.data('fieldName')}'][value!=0]`);
- // Enables support for limiting the number of users selected
- // Automatically removes the first on the list if more users are selected
- checkMaxSelect();
+ // Enables support for limiting the number of users selected
+ // Automatically removes the first on the list if more users are selected
+ checkMaxSelect();
- if (user.beforeDivider && user.name.toLowerCase() === 'unassigned') {
- // Unassigned selected
- previouslySelected.each((index, element) => {
- element.remove();
- });
- if ($dropdown.hasClass(elsClassName)) {
- emitSidebarEvent('sidebar.removeAllReviewers');
+ if (user.beforeDivider && user.name.toLowerCase() === 'unassigned') {
+ // Unassigned selected
+ previouslySelected.each((index, element) => {
+ element.remove();
+ });
+ if ($dropdown.hasClass(elsClassName)) {
+ emitSidebarEvent('sidebar.removeAllReviewers');
+ } else {
+ emitSidebarEvent('sidebar.removeAllAssignees');
+ }
+ } else if (isActive) {
+ // user selected
+ if ($dropdown.hasClass(elsClassName)) {
+ emitSidebarEvent('sidebar.addReviewer', user);
+ } else {
+ emitSidebarEvent('sidebar.addAssignee', user);
+ }
+
+ // Remove unassigned selection (if it was previously selected)
+ const unassignedSelected = $dropdown
+ .closest('.selectbox')
+ .find(`input[name='${$dropdown.data('fieldName')}'][value=0]`);
+
+ if (unassignedSelected) {
+ unassignedSelected.remove();
+ }
} else {
- emitSidebarEvent('sidebar.removeAllAssignees');
+ if (previouslySelected.length === 0) {
+ // Select unassigned because there is no more selected users
+ this.addInput($dropdown.data('fieldName'), 0, {});
+ }
+
+ // User unselected
+ if ($dropdown.hasClass(elsClassName)) {
+ emitSidebarEvent('sidebar.removeReviewer', user);
+ } else {
+ emitSidebarEvent('sidebar.removeAssignee', user);
+ }
}
- } else if (isActive) {
- // user selected
- if ($dropdown.hasClass(elsClassName)) {
- emitSidebarEvent('sidebar.addReviewer', user);
+
+ if (getSelected().find((u) => u === gon.current_user_id)) {
+ $assignToMeLink.hide();
} else {
- emitSidebarEvent('sidebar.addAssignee', user);
+ $assignToMeLink.show();
}
+ }
- // Remove unassigned selection (if it was previously selected)
- const unassignedSelected = $dropdown
- .closest('.selectbox')
- .find(`input[name='${$dropdown.data('fieldName')}'][value=0]`);
+ const page = $('body').attr('data-page');
+ const isIssueIndex = page === 'projects:issues:index';
+ const isMRIndex = page === page && page === 'projects:merge_requests:index';
+ if (
+ $dropdown.hasClass('js-filter-bulk-update') ||
+ $dropdown.hasClass('js-issuable-form-dropdown')
+ ) {
+ e.preventDefault();
- if (unassignedSelected) {
- unassignedSelected.remove();
- }
- } else {
- if (previouslySelected.length === 0) {
- // Select unassigned because there is no more selected users
- this.addInput($dropdown.data('fieldName'), 0, {});
- }
+ const isSelecting = user.id !== selectedId;
+ selectedId = isSelecting ? user.id : selectedIdDefault;
- // User unselected
- if ($dropdown.hasClass(elsClassName)) {
- emitSidebarEvent('sidebar.removeReviewer', user);
+ if (selectedId === gon.current_user_id) {
+ $('.assign-to-me-link').hide();
} else {
- emitSidebarEvent('sidebar.removeAssignee', user);
+ $('.assign-to-me-link').show();
}
+ return;
}
-
- if (getSelected().find((u) => u === gon.current_user_id)) {
- $assignToMeLink.hide();
- } else {
- $assignToMeLink.show();
+ if (handleClick) {
+ e.preventDefault();
+ handleClick(user, isMarking);
+ } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
+ return Issuable.filterResults($dropdown.closest('form'));
+ } else if ($dropdown.hasClass('js-filter-submit')) {
+ return $dropdown.closest('form').submit();
+ } else if (!$dropdown.hasClass('js-multiselect')) {
+ const selected = $dropdown
+ .closest('.selectbox')
+ .find(`input[name='${$dropdown.data('fieldName')}']`)
+ .val();
+ return assignTo(selected);
}
- }
- const page = $('body').attr('data-page');
- const isIssueIndex = page === 'projects:issues:index';
- const isMRIndex = page === page && page === 'projects:merge_requests:index';
- if (
- $dropdown.hasClass('js-filter-bulk-update') ||
- $dropdown.hasClass('js-issuable-form-dropdown')
- ) {
- e.preventDefault();
-
- const isSelecting = user.id !== selectedId;
- selectedId = isSelecting ? user.id : selectedIdDefault;
+ // Automatically close dropdown after assignee is selected
+ // since CE has no multiple assignees
+ // EE does not have a max-select
+ if ($dropdown.data('maxSelect') && getSelected().length === $dropdown.data('maxSelect')) {
+ // Close the dropdown
+ $dropdown.dropdown('toggle');
+ }
+ },
+ id(user) {
+ return user.id;
+ },
+ opened(e) {
+ const $el = $(e.currentTarget);
+ const selected = getSelected();
+ $el.find('.is-active').removeClass('is-active');
+
+ function highlightSelected(id) {
+ $el.find(`li[data-user-id="${id}"] .dropdown-menu-user-link`).addClass('is-active');
+ }
- if (selectedId === gon.current_user_id) {
- $('.assign-to-me-link').hide();
+ if (selected.length > 0) {
+ getSelected().forEach((selectedId) => highlightSelected(selectedId));
} else {
- $('.assign-to-me-link').show();
+ highlightSelected(selectedId);
}
- return;
- }
- if (handleClick) {
- e.preventDefault();
- handleClick(user, isMarking);
- } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
- return Issuable.filterResults($dropdown.closest('form'));
- } else if ($dropdown.hasClass('js-filter-submit')) {
- return $dropdown.closest('form').submit();
- } else if (!$dropdown.hasClass('js-multiselect')) {
- const selected = $dropdown
- .closest('.selectbox')
- .find(`input[name='${$dropdown.data('fieldName')}']`)
- .val();
- return assignTo(selected);
- }
+ },
+ updateLabel: $dropdown.data('dropdownTitle'),
+ renderRow(user) {
+ const username = user.username ? `@${user.username}` : '';
+ const avatar = user.avatar_url ? user.avatar_url : gon.default_avatar_url;
- // Automatically close dropdown after assignee is selected
- // since CE has no multiple assignees
- // EE does not have a max-select
- if ($dropdown.data('maxSelect') && getSelected().length === $dropdown.data('maxSelect')) {
- // Close the dropdown
- $dropdown.dropdown('toggle');
- }
- },
- id(user) {
- return user.id;
- },
- opened(e) {
- const $el = $(e.currentTarget);
- const selected = getSelected();
- $el.find('.is-active').removeClass('is-active');
-
- function highlightSelected(id) {
- $el.find(`li[data-user-id="${id}"] .dropdown-menu-user-link`).addClass('is-active');
- }
-
- if (selected.length > 0) {
- getSelected().forEach((selectedId) => highlightSelected(selectedId));
- } else {
- highlightSelected(selectedId);
- }
- },
- updateLabel: $dropdown.data('dropdownTitle'),
- renderRow(user) {
- const username = user.username ? `@${user.username}` : '';
- const avatar = user.avatar_url ? user.avatar_url : gon.default_avatar_url;
+ let selected = false;
- let selected = false;
+ if (this.multiSelect) {
+ selected = getSelected().find((u) => user.id === u);
- if (this.multiSelect) {
- selected = getSelected().find((u) => user.id === u);
-
- const { fieldName } = this;
- const field = $dropdown
- .closest('.selectbox')
- .find(`input[name='${fieldName}'][value='${user.id}']`);
+ const { fieldName } = this;
+ const field = $dropdown
+ .closest('.selectbox')
+ .find(`input[name='${fieldName}'][value='${user.id}']`);
- if (field.length) {
- selected = true;
+ if (field.length) {
+ selected = true;
+ }
+ } else {
+ selected = user.id === selectedId;
}
- } else {
- selected = user.id === selectedId;
- }
- let img = '';
- if (user.beforeDivider != null) {
- `${escape(
- user.name,
- )}`;
- } else {
- // 0 margin, because it's now handled by a wrapper
- img = `
`;
- }
+ let img = '';
+ if (user.beforeDivider != null) {
+ `${escape(
+ user.name,
+ )}`;
+ } else {
+ // 0 margin, because it's now handled by a wrapper
+ img = `
`;
+ }
- return userSelect.renderRow(
- options.issuableType,
- user,
- selected,
- username,
- img,
- elsClassName,
- );
- },
- });
- });
+ return userSelect.renderRow(
+ options.issuableType,
+ user,
+ selected,
+ username,
+ img,
+ elsClassName,
+ );
+ },
+ })
+ .get()
+ .map((dropdown) => dropdown.GitLabDropdownInstance);
+ })
+ .get();
}
// Return users list. Filtered by query
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 884cb70cb9fcc19e03725a51bebbcaca8878b2ce..a467d9e8c8afca4a1a768f50d0fc41f3df8f7f96 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -287,6 +287,23 @@
}
}
+ .non-blocking-loader & {
+ &.is-loading{
+ .dropdown-content {
+ display: block;
+ height: 2rem;
+
+ ul{
+ display: none;
+ }
+ }
+ }
+
+ .dropdown-loading{
+ position: relative;
+ }
+ }
+
ul {
margin: 0;
padding: 0;