diff options
Diffstat (limited to 'app/assets/javascripts/labels_select.js')
-rw-r--r-- | app/assets/javascripts/labels_select.js | 515 |
1 files changed, 0 insertions, 515 deletions
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js deleted file mode 100644 index 68019a35dbb..00000000000 --- a/app/assets/javascripts/labels_select.js +++ /dev/null @@ -1,515 +0,0 @@ -/* eslint-disable func-names, no-underscore-dangle, no-new, consistent-return, no-shadow, no-param-reassign, no-lonely-if, no-empty */ -/* global Issuable */ - -import $ from 'jquery'; -import { difference, isEqual, escape, sortBy, template, union } from 'lodash'; -import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown'; -import IssuableBulkUpdateActions from '~/issuable_bulk_update_sidebar/issuable_bulk_update_actions'; -import { isScopedLabel } from '~/lib/utils/common_utils'; -import CreateLabelDropdown from './create_label'; -import createFlash from './flash'; -import axios from './lib/utils/axios_utils'; -import { sprintf, __ } from './locale'; - -export default class LabelsSelect { - constructor(els, options = {}) { - const _this = this; - - let $els = $(els); - - if (!els) { - $els = $('.js-label-select'); - } - - $els.each((i, dropdown) => { - const $dropdown = $(dropdown); - const $dropdownContainer = $dropdown.closest('.labels-filter'); - const namespacePath = $dropdown.data('namespacePath'); - const projectPath = $dropdown.data('projectPath'); - const issueUpdateURL = $dropdown.data('issueUpdate'); - let selectedLabel = $dropdown.data('selected'); - if (selectedLabel != null && !$dropdown.hasClass('js-multiselect')) { - selectedLabel = selectedLabel.split(','); - } - const showNo = $dropdown.data('showNo'); - const showAny = $dropdown.data('showAny'); - const showMenuAbove = $dropdown.data('showMenuAbove'); - const defaultLabel = $dropdown.data('defaultLabel') || __('Label'); - const abilityName = $dropdown.data('abilityName'); - const $selectbox = $dropdown.closest('.selectbox'); - const $block = $selectbox.closest('.block'); - const $form = $dropdown.closest('form, .js-issuable-update'); - const $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span'); - const $value = $block.find('.value'); - const $loading = $block.find('.block-loading').addClass('gl-display-none'); - const fieldName = $dropdown.data('fieldName'); - let initialSelected = $selectbox - .find(`input[name="${$dropdown.data('fieldName')}"]`) - .map(function () { - return this.value; - }) - .get(); - const scopedLabels = $dropdown.data('scopedLabels'); - const { handleClick } = options; - - if ($dropdown.closest('.dropdown').find('.dropdown-new-label').length) { - new CreateLabelDropdown( - $dropdown.closest('.dropdown').find('.dropdown-new-label'), - namespacePath, - projectPath, - ); - } - - const saveLabelData = function () { - const selected = $dropdown - .closest('.selectbox') - .find(`input[name='${fieldName}']`) - .map(function () { - return this.value; - }) - .get(); - - if (isEqual(initialSelected, selected)) return; - initialSelected = selected; - - const data = {}; - data[abilityName] = {}; - data[abilityName].label_ids = selected; - if (!selected.length) { - data[abilityName].label_ids = ['']; - } - $loading.removeClass('gl-display-none'); - $dropdown.trigger('loading.gl.dropdown'); - axios - .put(issueUpdateURL, data) - .then(({ data }) => { - let template; - $loading.addClass('gl-display-none'); - $dropdown.trigger('loaded.gl.dropdown'); - $selectbox.hide(); - data.issueUpdateURL = issueUpdateURL; - let labelCount = 0; - if (data.labels.length && issueUpdateURL) { - template = LabelsSelect.getLabelTemplate({ - labels: sortBy(data.labels, 'title'), - issueUpdateURL, - enableScopedLabels: scopedLabels, - }); - labelCount = data.labels.length; - - // EE Specific - if (IS_EE) { - /** - * For Scoped labels, the last label selected with the - * same key will be applied to the current issueable. - * - * If these are the labels - priority::1, priority::2; and if - * we apply them in the same order, only priority::2 will stick - * with the issuable. - * - * In the current dropdown implementation, we keep track of all - * the labels selected via a hidden DOM element. Since a User - * can select priority::1 and priority::2 at the same time, the - * DOM will have 2 hidden input and the dropdown will show both - * the items selected but in reality server only applied - * priority::2. - * - * We find all the labels then find all the labels server accepted - * and then remove the excess ones. - */ - const toRemoveIds = Array.from( - $form.find(`input[type="hidden"][name="${fieldName}"]`), - ) - .map((el) => el.value) - .map(Number); - - data.labels.forEach((label) => { - const index = toRemoveIds.indexOf(label.id); - toRemoveIds.splice(index, 1); - }); - - toRemoveIds.forEach((id) => { - $form - .find(`input[type="hidden"][name="${fieldName}"][value="${id}"]`) - .last() - .remove(); - }); - } - } else { - template = `<span class="no-value">${__('None')}</span>`; - } - $value.removeAttr('style').html(template); - $sidebarCollapsedValue.text(labelCount); - - $('.has-tooltip', $value).tooltip({ - container: 'body', - }); - }) - .catch(() => - createFlash({ - message: __('Error saving label update.'), - }), - ); - }; - initDeprecatedJQueryDropdown($dropdown, { - showMenuAbove, - data(term, callback) { - const labelUrl = $dropdown.attr('data-labels'); - axios - .get(labelUrl) - .then((res) => { - let { data } = res; - if ($dropdown.hasClass('js-extra-options')) { - const extraData = []; - if (showNo) { - extraData.unshift({ - id: 0, - title: __('No label'), - }); - } - if (showAny) { - extraData.unshift({ - isAny: true, - title: __('Any label'), - }); - } - if (extraData.length) { - extraData.push({ type: 'divider' }); - data = extraData.concat(data); - } - } - - callback(data); - if (showMenuAbove) { - $dropdown.data('deprecatedJQueryDropdown').positionMenuAbove(); - } - }) - .catch(() => - createFlash({ - message: __('Error fetching labels.'), - }), - ); - }, - renderRow(label) { - let colorEl; - - const selectedClass = []; - const removesAll = label.id <= 0 || label.id == null; - - if ($dropdown.hasClass('js-filter-bulk-update')) { - const indeterminate = $dropdown.data('indeterminate') || []; - const marked = $dropdown.data('marked') || []; - - if (indeterminate.indexOf(label.id) !== -1) { - selectedClass.push('is-indeterminate'); - } - - if (marked.indexOf(label.id) !== -1) { - // Remove is-indeterminate class if the item will be marked as active - const i = selectedClass.indexOf('is-indeterminate'); - if (i !== -1) { - selectedClass.splice(i, 1); - } - selectedClass.push('is-active'); - } - } else { - if (this.id(label)) { - const dropdownValue = this.id(label).toString().replace(/'/g, "\\'"); - - if ( - $form.find( - `input[type='hidden'][name='${this.fieldName}'][value='${dropdownValue}']`, - ).length - ) { - selectedClass.push('is-active'); - } - } - - if (this.multiSelect && removesAll) { - selectedClass.push('dropdown-clear-active'); - } - } - - if (label.color) { - colorEl = `<span class='dropdown-label-box' style='background: ${label.color}'></span>`; - } else { - colorEl = ''; - } - - const linkEl = document.createElement('a'); - linkEl.href = '#'; - - // We need to identify which items are actually labels - if (label.id) { - const selectedLayoutClasses = ['d-flex', 'flex-row', 'text-break-word']; - selectedClass.push('label-item', ...selectedLayoutClasses); - linkEl.dataset.labelId = label.id; - } - - linkEl.className = selectedClass.join(' '); - linkEl.innerHTML = `${colorEl} ${escape(label.title)}`; - - const listItemEl = document.createElement('li'); - listItemEl.appendChild(linkEl); - - return listItemEl; - }, - search: { - fields: ['title'], - }, - selectable: true, - filterable: true, - selected: $dropdown.data('selected') || [], - toggleLabel(selected, el) { - const $dropdownParent = $dropdown.parent(); - const $dropdownInputField = $dropdownParent.find('.dropdown-input-field'); - const isSelected = el !== null ? el.hasClass('is-active') : false; - - const title = selected ? selected.title : null; - const selectedLabels = this.selected; - - if ($dropdownInputField.length && $dropdownInputField.val().length) { - $dropdownParent.find('.dropdown-input-clear').trigger('click'); - } - - if (selected && selected.id === 0) { - this.selected = []; - return __('No label'); - } else if (isSelected) { - this.selected.push(title); - } else if (!isSelected && title) { - const index = this.selected.indexOf(title); - this.selected.splice(index, 1); - } - - if (selectedLabels.length === 1) { - return selectedLabels; - } else if (selectedLabels.length) { - return sprintf(__('%{firstLabel} +%{labelCount} more'), { - firstLabel: selectedLabels[0], - labelCount: selectedLabels.length - 1, - }); - } - return defaultLabel; - }, - fieldName: $dropdown.data('fieldName'), - id(label) { - if (label.id <= 0) return label.title; - - if ($dropdown.hasClass('js-issuable-form-dropdown')) { - return label.id; - } - - if ($dropdown.hasClass('js-filter-submit') && label.isAny == null) { - return label.title; - } - return label.id; - }, - hidden() { - const page = $('body').attr('data-page'); - const isIssueIndex = page === 'projects:issues:index'; - const isMRIndex = page === 'projects:merge_requests:index'; - $selectbox.hide(); - // display:block overrides the hide-collapse rule - $value.removeAttr('style'); - - if ($dropdown.hasClass('js-issuable-form-dropdown')) { - return; - } - - if ( - $('html') - .attr('class') - .match(/issue-boards-page|epic-boards-page/) - ) { - return; - } - if ($dropdown.hasClass('js-multiselect')) { - if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { - Issuable.filterResults($dropdown.closest('form')); - } else if ($dropdown.hasClass('js-filter-submit')) { - $dropdown.closest('form').submit(); - } else { - if (!$dropdown.hasClass('js-filter-bulk-update')) { - saveLabelData(); - $dropdown.data('deprecatedJQueryDropdown').clearMenu(); - } - } - } - }, - multiSelect: $dropdown.hasClass('js-multiselect'), - vue: false, - clicked(clickEvent) { - const { e, isMarking } = clickEvent; - const label = clickEvent.selectedObj; - - const page = $('body').attr('data-page'); - const isIssueIndex = page === 'projects:issues:index'; - const isMRIndex = page === 'projects:merge_requests:index'; - - if ($dropdown.parent().find('.is-active:not(.dropdown-clear-active)').length) { - $dropdown.parent().find('.dropdown-clear-active').removeClass('is-active'); - } - - if ($dropdown.hasClass('js-issuable-form-dropdown')) { - return; - } - - if ($dropdown.hasClass('js-filter-bulk-update')) { - _this.enableBulkLabelDropdown(); - _this.setDropdownData($dropdown, isMarking, label.id); - return; - } - - if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { - if (!$dropdown.hasClass('js-multiselect')) { - selectedLabel = label.title; - return Issuable.filterResults($dropdown.closest('form')); - } - } else if ($dropdown.hasClass('js-filter-submit')) { - return $dropdown.closest('form').submit(); - } else if (handleClick) { - e.preventDefault(); - handleClick(label); - } else { - if ($dropdown.hasClass('js-multiselect')) { - } else { - return saveLabelData(); - } - } - }, - preserveContext: true, - }); - - // Set dropdown data - _this.setOriginalDropdownData($dropdownContainer, $dropdown); - }); - this.bindEvents(); - } - - static getLabelTemplate(tplData) { - // We could use ES6 template string here - // and properly indent markup for readability - // but that also introduces unintended white-space - // so best approach is to use traditional way of - // concatenation - // see: http://2ality.com/2016/05/template-literal-whitespace.html#joining-arrays - - const linkOpenTag = - '<a href="<%- issueUpdateURL.slice(0, issueUpdateURL.lastIndexOf("/")) %>?label_name[]=<%- encodeURIComponent(label.title) %>" class="gl-link gl-label-link has-tooltip" <%= linkAttrs %> title="<%= tooltipTitleTemplate({ label, isScopedLabel, enableScopedLabels, escapeStr }) %>">'; - const labelTemplate = template( - [ - '<span class="gl-label">', - linkOpenTag, - '<span class="gl-label-text <%= labelTextClass({ label, escapeStr }) %>" style="background-color: <%= escapeStr(label.color) %>;">', - '<%- label.title %>', - '</span>', - '</a>', - '</span>', - ].join(''), - ); - - const labelTextClass = ({ label, escapeStr }) => { - return escapeStr( - label.text_color === '#FFFFFF' ? 'gl-label-text-light' : 'gl-label-text-dark', - ); - }; - - const rightLabelTextClass = ({ label, escapeStr }) => { - return escapeStr(label.text_color === '#333333' ? labelTextClass({ label, escapeStr }) : ''); - }; - - const scopedLabelTemplate = template( - [ - '<span class="gl-label gl-label-scoped" style="color: <%= escapeStr(label.color) %>; --label-inset-border: inset 0 0 0 2px <%= escapeStr(label.color) %>;">', - linkOpenTag, - '<span class="gl-label-text <%= labelTextClass({ label, escapeStr }) %>" style="background-color: <%= escapeStr(label.color) %>;">', - '<%- label.title.slice(0, label.title.lastIndexOf("::")) %>', - '</span>', - '<span class="gl-label-text <%= rightLabelTextClass({ label, escapeStr }) %>">', - '<%- label.title.slice(label.title.lastIndexOf("::") + 2) %>', - '</span>', - '</a>', - '</span>', - ].join(''), - ); - - const tooltipTitleTemplate = template( - [ - '<% if (isScopedLabel(label) && enableScopedLabels) { %>', - "<span class='font-weight-bold scoped-label-tooltip-title'>Scoped label</span>", - '<br />', - '<%= escapeStr(label.description) %>', - '<% } else { %>', - '<%= escapeStr(label.description) %>', - '<% } %>', - ].join(''), - ); - - const tpl = template( - [ - '<% labels.forEach(function(label){ %>', - '<% if (isScopedLabel(label) && enableScopedLabels) { %>', - '<%= scopedLabelTemplate({ label, issueUpdateURL, isScopedLabel, enableScopedLabels, labelTextClass, rightLabelTextClass, tooltipTitleTemplate, escapeStr, linkAttrs: \'data-html="true"\' }) %>', - '<% } else { %>', - '<%= labelTemplate({ label, issueUpdateURL, isScopedLabel, enableScopedLabels, labelTextClass, tooltipTitleTemplate, escapeStr, linkAttrs: "" }) %>', - '<% } %>', - '<% }); %>', - ].join(''), - ); - - return tpl({ - ...tplData, - labelTemplate, - labelTextClass, - rightLabelTextClass, - scopedLabelTemplate, - tooltipTitleTemplate, - isScopedLabel, - escapeStr: escape, - }); - } - - bindEvents() { - return $('body').on( - 'change', - '.issuable-list input[type="checkbox"]', - this.onSelectCheckboxIssue, - ); - } - // eslint-disable-next-line class-methods-use-this - onSelectCheckboxIssue() { - if ($('.issuable-list input[type="checkbox"]:checked').length) { - return; - } - return $('.issues-bulk-update .labels-filter .dropdown-toggle-text').text(__('Label')); - } - // eslint-disable-next-line class-methods-use-this - enableBulkLabelDropdown() { - IssuableBulkUpdateActions.willUpdateLabels = true; - } - // eslint-disable-next-line class-methods-use-this - setDropdownData($dropdown, isMarking, labelId) { - let userCheckedIds = $dropdown.data('user-checked') || []; - let userUncheckedIds = $dropdown.data('user-unchecked') || []; - - if (isMarking) { - userCheckedIds = union(userCheckedIds, [labelId]); - userUncheckedIds = difference(userUncheckedIds, [labelId]); - } else { - userUncheckedIds = union(userUncheckedIds, [labelId]); - userCheckedIds = difference(userCheckedIds, [labelId]); - } - - $dropdown.data('user-checked', userCheckedIds); - $dropdown.data('user-unchecked', userUncheckedIds); - } - // eslint-disable-next-line class-methods-use-this - setOriginalDropdownData($container, $dropdown) { - const labels = []; - $container.find('[name="label_name[]"]').map(function () { - return labels.push(this.value); - }); - $dropdown.data('marked', labels); - } -} |