/* eslint-disable one-var, no-self-compare, consistent-return, no-param-reassign, no-shadow */ /* global Issuable */ import $ from 'jquery'; import { template, escape } from 'lodash'; import Api from '~/api'; import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown'; import { __, sprintf } from '~/locale'; import { sortMilestonesByDueDate } from '~/milestones/milestone_utils'; import axios from './lib/utils/axios_utils'; import { timeFor, parsePikadayDate, dateInWords } from './lib/utils/datetime_utility'; export default class MilestoneSelect { constructor(currentProject, els, options = {}) { if (currentProject !== null) { this.currentProject = typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject; } MilestoneSelect.init(els, options); } static init(els, options) { let $els = $(els); if (!els) { $els = $('.js-milestone-select'); } $els.each((i, dropdown) => { let milestoneLinkNoneTemplate, milestoneLinkTemplate, milestoneExpiredLinkTemplate, selectedMilestone, selectedMilestoneDefault; const $dropdown = $(dropdown); const issueUpdateURL = $dropdown.data('issueUpdate'); const showNo = $dropdown.data('showNo'); const showAny = $dropdown.data('showAny'); const showMenuAbove = $dropdown.data('showMenuAbove'); const showUpcoming = $dropdown.data('showUpcoming'); const showStarted = $dropdown.data('showStarted'); const useId = $dropdown.data('useId'); const defaultLabel = $dropdown.data('defaultLabel'); const defaultNo = $dropdown.data('defaultNo'); const abilityName = $dropdown.data('abilityName'); const $selectBox = $dropdown.closest('.selectbox'); const $block = $selectBox.closest('.block'); const $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon'); const $value = $block.find('.value'); const $loading = $block.find('.block-loading').addClass('gl-display-none'); selectedMilestoneDefault = showAny ? '' : null; selectedMilestoneDefault = showNo && defaultNo ? __('No milestone') : selectedMilestoneDefault; selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault; if (issueUpdateURL) { milestoneLinkTemplate = template( '<%- title %>', ); milestoneExpiredLinkTemplate = template( '<%- title %> (Past due)', ); milestoneLinkNoneTemplate = `${__('None')}`; } return initDeprecatedJQueryDropdown($dropdown, { showMenuAbove, data: (term, callback) => { let contextId = parseInt($dropdown.get(0).dataset.projectId, 10); let getMilestones = Api.projectMilestones.bind(Api); const reqParams = { state: 'active', include_parent_milestones: true }; if (term) { reqParams.search = term.trim(); } if (!contextId) { contextId = $dropdown.get(0).dataset.groupId; delete reqParams.include_parent_milestones; getMilestones = Api.groupMilestones.bind(Api); } // We don't use $.data() as it caches initial value and never updates! return getMilestones(contextId, reqParams) .then(({ data }) => data .map((m) => ({ ...m, // Public API includes `title` instead of `name`. name: m.title, })) .sort(sortMilestonesByDueDate), ) .then((data) => { const extraOptions = []; if (showAny) { extraOptions.push({ id: null, name: null, title: __('Any milestone'), }); } if (showNo && term.trim() === '') { extraOptions.push({ id: -1, name: __('No milestone'), title: __('No milestone'), }); } if (showUpcoming) { extraOptions.push({ id: -2, name: '#upcoming', title: __('Upcoming'), }); } if (showStarted) { extraOptions.push({ id: -3, name: '#started', title: __('Started'), }); } if (extraOptions.length) { extraOptions.push({ type: 'divider' }); } callback(extraOptions.concat(data)); if (showMenuAbove) { $dropdown.data('deprecatedJQueryDropdown').positionMenuAbove(); } $(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active'); }); }, renderRow: (milestone) => { const milestoneName = milestone.title || milestone.name; let milestoneDisplayName = escape(milestoneName); if (milestone.expired) { milestoneDisplayName = sprintf(__('%{milestone} (expired)'), { milestone: milestoneDisplayName, }); } return `
  • ${milestoneDisplayName}
  • `; }, filterable: true, filterRemote: true, search: { fields: ['title'], }, selectable: true, toggleLabel: (selected, el) => { if (selected && 'id' in selected && $(el).hasClass('is-active')) { return selected.title; } return defaultLabel; }, defaultLabel, fieldName: $dropdown.data('fieldName'), text: (milestone) => escape(milestone.title), id: (milestone) => { if (milestone !== undefined) { if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) { return milestone.name; } return milestone.id; } }, hidden: () => { $selectBox.hide(); // display:block overrides the hide-collapse rule return $value.css('display', ''); }, opened: (e) => { const $el = $(e.currentTarget); if (options.handleClick) { selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault; } $('a.is-active', $el).removeClass('is-active'); $(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active'); }, vue: false, clicked: (clickEvent) => { const { e } = clickEvent; let selected = clickEvent.selectedObj; if (!selected) return; if (options.handleClick) { e.preventDefault(); options.handleClick(selected); return; } const page = $('body').attr('data-page'); const isIssueIndex = page === 'projects:issues:index'; const isMRIndex = page === page && page === 'projects:merge_requests:index'; const isSelecting = selected.name !== selectedMilestone; selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault; if ( $dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown') ) { e.preventDefault(); return; } 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(); } selected = $selectBox.find('input[type="hidden"]').val(); const data = {}; data[abilityName] = {}; data[abilityName].milestone_id = selected != null ? selected : null; $loading.removeClass('gl-display-none'); $dropdown.trigger('loading.gl.dropdown'); return axios .put(issueUpdateURL, data) .then(({ data }) => { $dropdown.trigger('loaded.gl.dropdown'); $loading.addClass('gl-display-none'); $selectBox.hide(); $value.css('display', ''); if (data.milestone != null) { data.milestone.remaining = timeFor(data.milestone.due_date); data.milestone.name = data.milestone.title; $value.html( data.milestone.expired ? milestoneExpiredLinkTemplate({ ...data.milestone, remaining: sprintf(__('%{due_date} (Past due)'), { due_date: dateInWords(parsePikadayDate(data.milestone.due_date)), }), }) : milestoneLinkTemplate(data.milestone), ); return $sidebarCollapsedValue .attr( 'data-original-title', `${data.milestone.name}
    ${data.milestone.remaining}`, ) .find('span') .text(data.milestone.title); } $value.html(milestoneLinkNoneTemplate); return $sidebarCollapsedValue .attr('data-original-title', __('Milestone')) .find('span') .text(__('None')); }) .catch(() => { $loading.addClass('gl-display-none'); }); }, }); }); } } window.MilestoneSelect = MilestoneSelect;