2016-06-02 11:05:42 +05:30
|
|
|
class @LabelsSelect
|
|
|
|
constructor: ->
|
2016-06-16 23:09:34 +05:30
|
|
|
_this = @
|
|
|
|
|
2016-06-02 11:05:42 +05:30
|
|
|
$('.js-label-select').each (i, dropdown) ->
|
|
|
|
$dropdown = $(dropdown)
|
|
|
|
projectId = $dropdown.data('project-id')
|
|
|
|
labelUrl = $dropdown.data('labels')
|
|
|
|
issueUpdateURL = $dropdown.data('issueUpdate')
|
|
|
|
selectedLabel = $dropdown.data('selected')
|
|
|
|
if selectedLabel? and not $dropdown.hasClass 'js-multiselect'
|
|
|
|
selectedLabel = selectedLabel.split(',')
|
|
|
|
newLabelField = $('#new_label_name')
|
|
|
|
newColorField = $('#new_label_color')
|
|
|
|
showNo = $dropdown.data('show-no')
|
|
|
|
showAny = $dropdown.data('show-any')
|
|
|
|
defaultLabel = $dropdown.data('default-label')
|
|
|
|
abilityName = $dropdown.data('ability-name')
|
|
|
|
$selectbox = $dropdown.closest('.selectbox')
|
|
|
|
$block = $selectbox.closest('.block')
|
|
|
|
$form = $dropdown.closest('form')
|
|
|
|
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span')
|
|
|
|
$value = $block.find('.value')
|
|
|
|
$newLabelError = $('.js-label-error')
|
|
|
|
$colorPreview = $('.js-dropdown-label-color-preview')
|
|
|
|
$newLabelCreateButton = $('.js-new-label-btn')
|
|
|
|
|
|
|
|
$newLabelError.hide()
|
|
|
|
$loading = $block.find('.block-loading').fadeOut()
|
|
|
|
|
|
|
|
issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL?
|
|
|
|
if issueUpdateURL
|
|
|
|
labelHTMLTemplate = _.template(
|
|
|
|
'<% _.each(labels, function(label){ %>
|
2016-08-24 12:49:21 +05:30
|
|
|
<a href="<%- ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%- encodeURIComponent(label.title) %>">
|
|
|
|
<span class="label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;">
|
|
|
|
<%- label.title %>
|
2016-06-02 11:05:42 +05:30
|
|
|
</span>
|
|
|
|
</a>
|
|
|
|
<% }); %>'
|
|
|
|
)
|
2016-06-22 15:30:34 +05:30
|
|
|
labelNoneHTMLTemplate = '<span class="no-value">None</span>'
|
2016-06-02 11:05:42 +05:30
|
|
|
|
|
|
|
if newLabelField.length
|
|
|
|
|
|
|
|
# Suggested colors in the dropdown to chose from pre-chosen colors
|
|
|
|
$('.suggest-colors-dropdown a').on "click", (e) ->
|
|
|
|
e.preventDefault()
|
|
|
|
e.stopPropagation()
|
|
|
|
newColorField
|
|
|
|
.val($(this).data('color'))
|
|
|
|
.trigger('change')
|
|
|
|
$colorPreview
|
|
|
|
.css 'background-color', $(this).data('color')
|
|
|
|
.parent()
|
|
|
|
.addClass 'is-active'
|
|
|
|
|
|
|
|
# Cancel button takes back to first page
|
|
|
|
resetForm = ->
|
|
|
|
newLabelField
|
|
|
|
.val ''
|
|
|
|
.trigger 'change'
|
|
|
|
newColorField
|
|
|
|
.val ''
|
|
|
|
.trigger 'change'
|
|
|
|
$colorPreview
|
|
|
|
.css 'background-color', ''
|
|
|
|
.parent()
|
|
|
|
.removeClass 'is-active'
|
|
|
|
|
|
|
|
$('.dropdown-menu-back').on 'click', ->
|
|
|
|
resetForm()
|
|
|
|
|
|
|
|
$('.js-cancel-label-btn').on 'click', (e) ->
|
|
|
|
e.preventDefault()
|
|
|
|
e.stopPropagation()
|
|
|
|
resetForm()
|
|
|
|
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
|
|
|
|
|
|
|
|
# Listen for change and keyup events on label and color field
|
|
|
|
# This allows us to enable the button when ready
|
|
|
|
enableLabelCreateButton = ->
|
|
|
|
if newLabelField.val() isnt '' and newColorField.val() isnt ''
|
|
|
|
$newLabelError.hide()
|
|
|
|
$newLabelCreateButton.enable()
|
|
|
|
else
|
|
|
|
$newLabelCreateButton.disable()
|
|
|
|
|
|
|
|
saveLabel = ->
|
|
|
|
# Create new label with API
|
|
|
|
Api.newLabel projectId, {
|
|
|
|
name: newLabelField.val()
|
|
|
|
color: newColorField.val()
|
|
|
|
}, (label) ->
|
|
|
|
$newLabelCreateButton.enable()
|
|
|
|
|
|
|
|
if label.message?
|
2016-06-16 23:09:34 +05:30
|
|
|
errors = _.map label.message, (value, key) ->
|
|
|
|
"#{key} #{value[0]}"
|
|
|
|
|
2016-06-02 11:05:42 +05:30
|
|
|
$newLabelError
|
2016-06-16 23:09:34 +05:30
|
|
|
.html errors.join("<br/>")
|
2016-06-02 11:05:42 +05:30
|
|
|
.show()
|
|
|
|
else
|
|
|
|
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
|
|
|
|
|
|
|
|
newLabelField.on 'keyup change', enableLabelCreateButton
|
|
|
|
|
|
|
|
newColorField.on 'keyup change', enableLabelCreateButton
|
|
|
|
|
|
|
|
# Send the API call to create the label
|
|
|
|
$newLabelCreateButton
|
|
|
|
.disable()
|
|
|
|
.on 'click', (e) ->
|
|
|
|
e.preventDefault()
|
|
|
|
e.stopPropagation()
|
|
|
|
saveLabel()
|
|
|
|
|
|
|
|
saveLabelData = ->
|
|
|
|
selected = $dropdown
|
|
|
|
.closest('.selectbox')
|
|
|
|
.find("input[name='#{$dropdown.data('field-name')}']")
|
|
|
|
.map(->
|
|
|
|
@value
|
|
|
|
).get()
|
|
|
|
data = {}
|
|
|
|
data[abilityName] = {}
|
|
|
|
data[abilityName].label_ids = selected
|
|
|
|
if not selected.length
|
|
|
|
data[abilityName].label_ids = ['']
|
|
|
|
$loading.fadeIn()
|
|
|
|
$dropdown.trigger('loading.gl.dropdown')
|
|
|
|
$.ajax(
|
|
|
|
type: 'PUT'
|
|
|
|
url: issueUpdateURL
|
|
|
|
dataType: 'JSON'
|
|
|
|
data: data
|
|
|
|
).done (data) ->
|
|
|
|
$loading.fadeOut()
|
|
|
|
$dropdown.trigger('loaded.gl.dropdown')
|
|
|
|
$selectbox.hide()
|
|
|
|
data.issueURLSplit = issueURLSplit
|
|
|
|
labelCount = 0
|
|
|
|
if data.labels.length
|
|
|
|
template = labelHTMLTemplate(data)
|
|
|
|
labelCount = data.labels.length
|
|
|
|
else
|
2016-06-22 15:30:34 +05:30
|
|
|
template = labelNoneHTMLTemplate
|
2016-06-02 11:05:42 +05:30
|
|
|
$value
|
|
|
|
.removeAttr('style')
|
|
|
|
.html(template)
|
|
|
|
$sidebarCollapsedValue.text(labelCount)
|
|
|
|
|
|
|
|
$('.has-tooltip', $value).tooltip(container: 'body')
|
|
|
|
|
|
|
|
$value
|
|
|
|
.find('a')
|
|
|
|
.each((i) ->
|
|
|
|
setTimeout(=>
|
|
|
|
gl.animate.animate($(@), 'pulse')
|
|
|
|
,200 * i
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
$dropdown.glDropdown(
|
|
|
|
data: (term, callback) ->
|
|
|
|
$.ajax(
|
|
|
|
url: labelUrl
|
|
|
|
).done (data) ->
|
|
|
|
data = _.chain data
|
|
|
|
.groupBy (label) ->
|
|
|
|
label.title
|
|
|
|
.map (label) ->
|
|
|
|
color = _.map label, (dup) ->
|
|
|
|
dup.color
|
|
|
|
|
|
|
|
return {
|
|
|
|
id: label[0].id
|
|
|
|
title: label[0].title
|
|
|
|
color: color
|
|
|
|
duplicate: color.length > 1
|
|
|
|
}
|
|
|
|
.value()
|
|
|
|
|
|
|
|
if $dropdown.hasClass 'js-extra-options'
|
|
|
|
if showNo
|
|
|
|
data.unshift(
|
|
|
|
id: 0
|
|
|
|
title: 'No Label'
|
|
|
|
)
|
|
|
|
|
|
|
|
if showAny
|
|
|
|
data.unshift(
|
|
|
|
isAny: true
|
|
|
|
title: 'Any Label'
|
|
|
|
)
|
|
|
|
|
|
|
|
if data.length > 2
|
|
|
|
data.splice 2, 0, 'divider'
|
|
|
|
|
|
|
|
callback data
|
|
|
|
|
2016-06-16 23:09:34 +05:30
|
|
|
renderRow: (label, instance) ->
|
|
|
|
$li = $('<li>')
|
|
|
|
$a = $('<a href="#">')
|
2016-06-02 11:05:42 +05:30
|
|
|
|
|
|
|
selectedClass = []
|
2016-06-16 23:09:34 +05:30
|
|
|
removesAll = label.id is 0 or not label.id?
|
|
|
|
|
|
|
|
if $dropdown.hasClass('js-filter-bulk-update')
|
|
|
|
indeterminate = instance.indeterminateIds
|
2016-06-22 15:30:34 +05:30
|
|
|
active = instance.activeIds
|
|
|
|
|
2016-06-16 23:09:34 +05:30
|
|
|
if indeterminate.indexOf(label.id) isnt -1
|
|
|
|
selectedClass.push 'is-indeterminate'
|
|
|
|
|
2016-06-22 15:30:34 +05:30
|
|
|
if active.indexOf(label.id) isnt -1
|
|
|
|
# Remove is-indeterminate class if the item will be marked as active
|
|
|
|
i = selectedClass.indexOf 'is-indeterminate'
|
|
|
|
selectedClass.splice i, 1 unless i is -1
|
|
|
|
|
|
|
|
selectedClass.push 'is-active'
|
|
|
|
|
|
|
|
# Add input manually
|
|
|
|
instance.addInput @fieldName, label.id
|
|
|
|
|
2016-06-02 11:05:42 +05:30
|
|
|
if $form.find("input[type='hidden']\
|
|
|
|
[name='#{$dropdown.data('fieldName')}']\
|
|
|
|
[value='#{this.id(label)}']").length
|
|
|
|
selectedClass.push 'is-active'
|
|
|
|
|
|
|
|
if $dropdown.hasClass('js-multiselect') and removesAll
|
|
|
|
selectedClass.push 'dropdown-clear-active'
|
|
|
|
|
|
|
|
if label.duplicate
|
|
|
|
spacing = 100 / label.color.length
|
|
|
|
|
|
|
|
# Reduce the colors to 4
|
|
|
|
label.color = label.color.filter (color, i) ->
|
|
|
|
i < 4
|
|
|
|
|
|
|
|
color = _.map(label.color, (color, i) ->
|
|
|
|
percentFirst = Math.floor(spacing * i)
|
|
|
|
percentSecond = Math.floor(spacing * (i + 1))
|
|
|
|
"#{color} #{percentFirst}%,#{color} #{percentSecond}% "
|
|
|
|
).join(',')
|
|
|
|
color = "linear-gradient(#{color})"
|
|
|
|
else
|
|
|
|
if label.color?
|
|
|
|
color = label.color[0]
|
|
|
|
|
|
|
|
if color
|
|
|
|
colorEl = "<span class='dropdown-label-box' style='background: #{color}'></span>"
|
|
|
|
else
|
|
|
|
colorEl = ''
|
|
|
|
|
2016-06-16 23:09:34 +05:30
|
|
|
# We need to identify which items are actually labels
|
|
|
|
if label.id
|
|
|
|
selectedClass.push('label-item')
|
|
|
|
$a.attr('data-label-id', label.id)
|
|
|
|
|
|
|
|
$a.addClass(selectedClass.join(' '))
|
2016-08-24 12:49:21 +05:30
|
|
|
.html("#{colorEl} #{label.title}")
|
2016-06-16 23:09:34 +05:30
|
|
|
|
|
|
|
# Return generated html
|
|
|
|
$li.html($a).prop('outerHTML')
|
|
|
|
persistWhenHide: $dropdown.data('persistWhenHide')
|
2016-06-02 11:05:42 +05:30
|
|
|
search:
|
|
|
|
fields: ['title']
|
|
|
|
selectable: true
|
2016-06-16 23:09:34 +05:30
|
|
|
filterable: true
|
2016-06-02 11:05:42 +05:30
|
|
|
toggleLabel: (selected, el) ->
|
|
|
|
selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active')
|
|
|
|
|
|
|
|
if selected and selected.title?
|
|
|
|
if selected_labels.length > 1
|
|
|
|
"#{selected.title} +#{selected_labels.length - 1} more"
|
|
|
|
else
|
|
|
|
selected.title
|
|
|
|
else if not selected and selected_labels.length isnt 0
|
|
|
|
if selected_labels.length > 1
|
|
|
|
"#{$(selected_labels[0]).text()} +#{selected_labels.length - 1} more"
|
|
|
|
else if selected_labels.length is 1
|
|
|
|
$(selected_labels).text()
|
|
|
|
else
|
|
|
|
defaultLabel
|
|
|
|
fieldName: $dropdown.data('field-name')
|
|
|
|
id: (label) ->
|
|
|
|
if $dropdown.hasClass("js-filter-submit") and not label.isAny?
|
2016-08-24 12:49:21 +05:30
|
|
|
label.title
|
2016-06-02 11:05:42 +05:30
|
|
|
else
|
|
|
|
label.id
|
|
|
|
|
|
|
|
hidden: ->
|
|
|
|
page = $('body').data 'page'
|
|
|
|
isIssueIndex = page is 'projects:issues:index'
|
|
|
|
isMRIndex = page is 'projects:merge_requests:index'
|
|
|
|
|
|
|
|
$selectbox.hide()
|
|
|
|
# display:block overrides the hide-collapse rule
|
|
|
|
$value.removeAttr('style')
|
|
|
|
if $dropdown.hasClass 'js-multiselect'
|
|
|
|
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
|
|
|
|
selectedLabels = $dropdown
|
|
|
|
.closest('form')
|
|
|
|
.find("input:hidden[name='#{$dropdown.data('fieldName')}']")
|
|
|
|
Issuable.filterResults $dropdown.closest('form')
|
|
|
|
else if $dropdown.hasClass('js-filter-submit')
|
|
|
|
$dropdown.closest('form').submit()
|
|
|
|
else
|
2016-06-16 23:09:34 +05:30
|
|
|
if not $dropdown.hasClass 'js-filter-bulk-update'
|
|
|
|
saveLabelData()
|
|
|
|
|
|
|
|
if $dropdown.hasClass('js-filter-bulk-update')
|
|
|
|
# If we are persisting state we need the classes
|
|
|
|
if not @options.persistWhenHide
|
|
|
|
$dropdown.parent().find('.is-active, .is-indeterminate').removeClass()
|
2016-06-02 11:05:42 +05:30
|
|
|
|
|
|
|
multiSelect: $dropdown.hasClass 'js-multiselect'
|
|
|
|
clicked: (label) ->
|
2016-08-24 12:49:21 +05:30
|
|
|
_this.enableBulkLabelDropdown()
|
|
|
|
|
2016-06-16 23:09:34 +05:30
|
|
|
if $dropdown.hasClass('js-filter-bulk-update')
|
|
|
|
return
|
|
|
|
|
2016-06-02 11:05:42 +05:30
|
|
|
page = $('body').data 'page'
|
|
|
|
isIssueIndex = page is 'projects:issues:index'
|
|
|
|
isMRIndex = page is 'projects:merge_requests:index'
|
|
|
|
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
|
|
|
|
if not $dropdown.hasClass 'js-multiselect'
|
|
|
|
selectedLabel = label.title
|
|
|
|
Issuable.filterResults $dropdown.closest('form')
|
|
|
|
else if $dropdown.hasClass 'js-filter-submit'
|
|
|
|
$dropdown.closest('form').submit()
|
|
|
|
else
|
|
|
|
if $dropdown.hasClass 'js-multiselect'
|
|
|
|
return
|
|
|
|
else
|
|
|
|
saveLabelData()
|
2016-06-16 23:09:34 +05:30
|
|
|
|
|
|
|
setIndeterminateIds: ->
|
|
|
|
if @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
|
|
|
|
@indeterminateIds = _this.getIndeterminateIds()
|
2016-06-22 15:30:34 +05:30
|
|
|
|
|
|
|
setActiveIds: ->
|
|
|
|
if @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
|
|
|
|
@activeIds = _this.getActiveIds()
|
2016-06-02 11:05:42 +05:30
|
|
|
)
|
2016-06-16 23:09:34 +05:30
|
|
|
|
|
|
|
@bindEvents()
|
|
|
|
|
|
|
|
bindEvents: ->
|
|
|
|
$('body').on 'change', '.selected_issue', @onSelectCheckboxIssue
|
|
|
|
|
|
|
|
onSelectCheckboxIssue: ->
|
|
|
|
return if $('.selected_issue:checked').length
|
|
|
|
|
|
|
|
# Remove inputs
|
|
|
|
$('.issues_bulk_update .labels-filter input[type="hidden"]').remove()
|
|
|
|
|
|
|
|
# Also restore button text
|
|
|
|
$('.issues_bulk_update .labels-filter .dropdown-toggle-text').text('Label')
|
|
|
|
|
|
|
|
getIndeterminateIds: ->
|
|
|
|
label_ids = []
|
|
|
|
|
|
|
|
$('.selected_issue:checked').each (i, el) ->
|
|
|
|
issue_id = $(el).data('id')
|
|
|
|
label_ids.push $("#issue_#{issue_id}").data('labels')
|
|
|
|
|
|
|
|
_.flatten(label_ids)
|
2016-06-22 15:30:34 +05:30
|
|
|
|
|
|
|
getActiveIds: ->
|
|
|
|
label_ids = []
|
|
|
|
|
|
|
|
$('.selected_issue:checked').each (i, el) ->
|
|
|
|
issue_id = $(el).data('id')
|
|
|
|
label_ids.push $("#issue_#{issue_id}").data('labels')
|
|
|
|
|
|
|
|
_.intersection.apply _, label_ids
|
2016-08-24 12:49:21 +05:30
|
|
|
|
|
|
|
enableBulkLabelDropdown: ->
|
|
|
|
if $('.selected_issue:checked').length
|
|
|
|
issuableBulkActions = $('.bulk-update').data('bulkActions')
|
|
|
|
issuableBulkActions.willUpdateLabels = true
|