Merge pull request #1036 from taigaio/us/4302/improve_tagging_system_v2
US #4302: Improve tagging system. Use generic colors for porject tagsstable
commit
36e4c2e650
|
@ -9,7 +9,7 @@ app/coffee/modules/locales/locale*.coffee
|
|||
*.swp
|
||||
*.swo
|
||||
.#*
|
||||
tags
|
||||
/tags
|
||||
tmp/
|
||||
app/config/main.coffee
|
||||
scss-lint.log
|
||||
|
|
|
@ -62,6 +62,7 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
@scope.project = {}
|
||||
|
||||
promise = @.loadInitialData()
|
||||
@scope.projectTags = []
|
||||
|
||||
promise.then =>
|
||||
sectionName = @translate.instant( @scope.sectionName)
|
||||
|
@ -96,6 +97,11 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
@scope.issueTypesList = _.sortBy(project.issue_types, "order")
|
||||
@scope.issueStatusList = _.sortBy(project.issue_statuses, "order")
|
||||
@scope.$emit('project:loaded', project)
|
||||
|
||||
|
||||
@scope.projectTags = _.map @scope.project.tags, (it) =>
|
||||
return [it, @scope.project.tags_colors[it]]
|
||||
|
||||
return project
|
||||
|
||||
loadInitialData: ->
|
||||
|
@ -107,6 +113,21 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
openDeleteLightbox: ->
|
||||
@rootscope.$broadcast("deletelightbox:new", @scope.project)
|
||||
|
||||
addTag: (name, color) ->
|
||||
tags = _.clone(@scope.project.tags)
|
||||
|
||||
tags.push(name)
|
||||
|
||||
@scope.projectTags.push([name, null])
|
||||
@scope.project.tags = tags
|
||||
|
||||
deleteTag: (tag) ->
|
||||
tags = _.clone(@scope.project.tags)
|
||||
_.pull(tags, tag[0])
|
||||
_.remove @scope.projectTags, (it) => it[0] == tag[0]
|
||||
|
||||
@scope.project.tags = tags
|
||||
|
||||
module.controller("ProjectProfileController", ProjectProfileController)
|
||||
|
||||
|
||||
|
|
|
@ -331,6 +331,10 @@ ColorSelectionDirective = () ->
|
|||
## Color selection Link
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
$scope.allowEmpty = false
|
||||
if $attrs.tgAllowEmpty
|
||||
$scope.allowEmpty = true
|
||||
|
||||
$ctrl = $el.controller()
|
||||
|
||||
$scope.$watch $attrs.ngModel, (element) ->
|
||||
|
@ -696,59 +700,268 @@ module.directive("tgProjectCustomAttributes", ["$log", "$tgConfirm", "animationF
|
|||
## Tags Controller
|
||||
#############################################################################
|
||||
|
||||
class ProjectTagsController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||
class ProjectTagsController extends taiga.Controller
|
||||
@.$inject = [
|
||||
"$scope",
|
||||
"$rootScope",
|
||||
"$tgRepo",
|
||||
"tgAppMetaService",
|
||||
"$translate"
|
||||
"$tgConfirm",
|
||||
"$tgResources",
|
||||
"$tgModel",
|
||||
]
|
||||
|
||||
constructor: (@scope, @rootscope, @repo, @appMetaService, @translate) ->
|
||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @model) ->
|
||||
@.loading = true
|
||||
@rootscope.$on "project:loaded", =>
|
||||
sectionName = @translate.instant(@scope.sectionName)
|
||||
title = @translate.instant("ADMIN.CUSTOM_ATTRIBUTES.PAGE_TITLE", {
|
||||
"sectionName": sectionName,
|
||||
"projectName": @scope.project.name
|
||||
})
|
||||
description = @scope.project.description
|
||||
@appMetaService.setAll(title, description)
|
||||
@rootscope.$on("project:loaded", @.loadTags)
|
||||
|
||||
loadTags: =>
|
||||
return @rs.projects.tagsColors(@scope.projectId).then (tags) =>
|
||||
@scope.projectTagsAll = _.map(tags.getAttrs(), (color, name) => @model.make_model('tag', {name: name, color: color}))
|
||||
@.filterAndSortTags()
|
||||
@.loading = false
|
||||
@.tagNames = Object.keys(@scope.project.tags_colors).sort()
|
||||
@scope.projectTags = _.map(@.tagNames, (tagName) => {name: tagName, color: @scope.project.tags_colors[tagName]})
|
||||
|
||||
updateTag: (tag) ->
|
||||
tags_colors = angular.copy(@scope.project.tags_colors)
|
||||
tags_colors[tag.name] = tag.color
|
||||
@scope.project.tags_colors = tags_colors
|
||||
return @repo.save(@scope.project)
|
||||
filterAndSortTags: =>
|
||||
@scope.projectTags = _.filter(
|
||||
_.sortBy(@scope.projectTagsAll, "name"),
|
||||
(tag) => tag.name.indexOf(@scope.tagsFilter.name) != -1
|
||||
)
|
||||
|
||||
deleteTag: (tag) =>
|
||||
return @rs.projects.deleteTag(@scope.projectId, tag)
|
||||
|
||||
createTag: (tag, color) =>
|
||||
return @rs.projects.createTag(@scope.projectId, tag, color)
|
||||
|
||||
editTag: (from_tag, to_tag, color) =>
|
||||
if from_tag == to_tag
|
||||
to_tag = null
|
||||
return @rs.projects.editTag(@scope.projectId, from_tag, to_tag, color)
|
||||
|
||||
startMixingTags: (tag) =>
|
||||
@scope.mixingTags.toTag = tag.name
|
||||
|
||||
toggleMixingFromTags: (tag) =>
|
||||
if tag.name != @scope.mixingTags.toTag
|
||||
index = @scope.mixingTags.fromTags.indexOf(tag.name)
|
||||
if index == -1
|
||||
@scope.mixingTags.fromTags.push(tag.name)
|
||||
else
|
||||
@scope.mixingTags.fromTags.splice(index, 1)
|
||||
|
||||
confirmMixingTags: () =>
|
||||
toTag = @scope.mixingTags.toTag
|
||||
fromTags = @scope.mixingTags.fromTags
|
||||
@rs.projects.mixTags(@scope.projectId, toTag, fromTags).then =>
|
||||
@.cancelMixingTags()
|
||||
@.loadTags()
|
||||
|
||||
cancelMixingTags: () =>
|
||||
@scope.mixingTags.toTag = null
|
||||
@scope.mixingTags.fromTags = []
|
||||
|
||||
mixingClass: (tag) =>
|
||||
if @scope.mixingTags.toTag != null
|
||||
if tag.name == @scope.mixingTags.toTag
|
||||
return "mixing-tags-to"
|
||||
else if @scope.mixingTags.fromTags.indexOf(tag.name) != -1
|
||||
return "mixing-tags-from"
|
||||
|
||||
module.controller("ProjectTagsController", ProjectTagsController)
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Tags Directive
|
||||
## Tags directive
|
||||
#############################################################################
|
||||
|
||||
ProjectTagDirective = () ->
|
||||
ProjectTagsDirective = ($log, $repo, $confirm, $location, animationFrame, $translate, $rootscope) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$el.color = $scope.tag.color
|
||||
$window = $(window)
|
||||
$ctrl = $el.controller()
|
||||
valueType = $attrs.type
|
||||
objName = $attrs.objname
|
||||
|
||||
initializeNewValue = ->
|
||||
$scope.newValue = {
|
||||
"name": ""
|
||||
"color": ""
|
||||
}
|
||||
|
||||
initializeTagsFilter = ->
|
||||
$scope.tagsFilter = {
|
||||
"name": ""
|
||||
}
|
||||
|
||||
initializeMixingTags = ->
|
||||
$scope.mixingTags = {
|
||||
"toTag": null,
|
||||
"fromTags": []
|
||||
}
|
||||
|
||||
initializeTextTranslations = ->
|
||||
$scope.addNewElementText = $translate.instant("ADMIN.PROJECT_VALUES_TAGS.ACTION_ADD")
|
||||
|
||||
initializeNewValue()
|
||||
initializeTagsFilter()
|
||||
initializeMixingTags()
|
||||
initializeTextTranslations()
|
||||
|
||||
$rootscope.$on "$translateChangeEnd", ->
|
||||
$scope.$evalAsync(initializeTextTranslations)
|
||||
|
||||
goToBottomList = (focus = false) =>
|
||||
table = $el.find(".table-main")
|
||||
|
||||
$(document.body).scrollTop(table.offset().top + table.height())
|
||||
|
||||
if focus
|
||||
$el.find(".new-value input:visible").first().focus()
|
||||
|
||||
saveValue = (target) ->
|
||||
formEl = target.parents("form")
|
||||
form = formEl.checksley()
|
||||
return if not form.validate()
|
||||
|
||||
tag = formEl.scope().tag
|
||||
originalTag = tag.clone()
|
||||
originalTag.revert()
|
||||
|
||||
promise = $ctrl.editTag(originalTag.name, tag.name, tag.color)
|
||||
promise.then =>
|
||||
row = target.parents(".row.table-main")
|
||||
row.addClass("hidden")
|
||||
row.siblings(".visualization").removeClass('hidden')
|
||||
$ctrl.loadTags()
|
||||
|
||||
promise.then null, (data) ->
|
||||
form.setErrors(data)
|
||||
|
||||
saveNewValue = (target) ->
|
||||
formEl = target.parents("form")
|
||||
formEl = target
|
||||
form = formEl.checksley()
|
||||
return if not form.validate()
|
||||
|
||||
promise = $ctrl.createTag($scope.newValue.name, $scope.newValue.color)
|
||||
promise.then (data) =>
|
||||
target.addClass("hidden")
|
||||
$ctrl.loadTags()
|
||||
initializeNewValue()
|
||||
|
||||
promise.then null, (data) ->
|
||||
form.setErrors(data)
|
||||
|
||||
cancel = (target) ->
|
||||
row = target.parents(".row.table-main")
|
||||
formEl = target.parents("form")
|
||||
tag = formEl.scope().tag
|
||||
|
||||
$scope.$apply ->
|
||||
row.addClass("hidden")
|
||||
tag.revert()
|
||||
row.siblings(".visualization").removeClass('hidden')
|
||||
|
||||
$scope.$watch "tagsFilter.name", (tagsFilter) ->
|
||||
$ctrl.filterAndSortTags()
|
||||
|
||||
$window.on "keyup", (event) ->
|
||||
if event.keyCode == 27
|
||||
$scope.$apply ->
|
||||
initializeMixingTags()
|
||||
|
||||
$el.on "click", ".show-add-new", (event) ->
|
||||
event.preventDefault()
|
||||
$el.find(".new-value").removeClass('hidden')
|
||||
|
||||
$el.on "click", ".add-new", debounce 2000, (event) ->
|
||||
event.preventDefault()
|
||||
target = $el.find(".new-value")
|
||||
saveNewValue(target)
|
||||
|
||||
$el.on "click", ".delete-new", (event) ->
|
||||
event.preventDefault()
|
||||
$el.find(".new-value").addClass("hidden")
|
||||
initializeNewValue()
|
||||
|
||||
$el.on "click", ".mix-tags", (event) ->
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
$scope.$apply ->
|
||||
$ctrl.startMixingTags(target.parents('form').scope().tag)
|
||||
|
||||
$el.on "click", ".mixing-row", (event) ->
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
$scope.$apply ->
|
||||
$ctrl.toggleMixingFromTags(target.parents('form').scope().tag)
|
||||
|
||||
$el.on "click", ".mixing-confirm", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
$scope.$apply ->
|
||||
$ctrl.confirmMixingTags()
|
||||
|
||||
$el.on "click", ".mixing-cancel", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
$scope.$apply ->
|
||||
$ctrl.cancelMixingTags()
|
||||
|
||||
$el.on "click", ".edit-value", (event) ->
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
|
||||
row = target.parents(".row.table-main")
|
||||
row.addClass("hidden")
|
||||
|
||||
editionRow = row.siblings(".edition")
|
||||
editionRow.removeClass('hidden')
|
||||
editionRow.find('input:visible').first().focus().select()
|
||||
|
||||
$el.on "keyup", ".new-value input", (event) ->
|
||||
if event.keyCode == 13
|
||||
target = $el.find(".new-value")
|
||||
saveNewValue(target)
|
||||
else if event.keyCode == 27
|
||||
$el.find(".new-value").addClass("hidden")
|
||||
initializeNewValue()
|
||||
|
||||
$el.on "keyup", ".status-name input", (event) ->
|
||||
target = angular.element(event.currentTarget)
|
||||
if event.keyCode == 13
|
||||
saveValue(target)
|
||||
else if event.keyCode == 27
|
||||
cancel(target)
|
||||
|
||||
$el.on "click", ".save", (event) ->
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
saveValue(target)
|
||||
|
||||
$el.on "click", ".cancel", (event) ->
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
cancel(target)
|
||||
|
||||
$el.on "click", ".delete-tag", (event) ->
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
formEl = target.parents("form")
|
||||
tag = formEl.scope().tag
|
||||
|
||||
title = $translate.instant("ADMIN.COMMON.TITLE_ACTION_DELETE_TAG")
|
||||
|
||||
$confirm.askOnDelete(title, tag.name).then (response) ->
|
||||
onSucces = ->
|
||||
$ctrl.loadTags().finally ->
|
||||
response.finish()
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
$ctrl.deleteTag(tag.name).then(onSucces, onError)
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
$window.off()
|
||||
|
||||
$scope.$watch "tag.color", (newColor) =>
|
||||
if $el.color != newColor
|
||||
promise = $ctrl.updateTag($scope.tag)
|
||||
promise.then null, (data) ->
|
||||
form.setErrors(data)
|
||||
return {link:link}
|
||||
|
||||
$el.color = newColor
|
||||
|
||||
return {link: link}
|
||||
|
||||
module.directive("tgProjectTag", [ProjectTagDirective])
|
||||
module.directive("tgProjectTags", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", "animationFrame", "$translate", "$rootScope", ProjectTagsDirective])
|
||||
|
|
|
@ -28,6 +28,7 @@ bindOnce = @.taiga.bindOnce
|
|||
timeout = @.taiga.timeout
|
||||
debounce = @.taiga.debounce
|
||||
sizeFormat = @.taiga.sizeFormat
|
||||
trim = @.taiga.trim
|
||||
|
||||
#############################################################################
|
||||
## Common Lightbox Services
|
||||
|
@ -295,6 +296,42 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService,
|
|||
if attachment.get("id")
|
||||
attachmentsToDelete = attachmentsToDelete.push(attachment)
|
||||
|
||||
$scope.addTag = (tag, color) ->
|
||||
value = trim(tag.toLowerCase())
|
||||
|
||||
tags = $scope.project.tags
|
||||
projectTags = $scope.project.tags_colors
|
||||
|
||||
tags = [] if not tags?
|
||||
projectTags = {} if not projectTags?
|
||||
|
||||
if value not in tags
|
||||
tags.push(value)
|
||||
|
||||
projectTags[tag] = color || null
|
||||
|
||||
$scope.project.tags = tags
|
||||
|
||||
itemtags = _.clone($scope.us.tags)
|
||||
|
||||
inserted = _.find itemtags, (it) -> it[0] == value
|
||||
|
||||
if !inserted
|
||||
itemtags.push([tag , color])
|
||||
$scope.us.tags = itemtags
|
||||
|
||||
$scope.deleteTag = (tag) ->
|
||||
value = trim(tag[0].toLowerCase())
|
||||
|
||||
tags = $scope.project.tags
|
||||
itemtags = _.clone($scope.us.tags)
|
||||
|
||||
_.remove itemtags, (tag) -> tag[0] == value
|
||||
|
||||
$scope.us.tags = itemtags
|
||||
|
||||
_.pull($scope.us.tags, value)
|
||||
|
||||
$scope.$on "usform:new", (ctx, projectId, status, statusList) ->
|
||||
form.reset() if form
|
||||
$scope.isNew = true
|
||||
|
|
|
@ -26,6 +26,7 @@ taiga = @.taiga
|
|||
trim = @.taiga.trim
|
||||
bindOnce = @.taiga.bindOnce
|
||||
|
||||
|
||||
module = angular.module("taigaCommon")
|
||||
|
||||
# Directive that parses/format tags inputfield.
|
||||
|
@ -61,28 +62,38 @@ ColorizeTagsDirective = ->
|
|||
templates = {
|
||||
backlog: _.template("""
|
||||
<% _.each(tags, function(tag) { %>
|
||||
<span class="tag" style="border-left: 5px solid <%- tag.color %>"><%- tag.name %></span>
|
||||
<span class="tag"
|
||||
<% if (tag[1] === null || tag[1] == undefined) { %>
|
||||
style="border-left: 5px solid <%- tag[1] %>"
|
||||
<% } %>
|
||||
title="<%- tag[0] %>"><%- tag[0] %></span>
|
||||
<% }) %>
|
||||
""")
|
||||
kanban: _.template("""
|
||||
<% _.each(tags, function(tag) { %>
|
||||
<a class="kanban-tag" href="" style="border-color: <%- tag.color %>" title="<%- tag.name %>" />
|
||||
<a class="kanban-tag"
|
||||
href=""
|
||||
<% if (tag[1] === null || tag[1] == undefined) { %>
|
||||
style="border-color: <%- tag[1] %>"
|
||||
<% } %>
|
||||
title="<%- tag[0] %>" />
|
||||
<% }) %>
|
||||
""")
|
||||
taskboard: _.template("""
|
||||
<% _.each(tags, function(tag) { %>
|
||||
<a class="taskboard-tag" href="" style="border-color: <%- tag.color %>" title="<%- tag.name %>" />
|
||||
<a class="taskboard-tag"
|
||||
href=""
|
||||
<% if (tag[1] === null || tag[1] == undefined) { %>
|
||||
style="border-color: <%- tag[1] %>"
|
||||
<% } %>
|
||||
title="<%- tag[0] %>" />
|
||||
<% }) %>
|
||||
""")
|
||||
}
|
||||
|
||||
link = ($scope, $el, $attrs, $ctrl) ->
|
||||
render = (srcTags) ->
|
||||
render = (tags) ->
|
||||
template = templates[$attrs.tgColorizeTagsType]
|
||||
srcTags.sort()
|
||||
tags = _.map srcTags, (tag) ->
|
||||
color = $scope.project.tags_colors[tag]
|
||||
return {name: tag, color: color}
|
||||
|
||||
html = template({tags: tags})
|
||||
$el.html(html)
|
||||
|
@ -111,15 +122,18 @@ LbTagLineDirective = ($rs, $template, $compile) ->
|
|||
autocomplete = null
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
withoutColors = _.has($attrs, "withoutColors")
|
||||
|
||||
## Render
|
||||
renderTags = (tags, tagsColors = []) ->
|
||||
ctx = {
|
||||
tags: _.map(tags, (t) -> {name: t, color: tagsColors[t]})
|
||||
}
|
||||
color = if not withoutColors then tagsColors[t] else null
|
||||
|
||||
_.map ctx.tags, (tag) =>
|
||||
if tag.color
|
||||
tag.style = "border-left: 5px solid #{tag.color}"
|
||||
ctx = {
|
||||
tags: _.map(tags, (t) -> {
|
||||
name: t,
|
||||
style: if color then "border-left: 5px solid #{color}" else ""
|
||||
})
|
||||
}
|
||||
|
||||
html = $compile(templateTags(ctx))($scope)
|
||||
$el.find(".tags-container").html(html)
|
||||
|
@ -196,7 +210,7 @@ LbTagLineDirective = ($rs, $template, $compile) ->
|
|||
|
||||
autocomplete = new Awesomplete(input[0], {
|
||||
list: _.keys(project.tags_colors)
|
||||
});
|
||||
})
|
||||
|
||||
input.on "awesomplete-selectcomplete", () ->
|
||||
addValue(input.val())
|
||||
|
@ -216,204 +230,3 @@ LbTagLineDirective = ($rs, $template, $compile) ->
|
|||
}
|
||||
|
||||
module.directive("tgLbTagLine", ["$tgResources", "$tgTemplate", "$compile", LbTagLineDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## TagLine Directive (for detail pages)
|
||||
#############################################################################
|
||||
|
||||
TagLineDirective = ($rootScope, $repo, $rs, $confirm, $modelTransform, $template, $compile) ->
|
||||
ENTER_KEY = 13
|
||||
ESC_KEY = 27
|
||||
COMMA_KEY = 188
|
||||
|
||||
templateTags = $template.get("common/tag/tags-line-tags.html", true)
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
autocomplete = null
|
||||
loading = false
|
||||
deleteTagLoading = null
|
||||
|
||||
isEditable = ->
|
||||
if $attrs.requiredPerm?
|
||||
return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1
|
||||
|
||||
return true
|
||||
|
||||
## Render
|
||||
renderTags = (tags, tagsColors) ->
|
||||
ctx = {
|
||||
tags: _.map(tags, (t) -> {name: t, color: tagsColors[t]})
|
||||
isEditable: isEditable()
|
||||
loading: loading
|
||||
deleteTagLoading: deleteTagLoading
|
||||
}
|
||||
|
||||
html = $compile(templateTags(ctx))($scope)
|
||||
$el.find("div.tags-container").html(html)
|
||||
|
||||
renderInReadModeOnly = ->
|
||||
$el.find(".add-tag").remove()
|
||||
$el.find("input").remove()
|
||||
$el.find(".save").remove()
|
||||
|
||||
showAddTagButton = -> $el.find(".add-tag").removeClass("hidden")
|
||||
hideAddTagButton = -> $el.find(".add-tag").addClass("hidden")
|
||||
|
||||
showAddTagButtonText = -> $el.find(".add-tag-text").removeClass("hidden")
|
||||
hideAddTagButtonText = -> $el.find(".add-tag-text").addClass("hidden")
|
||||
|
||||
showSaveButton = -> $el.find(".save").removeClass("hidden")
|
||||
hideSaveButton = -> $el.find(".save").addClass("hidden")
|
||||
|
||||
showInput = -> $el.find("input").removeClass("hidden").focus()
|
||||
hideInput = -> $el.find("input").addClass("hidden").blur()
|
||||
resetInput = ->
|
||||
$el.find("input").val("")
|
||||
|
||||
autocomplete.close()
|
||||
|
||||
## Aux methods
|
||||
addValue = (value) ->
|
||||
loading = true
|
||||
value = trim(value.toLowerCase())
|
||||
return if value.length == 0
|
||||
renderTags($model.$modelValue.tags, $scope.project?.tags_colors)
|
||||
|
||||
transform = $modelTransform.save (item) ->
|
||||
if not item.tags
|
||||
item.tags = []
|
||||
|
||||
tags = _.clone(item.tags)
|
||||
|
||||
tags.push(value) if value not in tags
|
||||
|
||||
item.tags = tags
|
||||
|
||||
return item
|
||||
|
||||
onSuccess = ->
|
||||
$rootScope.$broadcast("object:updated")
|
||||
loading = false
|
||||
renderTags($model.$modelValue.tags, $scope.project?.tags_colors)
|
||||
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
|
||||
hideSaveButton()
|
||||
|
||||
return transform.then(onSuccess, onError)
|
||||
|
||||
deleteValue = (value) ->
|
||||
value = trim(value.toLowerCase())
|
||||
return if value.length == 0
|
||||
deleteTagLoading = value
|
||||
renderTags($model.$modelValue.tags, $scope.project?.tags_colors)
|
||||
|
||||
transform = $modelTransform.save (item) ->
|
||||
tags = _.clone(item.tags, false)
|
||||
item.tags = _.pull(tags, value)
|
||||
|
||||
return item
|
||||
|
||||
onSuccess = ->
|
||||
$rootScope.$broadcast("object:updated")
|
||||
renderTags($model.$modelValue.tags, $scope.project?.tags_colors)
|
||||
deleteTagLoading = null
|
||||
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
deleteTagLoading = null
|
||||
|
||||
return transform.then(onSuccess, onError)
|
||||
|
||||
saveInputTag = () ->
|
||||
value = $el.find("input").val()
|
||||
|
||||
addValue(value)
|
||||
resetInput()
|
||||
|
||||
## Events
|
||||
$el.on "keypress", "input", (event) ->
|
||||
target = angular.element(event.currentTarget)
|
||||
|
||||
if event.keyCode == ENTER_KEY
|
||||
saveInputTag()
|
||||
else if String.fromCharCode(event.keyCode) == ','
|
||||
event.preventDefault()
|
||||
saveInputTag()
|
||||
else
|
||||
if target.val().length
|
||||
showSaveButton()
|
||||
else
|
||||
hideSaveButton()
|
||||
|
||||
$el.on "keyup", "input", (event) ->
|
||||
if event.keyCode == ESC_KEY
|
||||
resetInput()
|
||||
hideInput()
|
||||
hideSaveButton()
|
||||
showAddTagButton()
|
||||
|
||||
$el.on "click", ".save", (event) ->
|
||||
event.preventDefault()
|
||||
saveInputTag()
|
||||
|
||||
$el.on "click", ".add-tag", (event) ->
|
||||
event.preventDefault()
|
||||
hideAddTagButton()
|
||||
showInput()
|
||||
|
||||
$el.on "click", ".remove-tag", (event) ->
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
|
||||
value = target.siblings(".tag-name").text()
|
||||
|
||||
deleteValue(value)
|
||||
$scope.$digest()
|
||||
|
||||
bindOnce $scope, "project.tags_colors", (tags_colors) ->
|
||||
if not isEditable()
|
||||
renderInReadModeOnly()
|
||||
return
|
||||
|
||||
showAddTagButton()
|
||||
|
||||
input = $el.find("input")
|
||||
|
||||
autocomplete = new Awesomplete(input[0], {
|
||||
list: _.keys(tags_colors)
|
||||
});
|
||||
|
||||
input.on "awesomplete-selectcomplete", () ->
|
||||
addValue(input.val())
|
||||
input.val("")
|
||||
|
||||
|
||||
$scope.$watchCollection () ->
|
||||
return $model.$modelValue?.tags
|
||||
, () ->
|
||||
model = $model.$modelValue
|
||||
|
||||
return if not model
|
||||
|
||||
if model.tags?.length
|
||||
hideAddTagButtonText()
|
||||
else
|
||||
showAddTagButtonText()
|
||||
|
||||
tagsColors = $scope.project?.tags_colors or []
|
||||
renderTags(model.tags, tagsColors)
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link:link,
|
||||
require:"ngModel"
|
||||
templateUrl: "common/tag/tag-line.html"
|
||||
}
|
||||
|
||||
module.directive("tgTagLine", ["$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", "$tgQueueModelTransformation",
|
||||
"$tgTemplate", "$compile", TagLineDirective])
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
taiga = @.taiga
|
||||
bindOnce = @.taiga.bindOnce
|
||||
debounce = @.taiga.debounce
|
||||
trim = @.taiga.trim
|
||||
|
||||
module = angular.module("taigaIssues")
|
||||
|
||||
|
@ -76,6 +77,42 @@ CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService, $loading,
|
|||
$scope.addAttachment = (attachment) ->
|
||||
attachmentsToAdd = attachmentsToAdd.push(attachment)
|
||||
|
||||
$scope.addTag = (tag, color) ->
|
||||
value = trim(tag.toLowerCase())
|
||||
|
||||
tags = $scope.project.tags
|
||||
projectTags = $scope.project.tags_colors
|
||||
|
||||
tags = [] if not tags?
|
||||
projectTags = {} if not projectTags?
|
||||
|
||||
if value not in tags
|
||||
tags.push(value)
|
||||
|
||||
projectTags[tag] = color || null
|
||||
|
||||
$scope.project.tags = tags
|
||||
|
||||
itemtags = _.clone($scope.issue.tags)
|
||||
|
||||
inserted = _.find itemtags, (it) -> it[0] == value
|
||||
|
||||
if !inserted
|
||||
itemtags.push([tag , color])
|
||||
$scope.issue.tags = itemtags
|
||||
|
||||
$scope.deleteTag = (tag) ->
|
||||
value = trim(tag[0].toLowerCase())
|
||||
|
||||
tags = $scope.project.tags
|
||||
itemtags = _.clone($scope.us.tags)
|
||||
|
||||
_.remove itemtags, (tag) -> tag[0] == value
|
||||
|
||||
$scope.us.tags = itemtags
|
||||
|
||||
_.pull($scope.issue.tags, value)
|
||||
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
|
@ -101,7 +138,6 @@ CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService, $loading,
|
|||
currentLoading.finish()
|
||||
$confirm.notify("error")
|
||||
|
||||
|
||||
submitButton = $el.find(".submit-button")
|
||||
|
||||
$el.on "submit", "form", submit
|
||||
|
|
|
@ -83,6 +83,34 @@ resourceProvider = ($config, $repo, $http, $urls, $auth, $q, $translate) ->
|
|||
service.tagsColors = (projectId) ->
|
||||
return $repo.queryOne("projects", "#{projectId}/tags_colors")
|
||||
|
||||
service.deleteTag = (projectId, tag) ->
|
||||
url = "#{$urls.resolve("projects")}/#{projectId}/delete_tag"
|
||||
return $http.post(url, {tag: tag})
|
||||
|
||||
service.createTag = (projectId, tag, color) ->
|
||||
url = "#{$urls.resolve("projects")}/#{projectId}/create_tag"
|
||||
data = {}
|
||||
data.tag = tag
|
||||
data.color = null
|
||||
if color
|
||||
data.color = color
|
||||
return $http.post(url, data)
|
||||
|
||||
service.editTag = (projectId, from_tag, to_tag, color) ->
|
||||
url = "#{$urls.resolve("projects")}/#{projectId}/edit_tag"
|
||||
data = {}
|
||||
data.from_tag = from_tag
|
||||
if to_tag
|
||||
data.to_tag = to_tag
|
||||
data.color = null
|
||||
if color
|
||||
data.color = color
|
||||
return $http.post(url, data)
|
||||
|
||||
service.mixTags = (projectId, to_tag, from_tags) ->
|
||||
url = "#{$urls.resolve("projects")}/#{projectId}/mix_tags"
|
||||
return $http.post(url, {to_tag: to_tag, from_tags: from_tags})
|
||||
|
||||
service.export = (projectId) ->
|
||||
url = "#{$urls.resolve("exporter")}/#{projectId}"
|
||||
return $http.get(url)
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
taiga = @.taiga
|
||||
bindOnce = @.taiga.bindOnce
|
||||
debounce = @.taiga.debounce
|
||||
trim = @.taiga.trim
|
||||
|
||||
CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxService, $translate, $q, attachmentsService) ->
|
||||
link = ($scope, $el, attrs) ->
|
||||
|
@ -56,6 +57,45 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxSer
|
|||
|
||||
return $q.all(promises)
|
||||
|
||||
tagsToAdd = []
|
||||
|
||||
$scope.addTag = (tag, color) ->
|
||||
value = trim(tag.toLowerCase())
|
||||
|
||||
tags = $scope.project.tags
|
||||
projectTags = $scope.project.tags_colors
|
||||
|
||||
tags = [] if not tags?
|
||||
projectTags = {} if not projectTags?
|
||||
|
||||
if value not in tags
|
||||
tags.push(value)
|
||||
|
||||
projectTags[tag] = color || null
|
||||
|
||||
$scope.project.tags = tags
|
||||
|
||||
itemtags = _.clone($scope.task.tags)
|
||||
|
||||
inserted = _.find itemtags, (it) -> it[0] == value
|
||||
|
||||
if !inserted
|
||||
itemtags.push([tag , color])
|
||||
$scope.task.tags = itemtags
|
||||
|
||||
|
||||
$scope.deleteTag = (tag) ->
|
||||
value = trim(tag[0].toLowerCase())
|
||||
|
||||
tags = $scope.project.tags
|
||||
itemtags = _.clone($scope.task.tags)
|
||||
|
||||
_.remove itemtags, (tag) -> tag[0] == value
|
||||
|
||||
$scope.task.tags = itemtags
|
||||
|
||||
_.pull($scope.task.tags, value)
|
||||
|
||||
$scope.$on "taskform:new", (ctx, sprintId, usId) ->
|
||||
$scope.task = {
|
||||
project: $scope.projectId
|
||||
|
|
|
@ -28,10 +28,12 @@ addClass = (el, className) ->
|
|||
else
|
||||
el.className += ' ' + className
|
||||
|
||||
|
||||
nl2br = (str) =>
|
||||
breakTag = '<br />'
|
||||
return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + breakTag + '$2')
|
||||
|
||||
|
||||
bindMethods = (object) =>
|
||||
dependencies = _.keys(object)
|
||||
|
||||
|
@ -43,6 +45,7 @@ bindMethods = (object) =>
|
|||
|
||||
_.bindAll(object, methods)
|
||||
|
||||
|
||||
bindOnce = (scope, attr, continuation) =>
|
||||
val = scope.$eval(attr)
|
||||
if val != undefined
|
||||
|
@ -75,6 +78,7 @@ slugify = (data) ->
|
|||
.replace(/[^\w\-]+/g, '')
|
||||
.replace(/\-\-+/g, '-')
|
||||
|
||||
|
||||
unslugify = (data) ->
|
||||
if data
|
||||
return _.capitalize(data.replace(/-/g, ' '))
|
||||
|
@ -165,6 +169,7 @@ sizeFormat = (input, precision=1) ->
|
|||
size = (input / Math.pow(1024, number)).toFixed(precision)
|
||||
return "#{size} #{units[number]}"
|
||||
|
||||
|
||||
stripTags = (str, exception) ->
|
||||
if exception
|
||||
pattern = new RegExp('<(?!' + exception + '\s*\/?)[^>]+>', 'gi')
|
||||
|
@ -172,6 +177,7 @@ stripTags = (str, exception) ->
|
|||
else
|
||||
return String(str).replace(/<\/?[^>]+>/g, '')
|
||||
|
||||
|
||||
replaceTags = (str, tags, replace) ->
|
||||
# open tag
|
||||
pattern = new RegExp('<(' + tags + ')>', 'gi')
|
||||
|
@ -183,6 +189,7 @@ replaceTags = (str, tags, replace) ->
|
|||
|
||||
return str
|
||||
|
||||
|
||||
defineImmutableProperty = (obj, name, fn) =>
|
||||
Object.defineProperty obj, name, {
|
||||
get: () =>
|
||||
|
@ -197,6 +204,7 @@ defineImmutableProperty = (obj, name, fn) =>
|
|||
return fn_result
|
||||
}
|
||||
|
||||
|
||||
_.mixin
|
||||
removeKeys: (obj, keys) ->
|
||||
_.chain([keys]).flatten().reduce(
|
||||
|
@ -211,13 +219,14 @@ _.mixin
|
|||
, [ [] ])
|
||||
|
||||
|
||||
|
||||
isImage = (name) ->
|
||||
return name.match(/\.(jpe?g|png|gif|gifv|webm)/i) != null
|
||||
|
||||
|
||||
isPdf = (name) ->
|
||||
return name.match(/\.(pdf)/i) != null
|
||||
|
||||
|
||||
patch = (oldImmutable, newImmutable) ->
|
||||
pathObj = {}
|
||||
|
||||
|
@ -230,6 +239,7 @@ patch = (oldImmutable, newImmutable) ->
|
|||
|
||||
return pathObj
|
||||
|
||||
|
||||
taiga = @.taiga
|
||||
taiga.addClass = addClass
|
||||
taiga.nl2br = nl2br
|
||||
|
|
|
@ -426,7 +426,8 @@
|
|||
"ADMIN": {
|
||||
"COMMON": {
|
||||
"TITLE_ACTION_EDIT_VALUE": "Edit value",
|
||||
"TITLE_ACTION_DELETE_VALUE": "Delete value"
|
||||
"TITLE_ACTION_DELETE_VALUE": "Delete value",
|
||||
"TITLE_ACTION_DELETE_TAG": "Delete tag"
|
||||
},
|
||||
"HELP": "Do you need help? Check out our support page!",
|
||||
"PROJECT_DEFAULT_VALUES": {
|
||||
|
@ -582,9 +583,14 @@
|
|||
},
|
||||
"PROJECT_VALUES_TAGS": {
|
||||
"TITLE": "Tags",
|
||||
"SUBTITLE": "View and edit the color of your user stories",
|
||||
"SUBTITLE": "View and edit the color of your tags",
|
||||
"EMPTY": "Currently there are no tags",
|
||||
"EMPTY_SEARCH": "It looks like nothing was found with your search criteria"
|
||||
"EMPTY_SEARCH": "It looks like nothing was found with your search criteria",
|
||||
"ACTION_ADD": "Add tag",
|
||||
"NEW_TAG": "New tag",
|
||||
"MIXING_HELP_TEXT": "Select the tags that you want to merge",
|
||||
"MIXING_MERGE": "Merge Tags",
|
||||
"SELECTED": "Selected"
|
||||
},
|
||||
"ROLES": {
|
||||
"PAGE_TITLE": "Roles - {{projectName}}",
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# File: color-selector.controller.coffee
|
||||
###
|
||||
|
||||
module = angular.module('taigaCommon')
|
||||
|
||||
class ColorSelectorController
|
||||
|
||||
constructor: () ->
|
||||
@.colorList = [
|
||||
'#fce94f',
|
||||
'#edd400',
|
||||
'#c4a000',
|
||||
'#8ae234',
|
||||
'#73d216',
|
||||
'#4e9a06',
|
||||
'#d3d7cf',
|
||||
'#fcaf3e',
|
||||
'#f57900',
|
||||
'#ce5c00',
|
||||
'#729fcf',
|
||||
'#3465a4',
|
||||
'#204a87',
|
||||
'#888a85',
|
||||
'#ad7fa8',
|
||||
'#75507b',
|
||||
'#5c3566',
|
||||
'#ef2929',
|
||||
'#cc0000',
|
||||
'#a40000'
|
||||
]
|
||||
@.displaycolorList = false
|
||||
|
||||
toggleColorList: () ->
|
||||
@.displaycolorList = !@.displaycolorList
|
||||
|
||||
onSelectDropdownColor: (color) ->
|
||||
@.onSelectColor({color: color})
|
||||
@.toggleColorList()
|
||||
|
||||
|
||||
module.controller("ColorSelectorCtrl", ColorSelectorController)
|
|
@ -0,0 +1,60 @@
|
|||
###
|
||||
# Copyright (C) 2014-2015 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# File: color-selector.controller.spec.coffee
|
||||
###
|
||||
|
||||
describe "ColorSelector", ->
|
||||
provide = null
|
||||
controller = null
|
||||
colorSelectorCtrl = null
|
||||
mocks = {}
|
||||
|
||||
_mocks = () ->
|
||||
module ($provide) ->
|
||||
provide = $provide
|
||||
return null
|
||||
|
||||
beforeEach ->
|
||||
module "taigaCommon"
|
||||
|
||||
_mocks()
|
||||
|
||||
inject ($controller) ->
|
||||
controller = $controller
|
||||
|
||||
colorSelectorCtrl = controller "ColorSelectorCtrl"
|
||||
colorSelectorCtrl.colorList = [
|
||||
'#fce94f',
|
||||
'#edd400',
|
||||
'#c4a000',
|
||||
]
|
||||
colorSelectorCtrl.displaycolorList = false
|
||||
|
||||
it "display Color Selector", () ->
|
||||
colorSelectorCtrl.toggleColorList()
|
||||
expect(colorSelectorCtrl.displaycolorList).to.be.true
|
||||
|
||||
it "on select Color", () ->
|
||||
colorSelectorCtrl.toggleColorList = sinon.stub()
|
||||
|
||||
color = '#FFFFFF'
|
||||
|
||||
colorSelectorCtrl.onSelectColor = sinon.spy()
|
||||
|
||||
colorSelectorCtrl.onSelectDropdownColor(color)
|
||||
expect(colorSelectorCtrl.toggleColorList).have.been.called
|
||||
expect(colorSelectorCtrl.onSelectColor).to.have.been.calledWith({color: color})
|
|
@ -0,0 +1,61 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# File: color-selector.directive.coffee
|
||||
###
|
||||
|
||||
module = angular.module('taigaCommon')
|
||||
|
||||
ColorSelectorDirective = ($timeout) ->
|
||||
link = (scope, el) ->
|
||||
timeout = null
|
||||
|
||||
cancel = () ->
|
||||
$timeout.cancel(timeout)
|
||||
timeout = null
|
||||
|
||||
close = () ->
|
||||
return if timeout
|
||||
|
||||
timeout = $timeout (() ->
|
||||
scope.vm.displaycolorList = false
|
||||
), 400
|
||||
|
||||
el.find('.color-selector')
|
||||
.mouseenter(cancel)
|
||||
.mouseleave(close)
|
||||
|
||||
el.find('.color-selector-dropdown')
|
||||
.mouseenter(cancel)
|
||||
.mouseleave(close)
|
||||
|
||||
return {
|
||||
link: link,
|
||||
scope:{
|
||||
onSelectColor: "&",
|
||||
color: "="
|
||||
},
|
||||
templateUrl:"components/tags/color-selector/color-selector.html",
|
||||
controller: "ColorSelectorCtrl",
|
||||
controllerAs: "vm",
|
||||
bindToController: true
|
||||
}
|
||||
|
||||
ColorSelectorDirective.$inject = [
|
||||
"$timeout"
|
||||
]
|
||||
|
||||
module.directive("tgColorSelector", ColorSelectorDirective)
|
|
@ -0,0 +1,30 @@
|
|||
.color-selector
|
||||
.tag-color.e2e-open-color-selector(
|
||||
ng-click="vm.toggleColorList()"
|
||||
ng-class="{'empty-color': !vm.color}"
|
||||
ng-style="{'background': vm.color}"
|
||||
)
|
||||
.color-selector-dropdown(ng-show="vm.displaycolorList")
|
||||
ul.color-selector-dropdown-list.e2e-color-dropdown
|
||||
li.color-selector-option(
|
||||
ng-repeat="color in vm.colorList"
|
||||
ng-style="{'background': color}"
|
||||
ng-title="color"
|
||||
ng-click="vm.onSelectDropdownColor(color)"
|
||||
)
|
||||
li.empty-color(ng-click="vm.onSelectDropdownColor(null)")
|
||||
.custom-color-selector
|
||||
.display-custom-color.empty-color(
|
||||
ng-if="!customColor.color || customColor.color.length < 7"
|
||||
)
|
||||
.display-custom-color(
|
||||
ng-if="customColor.color.length === 7"
|
||||
ng-style="{'background': customColor.color}"
|
||||
ng-click="vm.onSelectDropdownColor(customColor.color)"
|
||||
)
|
||||
input.custom-color-input(
|
||||
type="text"
|
||||
maxlength="7"
|
||||
placeholder="#000000"
|
||||
ng-model="customColor.color"
|
||||
)
|
|
@ -0,0 +1,68 @@
|
|||
@mixin color-selector-option {
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
height: 2.25rem;
|
||||
width: 2.25rem;
|
||||
min-width: 2.25rem;
|
||||
margin: 0 .5rem .5rem 0;
|
||||
&:nth-child(7n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.color-selector {
|
||||
position: relative;
|
||||
.tag-color {
|
||||
@include color-selector-option;
|
||||
border: 1px solid $gray-light;
|
||||
border-left: 0;
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
transition: background .3s ease-out;
|
||||
&.empty-color {
|
||||
@include empty-color(34);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.color-selector-dropdown {
|
||||
background: $blackish;
|
||||
left: 0;
|
||||
padding: 1rem;
|
||||
position: absolute;
|
||||
top: 2.25rem;
|
||||
width: 332px;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.color-selector-dropdown-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
list-style-type: none;
|
||||
margin-bottom: 0;
|
||||
.color-selector-option {
|
||||
@include color-selector-option;
|
||||
}
|
||||
.empty-color {
|
||||
@include color-selector-option;
|
||||
@include empty-color(34);
|
||||
}
|
||||
}
|
||||
|
||||
.custom-color-selector {
|
||||
display: flex;
|
||||
.custom-color-input {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.display-custom-color {
|
||||
@include color-selector-option;
|
||||
flex-shrink: 0;
|
||||
margin: 0;
|
||||
margin-right: .5rem;
|
||||
&.empty-color {
|
||||
@include empty-color(34);
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
a.add-tag-button.ng-animate-disabled.e2e-show-tag-input(
|
||||
ng-if="!vm.addTag && vm.checkPermissions()"
|
||||
href="#"
|
||||
title="{{'COMMON.TAGS.ADD' | translate}}"
|
||||
ng-click="vm.displayTagInput()"
|
||||
)
|
||||
tg-svg(
|
||||
svg-icon="icon-add"
|
||||
svg-title-translate="COMMON.TAGS.ADD"
|
||||
)
|
||||
span.add-tag-text(translate="COMMON.TAGS.ADD")
|
|
@ -0,0 +1,33 @@
|
|||
.add-tag-input(
|
||||
novalidate
|
||||
ng-if="vm.addTag && vm.checkPermissions()"
|
||||
tg-loading="vm.loadingAddTag"
|
||||
)
|
||||
input.tag-input.e2e-add-tag-input(
|
||||
type="text"
|
||||
placeholder="{{'COMMON.TAGS.PLACEHOLDER' | translate}}"
|
||||
autofocus
|
||||
ng-model="vm.newTag.name"
|
||||
ng-model-options="{debounce: 200}"
|
||||
)
|
||||
|
||||
tg-tags-dropdown(
|
||||
ng-if="!vm.disableColorSelection"
|
||||
ng-show="vm.newTag.name.length",
|
||||
color-array="vm.colorArray",
|
||||
tag="vm.newTag",
|
||||
on-select-tag="vm.addNewTag(name, color)"
|
||||
)
|
||||
|
||||
tg-color-selector(
|
||||
ng-if="!vm.disableColorSelection"
|
||||
color="vm.newTag.color",
|
||||
on-select-color="vm.selectColor(color)"
|
||||
)
|
||||
|
||||
tg-svg.save(
|
||||
ng-show="vm.newTag.name.length"
|
||||
svg-icon="icon-save"
|
||||
svg-title-translate="COMMON.TAGS.ADD"
|
||||
ng-click="vm.addNewTag(vm.newTag.name, vm.newTag.color)"
|
||||
)
|
|
@ -0,0 +1,59 @@
|
|||
$tag-input-width: 250px;
|
||||
|
||||
.add-tag-input {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
width: $tag-input-width;
|
||||
input {
|
||||
border-color: $gray-light;
|
||||
padding: 6px;
|
||||
width: 14rem;
|
||||
}
|
||||
.save {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
fill: $grayer;
|
||||
margin: .5rem 0 0 .5rem;
|
||||
transition: .2s linear;
|
||||
&:hover {
|
||||
fill: $primary;
|
||||
}
|
||||
}
|
||||
.tags-dropdown {
|
||||
@include font-size(small);
|
||||
background: $white;
|
||||
border: 1px solid $gray-light;
|
||||
border-top: 0;
|
||||
box-shadow: 2px 2px 3px rgba($black, .2);
|
||||
left: 0;
|
||||
max-height: 20vh;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
top: 2.25rem;
|
||||
width: 85%;
|
||||
z-index: 99;
|
||||
}
|
||||
.tags-dropdown-option {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: .5rem;
|
||||
}
|
||||
.tags-dropdown-color {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
li {
|
||||
&:hover,
|
||||
&.selected {
|
||||
background: lighten($primary-light, 50%);
|
||||
cursor: pointer;
|
||||
transition: .2s;
|
||||
transition-delay: .1s;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# File: tag-line.directive.coffee
|
||||
###
|
||||
|
||||
module = angular.module('taigaCommon')
|
||||
|
||||
TagOptionDirective = () ->
|
||||
select = (selected) ->
|
||||
selected.addClass('selected')
|
||||
|
||||
selectedPosition = selected.position().top + selected.outerHeight()
|
||||
containerHeight = selected.parent().outerHeight()
|
||||
|
||||
if selectedPosition > containerHeight
|
||||
diff = selectedPosition - containerHeight
|
||||
selected.parent().scrollTop(selected.parent().scrollTop() + diff)
|
||||
else if selected.position().top < 0
|
||||
selected.parent().scrollTop(selected.parent().scrollTop() + selected.position().top)
|
||||
|
||||
dispatch = (el, code, scope) ->
|
||||
activeElement = el.find(".selected")
|
||||
|
||||
# Key: down
|
||||
if code == 40
|
||||
if not activeElement.length
|
||||
select(el.find('li:first'))
|
||||
else
|
||||
next = activeElement.next('li')
|
||||
if next.length
|
||||
activeElement.removeClass('selected')
|
||||
select(next)
|
||||
# Key: up
|
||||
else if code == 38
|
||||
if not activeElement.length
|
||||
select(el.find('li:last'))
|
||||
else
|
||||
prev = activeElement.prev('li')
|
||||
|
||||
if prev.length
|
||||
activeElement.removeClass('selected')
|
||||
select(prev)
|
||||
|
||||
stop = ->
|
||||
$(document).off(".tags-keyboard-navigation")
|
||||
|
||||
link = (scope, el) ->
|
||||
stop()
|
||||
|
||||
$(document).on "keydown.tags-keyboard-navigation", (event) =>
|
||||
code = if event.keyCode then event.keyCode else event.which
|
||||
|
||||
if code == 40 || code == 38
|
||||
event.preventDefault()
|
||||
|
||||
dispatch(el, code, scope)
|
||||
|
||||
scope.$on("$destroy", stop)
|
||||
|
||||
return {
|
||||
link: link,
|
||||
templateUrl:"components/tags/tag-dropdown/tag-dropdown.html",
|
||||
scope: {
|
||||
onSelectTag: "&",
|
||||
colorArray: "=",
|
||||
tag: "="
|
||||
}
|
||||
}
|
||||
|
||||
module.directive("tgTagsDropdown", TagOptionDirective)
|
|
@ -0,0 +1,12 @@
|
|||
ul.tags-dropdown
|
||||
li(
|
||||
ng-repeat="tag in colorArray | filter: tag.name",
|
||||
ng-click="onSelectTag({name: tag[0], color: tag[1]})"
|
||||
)
|
||||
.tags-dropdown-option
|
||||
span.tags-dropdown-name {{tag[0]}}
|
||||
span.tags-dropdown-color(
|
||||
ng-if="tag[1]"
|
||||
ng-style="{'background': tag[1]}"
|
||||
ng-title="tag[1]"
|
||||
)
|
|
@ -0,0 +1,56 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# File: tag-line.controller.coffee
|
||||
###
|
||||
|
||||
trim = @.taiga.trim
|
||||
|
||||
module = angular.module('taigaCommon')
|
||||
|
||||
class TagLineCommonController
|
||||
|
||||
@.$inject = [
|
||||
"tgTagLineService"
|
||||
]
|
||||
|
||||
constructor: (@tagLineService) ->
|
||||
@.newTag = {name: "", color: null}
|
||||
@.colorArray = []
|
||||
@.addTag = false
|
||||
|
||||
checkPermissions: () ->
|
||||
return @tagLineService.checkPermissions(@.project.my_permissions, @.permissions)
|
||||
|
||||
_createColorsArray: (projectTagColors) ->
|
||||
@.colorArray = @tagLineService.createColorsArray(projectTagColors)
|
||||
|
||||
displayTagInput: () ->
|
||||
@.addTag = true
|
||||
|
||||
addNewTag: (name, color) ->
|
||||
@.newTag.name = ""
|
||||
@.newTag.color = null
|
||||
|
||||
if @.project.tags_colors[name]
|
||||
color = @.project.tags_colors[name]
|
||||
|
||||
@.onAddTag({name: name, color: color}) if name.length
|
||||
|
||||
selectColor: (color) ->
|
||||
@.newTag.color = color
|
||||
|
||||
module.controller("TagLineCommonCtrl", TagLineCommonController)
|
|
@ -0,0 +1,96 @@
|
|||
###
|
||||
# Copyright (C) 2014-2015 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# File:tag-line-common.controller.spec.coffee
|
||||
###
|
||||
|
||||
describe "TagLineCommon", ->
|
||||
provide = null
|
||||
controller = null
|
||||
TagLineCommonCtrl = null
|
||||
mocks = {}
|
||||
|
||||
_mockTgTagLineService = () ->
|
||||
mocks.tgTagLineService = {
|
||||
checkPermissions: sinon.stub()
|
||||
createColorsArray: sinon.stub()
|
||||
renderTags: sinon.stub()
|
||||
}
|
||||
|
||||
provide.value "tgTagLineService", mocks.tgTagLineService
|
||||
|
||||
|
||||
_mocks = () ->
|
||||
module ($provide) ->
|
||||
provide = $provide
|
||||
_mockTgTagLineService()
|
||||
return null
|
||||
|
||||
beforeEach ->
|
||||
module "taigaCommon"
|
||||
|
||||
_mocks()
|
||||
|
||||
inject ($controller) ->
|
||||
controller = $controller
|
||||
|
||||
TagLineCommonCtrl = controller "TagLineCommonCtrl"
|
||||
TagLineCommonCtrl.tags = []
|
||||
TagLineCommonCtrl.colorArray = []
|
||||
TagLineCommonCtrl.addTag = false
|
||||
|
||||
it "check permissions", () ->
|
||||
TagLineCommonCtrl.project = {
|
||||
}
|
||||
TagLineCommonCtrl.project.my_permissions = [
|
||||
'permission1',
|
||||
'permission2'
|
||||
]
|
||||
TagLineCommonCtrl.permissions = 'permissions1'
|
||||
|
||||
TagLineCommonCtrl.checkPermissions()
|
||||
expect(mocks.tgTagLineService.checkPermissions).have.been.calledWith(TagLineCommonCtrl.project.my_permissions, TagLineCommonCtrl.permissions)
|
||||
|
||||
it "create Colors Array", () ->
|
||||
projectTagColors = 'string'
|
||||
mocks.tgTagLineService.createColorsArray.withArgs(projectTagColors).returns(true)
|
||||
TagLineCommonCtrl._createColorsArray(projectTagColors)
|
||||
expect(TagLineCommonCtrl.colorArray).to.be.equal(true)
|
||||
|
||||
it "display tag input", () ->
|
||||
TagLineCommonCtrl.addTag = false
|
||||
TagLineCommonCtrl.displayTagInput()
|
||||
expect(TagLineCommonCtrl.addTag).to.be.true
|
||||
|
||||
it "on add tag", () ->
|
||||
TagLineCommonCtrl.loadingAddTag = true
|
||||
tag = 'tag1'
|
||||
tags = ['tag1', 'tag2']
|
||||
color = "CC0000"
|
||||
|
||||
TagLineCommonCtrl.project = {
|
||||
tags: ['tag1', 'tag2'],
|
||||
tags_colors: ["#CC0000", "CCBB00"]
|
||||
}
|
||||
|
||||
TagLineCommonCtrl.onAddTag = sinon.spy()
|
||||
TagLineCommonCtrl.newTag = {name: "11", color: "22"}
|
||||
|
||||
TagLineCommonCtrl.addNewTag(tag, color)
|
||||
|
||||
expect(TagLineCommonCtrl.onAddTag).have.been.calledWith({name: tag, color: color})
|
||||
expect(TagLineCommonCtrl.newTag.name).to.be.eql("")
|
||||
expect(TagLineCommonCtrl.newTag.color).to.be.null
|
|
@ -0,0 +1,69 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# File: tag-line.directive.coffee
|
||||
###
|
||||
|
||||
module = angular.module('taigaCommon')
|
||||
|
||||
TagLineCommonDirective = () ->
|
||||
link = (scope, el, attr, ctrl) ->
|
||||
if !_.isUndefined(attr.disableColorSelection)
|
||||
ctrl.disableColorSelection = true
|
||||
|
||||
unwatch = scope.$watch "vm.project", (project) ->
|
||||
return if !project || !Object.keys(project).length
|
||||
|
||||
unwatch()
|
||||
ctrl.colorArray = ctrl._createColorsArray(ctrl.project.tags_colors)
|
||||
|
||||
el.on "keydown", ".tag-input", (event) ->
|
||||
if event.keyCode == 27 && ctrl.newTag.name.length
|
||||
ctrl.addTag = false
|
||||
|
||||
ctrl.newTag.name = ""
|
||||
ctrl.newTag.color = ""
|
||||
|
||||
event.stopPropagation()
|
||||
else if event.keyCode == 13
|
||||
event.preventDefault()
|
||||
|
||||
if el.find('.tags-dropdown .selected').length
|
||||
tagName = $('.tags-dropdown .selected .tags-dropdown-name').text()
|
||||
ctrl.addNewTag(tagName, null)
|
||||
else
|
||||
ctrl.addNewTag(ctrl.newTag.name, ctrl.newTag.color)
|
||||
|
||||
scope.$apply()
|
||||
|
||||
return {
|
||||
link: link,
|
||||
scope: {
|
||||
permissions: "@",
|
||||
loadingAddTag: "=",
|
||||
loadingRemoveTag: "=",
|
||||
tags: "=",
|
||||
project: "=",
|
||||
onAddTag: "&",
|
||||
onDeleteTag: "&"
|
||||
},
|
||||
templateUrl:"components/tags/tag-line-common/tag-line-common.html",
|
||||
controller: "TagLineCommonCtrl",
|
||||
controllerAs: "vm",
|
||||
bindToController: true
|
||||
}
|
||||
|
||||
module.directive("tgTagLineCommon", TagLineCommonDirective)
|
|
@ -0,0 +1,26 @@
|
|||
.tags-container
|
||||
.tag(
|
||||
ng-if="tag[1]"
|
||||
ng-repeat="tag in vm.tags"
|
||||
ng-style="{'border-left': '.3rem solid' + tag[1]}"
|
||||
)
|
||||
tg-tag(
|
||||
tag="tag"
|
||||
loading-remove-tag="vm.loadingRemoveTag"
|
||||
project="vm.project"
|
||||
on-delete-tag="vm.onDeleteTag({tag: tag})"
|
||||
has-permissions="{{vm.checkPermissions()}}"
|
||||
)
|
||||
.tag(
|
||||
ng-if="!tag[1]"
|
||||
ng-repeat="tag in vm.tags"
|
||||
)
|
||||
tg-tag(
|
||||
tag="tag"
|
||||
loading-remove-tag="vm.loadingRemoveTag"
|
||||
on-delete-tag="vm.onDeleteTag({tag: tag})"
|
||||
has-permissions="{{vm.checkPermissions()}}"
|
||||
)
|
||||
|
||||
include ../components/add-tag-button
|
||||
include ../components/add-tag-input
|
|
@ -0,0 +1,88 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# File: tag-line.controller.coffee
|
||||
###
|
||||
|
||||
trim = @.taiga.trim
|
||||
|
||||
module = angular.module('taigaCommon')
|
||||
|
||||
class TagLineController
|
||||
|
||||
@.$inject = [
|
||||
"$rootScope",
|
||||
"$tgConfirm",
|
||||
"$tgQueueModelTransformation",
|
||||
]
|
||||
|
||||
constructor: (@rootScope, @confirm, @modelTransform) ->
|
||||
@.loadingAddTag = false
|
||||
|
||||
onDeleteTag: (tag) ->
|
||||
@.loadingRemoveTag = tag[0]
|
||||
|
||||
onDeleteTagSuccess = (item) =>
|
||||
@rootScope.$broadcast("object:updated")
|
||||
@.loadingRemoveTag = false
|
||||
|
||||
return item
|
||||
|
||||
onDeleteTagError = () =>
|
||||
@confirm.notify("error")
|
||||
@.loadingRemoveTag = false
|
||||
|
||||
tagName = trim(tag[0].toLowerCase())
|
||||
|
||||
transform = @modelTransform.save (item) ->
|
||||
itemtags = _.clone(item.tags)
|
||||
|
||||
_.remove itemtags, (tag) -> tag[0] == tagName
|
||||
|
||||
item.tags = itemtags
|
||||
|
||||
return item
|
||||
|
||||
return transform.then(onDeleteTagSuccess, onDeleteTagError)
|
||||
|
||||
onAddTag: (tag, color) ->
|
||||
@.loadingAddTag = true
|
||||
|
||||
onAddTagSuccess = (item) =>
|
||||
@rootScope.$broadcast("object:updated") #its a kind of magic.
|
||||
@.addTag = false
|
||||
@.loadingAddTag = false
|
||||
|
||||
return item
|
||||
|
||||
onAddTagError = () =>
|
||||
@.loadingAddTag = false
|
||||
@confirm.notify("error")
|
||||
|
||||
transform = @modelTransform.save (item) =>
|
||||
value = trim(tag.toLowerCase())
|
||||
|
||||
itemtags = _.clone(item.tags)
|
||||
|
||||
itemtags.push([tag , color])
|
||||
|
||||
item.tags = itemtags
|
||||
|
||||
return item
|
||||
|
||||
return transform.then(onAddTagSuccess, onAddTagError)
|
||||
|
||||
module.controller("TagLineCtrl", TagLineController)
|
|
@ -0,0 +1,157 @@
|
|||
###
|
||||
# Copyright (C) 2014-2015 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# File:tag-line-detail.controller.spec.coffee
|
||||
###
|
||||
|
||||
describe "TagLineDetail", ->
|
||||
provide = null
|
||||
controller = null
|
||||
TagLineController = null
|
||||
mocks = {}
|
||||
|
||||
_mockRootScope = () ->
|
||||
mocks.rootScope = {
|
||||
$broadcast: sinon.stub()
|
||||
}
|
||||
|
||||
provide.value "$rootScope", mocks.rootScope
|
||||
|
||||
_mockTgConfirm = () ->
|
||||
mocks.tgConfirm = {
|
||||
notify: sinon.stub()
|
||||
}
|
||||
|
||||
provide.value "$tgConfirm", mocks.tgConfirm
|
||||
|
||||
_mockTgQueueModelTransformation = () ->
|
||||
mocks.tgQueueModelTransformation = {
|
||||
save: sinon.stub()
|
||||
}
|
||||
|
||||
provide.value "$tgQueueModelTransformation", mocks.tgQueueModelTransformation
|
||||
|
||||
|
||||
_mocks = () ->
|
||||
module ($provide) ->
|
||||
provide = $provide
|
||||
_mockRootScope()
|
||||
_mockTgConfirm()
|
||||
_mockTgQueueModelTransformation()
|
||||
|
||||
return null
|
||||
|
||||
beforeEach ->
|
||||
module "taigaCommon"
|
||||
|
||||
_mocks()
|
||||
|
||||
inject ($controller) ->
|
||||
controller = $controller
|
||||
|
||||
TagLineController = controller "TagLineCtrl"
|
||||
|
||||
it "on delete tag success", (done) ->
|
||||
tag = {
|
||||
name: 'tag1'
|
||||
}
|
||||
tagName = tag.name
|
||||
|
||||
item = {
|
||||
tags: [
|
||||
['tag1'],
|
||||
['tag2'],
|
||||
['tag3']
|
||||
]
|
||||
}
|
||||
|
||||
mocks.tgQueueModelTransformation.save.callsArgWith(0, item)
|
||||
mocks.tgQueueModelTransformation.save.promise().resolve(item)
|
||||
|
||||
TagLineController.onDeleteTag(['tag1', '#000']).then (item) ->
|
||||
expect(item.tags).to.be.eql([
|
||||
['tag2'],
|
||||
['tag3']
|
||||
])
|
||||
expect(TagLineController.loadingRemoveTag).to.be.false
|
||||
expect(mocks.rootScope.$broadcast).to.be.calledWith("object:updated")
|
||||
done()
|
||||
|
||||
it "on delete tag error", (done) ->
|
||||
mocks.tgQueueModelTransformation.save.promise().reject(new Error('error'))
|
||||
|
||||
TagLineController.onDeleteTag(['tag1']).finally () ->
|
||||
expect(TagLineController.loadingRemoveTag).to.be.false
|
||||
expect(mocks.tgConfirm.notify).to.be.calledWith("error")
|
||||
done()
|
||||
|
||||
it "on add tag success", (done) ->
|
||||
tag = 'tag1'
|
||||
tagColor = '#eee'
|
||||
|
||||
item = {
|
||||
tags: [
|
||||
['tag2'],
|
||||
['tag3']
|
||||
]
|
||||
}
|
||||
|
||||
mockPromise = mocks.tgQueueModelTransformation.save.promise()
|
||||
|
||||
mocks.tgQueueModelTransformation.save.callsArgWith(0, item)
|
||||
promise = TagLineController.onAddTag(tag, tagColor)
|
||||
|
||||
expect(TagLineController.loadingAddTag).to.be.true
|
||||
|
||||
mockPromise.resolve(item)
|
||||
|
||||
promise.then (item) ->
|
||||
expect(item.tags).to.be.eql([
|
||||
['tag2'],
|
||||
['tag3'],
|
||||
['tag1', '#eee']
|
||||
])
|
||||
|
||||
expect(mocks.rootScope.$broadcast).to.be.calledWith("object:updated")
|
||||
expect(TagLineController.addTag).to.be.false
|
||||
expect(TagLineController.loadingAddTag).to.be.false
|
||||
|
||||
done()
|
||||
|
||||
it "on add tag error", (done) ->
|
||||
tag = 'tag1'
|
||||
tagColor = '#eee'
|
||||
|
||||
item = {
|
||||
tags: [
|
||||
['tag2'],
|
||||
['tag3']
|
||||
]
|
||||
}
|
||||
|
||||
mockPromise = mocks.tgQueueModelTransformation.save.promise()
|
||||
|
||||
mocks.tgQueueModelTransformation.save.callsArgWith(0, item)
|
||||
promise = TagLineController.onAddTag(tag, tagColor)
|
||||
|
||||
expect(TagLineController.loadingAddTag).to.be.true
|
||||
|
||||
mockPromise.reject(new Error('error'))
|
||||
|
||||
promise.then (item) ->
|
||||
expect(TagLineController.loadingAddTag).to.be.false
|
||||
expect(mocks.tgConfirm.notify).to.be.calledWith("error")
|
||||
done()
|
|
@ -0,0 +1,35 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# File: tag-line.directive.coffee
|
||||
###
|
||||
|
||||
module = angular.module('taigaCommon')
|
||||
|
||||
TagLineDirective = () ->
|
||||
return {
|
||||
scope: {
|
||||
item: "=",
|
||||
permissions: "@",
|
||||
project: "="
|
||||
},
|
||||
templateUrl:"components/tags/tag-line-detail/tag-line-detail.html",
|
||||
controller: "TagLineCtrl",
|
||||
controllerAs: "vm",
|
||||
bindToController: true
|
||||
}
|
||||
|
||||
module.directive("tgTagLine", TagLineDirective)
|
|
@ -0,0 +1,9 @@
|
|||
tg-tag-line-common.tags-block(
|
||||
project="vm.project"
|
||||
tags="vm.item.tags"
|
||||
permissions="{{vm.permissions}}"
|
||||
loading-remove-tag="vm.loadingRemoveTag"
|
||||
loading-add-tag="vm.loadingAddTag"
|
||||
on-add-tag="vm.onAddTag(name, color)"
|
||||
on-delete-tag="vm.onDeleteTag(tag)"
|
||||
)
|
|
@ -0,0 +1,22 @@
|
|||
.tags-block {
|
||||
align-content: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.add-tag-button {
|
||||
color: $gray-light;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
&:hover {
|
||||
color: $primary-light;
|
||||
}
|
||||
.icon-add {
|
||||
@include svg-size(.9rem);
|
||||
fill: currentColor;
|
||||
margin: .5rem .25rem 0 0;
|
||||
}
|
||||
.add-tag-text {
|
||||
@include font-size(small);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# File: tag-line.service.coffee
|
||||
###
|
||||
|
||||
module = angular.module('taigaCommon')
|
||||
|
||||
class TagLineService extends taiga.Service
|
||||
@.$inject = []
|
||||
|
||||
constructor: () ->
|
||||
|
||||
checkPermissions: (myPermissions, projectPermissions) ->
|
||||
return _.includes(myPermissions, projectPermissions)
|
||||
|
||||
createColorsArray: (projectTagColors) ->
|
||||
return _.map(projectTagColors, (index, value) ->
|
||||
return [value, index]
|
||||
)
|
||||
|
||||
module.service("tgTagLineService", TagLineService)
|
|
@ -0,0 +1,33 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# File: tag-line.directive.coffee
|
||||
###
|
||||
|
||||
module = angular.module('taigaCommon')
|
||||
|
||||
TagDirective = () ->
|
||||
return {
|
||||
templateUrl:"components/tags/tag/tag.html",
|
||||
scope: {
|
||||
tag: "<",
|
||||
loadingRemoveTag: "<",
|
||||
onDeleteTag: "&",
|
||||
hasPermissions: "@"
|
||||
}
|
||||
}
|
||||
|
||||
module.directive("tgTag", TagDirective)
|
|
@ -0,0 +1,8 @@
|
|||
span {{ tag[0] }}
|
||||
tg-svg.icon-close.e2e-delete-tag(
|
||||
ng-if="hasPermissions"
|
||||
svg-icon="icon-close"
|
||||
svg-title-translate="COMMON.TAG.DELETE"
|
||||
ng-click="onDeleteTag(tag)"
|
||||
tg-loading="loadingRemoveTag == tag[0]"
|
||||
)
|
|
@ -0,0 +1,21 @@
|
|||
.tag {
|
||||
@include font-type(light);
|
||||
@include font-size(small);
|
||||
background: $mass-white;
|
||||
border-radius: 0 5px 5px 0;
|
||||
color: $grayer;
|
||||
display: inline-block;
|
||||
margin: 0 .5rem .5rem 0;
|
||||
padding: .5rem;
|
||||
text-align: center;
|
||||
.icon-close {
|
||||
@include svg-size(.7rem);
|
||||
cursor: pointer;
|
||||
fill: $red-light;
|
||||
margin-left: .25rem;
|
||||
}
|
||||
.loading-spinner {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
}
|
|
@ -5,12 +5,6 @@ section.home-project-list(ng-if="vm.projects.size")
|
|||
tg-repeat="project in vm.projects"
|
||||
ng-class="{'blocked-project': project.get('blocked_code')}"
|
||||
)
|
||||
.tags-container
|
||||
.project-tag(
|
||||
style="background: {{tag.get('color')}}"
|
||||
title="{{tag.get('name')}}"
|
||||
tg-repeat="tag in project.get('colorized_tags') track by tag.get('name')"
|
||||
)
|
||||
.project-card-inner(
|
||||
href="#"
|
||||
tg-nav="project:project=project.get('slug')"
|
||||
|
|
|
@ -43,11 +43,8 @@ div.wrapper
|
|||
p.description {{vm.project.get('description')}}
|
||||
|
||||
div.single-project-tags.tags-container(ng-if="::vm.project.get('tags').size")
|
||||
span.tag(
|
||||
style='border-left: 5px solid {{::tag.get("color")}};',
|
||||
tg-repeat="tag in ::vm.project.get('colorized_tags')"
|
||||
)
|
||||
span.tag-name {{::tag.get('name')}}
|
||||
span.tag(tg-repeat="tag in ::vm.project.get('tags')")
|
||||
span.tag-name {{::tag}}
|
||||
|
||||
div.project-data
|
||||
section.timeline(ng-if="vm.project")
|
||||
|
@ -60,7 +57,9 @@ div.wrapper
|
|||
title="{{'PROJECT.LOOKING_FOR_PEOPLE' | translate}}"
|
||||
)
|
||||
h3 {{'PROJECT.LOOKING_FOR_PEOPLE' | translate}}
|
||||
p(ng-if="vm.project.get('looking_for_people_note')") {{::vm.project.get('looking_for_people_note')}}
|
||||
p(ng-if="vm.project.get('looking_for_people_note')")
|
||||
| {{::vm.project.get('looking_for_people_note')}}
|
||||
|
||||
h2.title {{"PROJECT.SECTION.TEAM" | translate}}
|
||||
ul.involved-team
|
||||
li(tg-repeat="member in vm.members")
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
taiga = @.taiga
|
||||
groupBy = @.taiga.groupBy
|
||||
|
||||
|
||||
class ProjectsService extends taiga.Service
|
||||
@.$inject = ["tgResources", "$projectUrl", "tgLightboxFactory"]
|
||||
|
||||
|
@ -42,16 +43,6 @@ class ProjectsService extends taiga.Service
|
|||
url = @projectUrl.get(project.toJS())
|
||||
|
||||
project = project.set("url", url)
|
||||
colorized_tags = []
|
||||
|
||||
if project.get("tags")
|
||||
tags = project.get("tags").sort()
|
||||
|
||||
colorized_tags = tags.map (tag) ->
|
||||
color = project.get("tags_colors").get(tag)
|
||||
return Immutable.fromJS({name: tag, color: color})
|
||||
|
||||
project = project.set("colorized_tags", colorized_tags)
|
||||
|
||||
return project
|
||||
|
||||
|
|
|
@ -129,8 +129,7 @@ describe "tgProjectsService", ->
|
|||
id: 2,
|
||||
url: 'url-2',
|
||||
tags: ['xx', 'yy', 'aa'],
|
||||
tags_colors: {xx: "red", yy: "blue", aa: "white"},
|
||||
colorized_tags: [{name: 'aa', color: 'white'}, {name: 'xx', color: 'red'}, {name: 'yy', color: 'blue'}]
|
||||
tags_colors: {xx: "red", yy: "blue", aa: "white"}
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -157,8 +156,7 @@ describe "tgProjectsService", ->
|
|||
id: 2,
|
||||
url: 'url-2',
|
||||
tags: ['xx', 'yy', 'aa'],
|
||||
tags_colors: {xx: "red", yy: "blue", aa: "white"},
|
||||
colorized_tags: [{name: 'aa', color: 'white'}, {name: 'xx', color: 'red'}, {name: 'yy', color: 'blue'}]
|
||||
tags_colors: {xx: "red", yy: "blue", aa: "white"}
|
||||
}
|
||||
])
|
||||
|
||||
|
|
|
@ -72,10 +72,15 @@ div.wrapper(
|
|||
)
|
||||
fieldset
|
||||
label(for="tags") {{ 'ADMIN.PROJECT_PROFILE.TAGS' | translate }}
|
||||
div.tags-block(
|
||||
ng-if="project.id"
|
||||
tg-lb-tag-line
|
||||
ng-model="project.tags"
|
||||
|
||||
tg-tag-line-common.tags-block(
|
||||
disable-color-selection
|
||||
ng-if="project"
|
||||
project="project"
|
||||
tags="projectTags"
|
||||
permissions="modify_project"
|
||||
on-add-tag="ctrl.addTag(name, color)"
|
||||
on-delete-tag="ctrl.deleteTag(tag)"
|
||||
)
|
||||
|
||||
fieldset(ng-if="project.owner.id != user.id")
|
||||
|
|
|
@ -13,55 +13,20 @@ div.wrapper(
|
|||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-tags")
|
||||
include ../includes/modules/admin-submenu-project-values
|
||||
|
||||
section.main.admin-common.admin-attributes.colors-table
|
||||
include ../includes/components/mainTitle
|
||||
p.admin-subtitle(translate="ADMIN.PROJECT_VALUES_TAGS.SUBTITLE")
|
||||
|
||||
.admin-attributes-section(
|
||||
ng-controller="ProjectTagsController as ctrl"
|
||||
)
|
||||
.admin-attributes-section-wrapper-empty(
|
||||
ng-if="!projectTags.length"
|
||||
tg-loading="ctrl.loading"
|
||||
)
|
||||
p(translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY")
|
||||
.admin-attributes-section-wrapper(
|
||||
ng-if="projectTags.length"
|
||||
)
|
||||
.table-header.table-tags-editor
|
||||
.row
|
||||
.color-column(translate="COMMON.FIELDS.COLOR")
|
||||
.color-name(translate="COMMON.FIELDS.NAME")
|
||||
.color-filter
|
||||
input.e2e-tags-filter(
|
||||
type="text"
|
||||
name="name"
|
||||
ng-model="tagsFilter.name"
|
||||
ng-model-options="{debounce: 200}"
|
||||
)
|
||||
tg-svg(
|
||||
svg-icon="icon-search"
|
||||
)
|
||||
|
||||
p.admin-attributes-section-wrapper-empty(
|
||||
tg-loading="ctrl.loading"
|
||||
translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY_SEARCH"
|
||||
ng-if="!(projectTags | filter:tagsFilter).length"
|
||||
section.main.admin-common.admin-attributes.colors-table.tags-table(
|
||||
tg-project-tags,
|
||||
ng-controller="ProjectTagsController as ctrl"
|
||||
)
|
||||
header.header-with-actions
|
||||
.title
|
||||
include ../includes/components/mainTitle
|
||||
p.admin-subtitle(translate="ADMIN.PROJECT_VALUES_TAGS.SUBTITLE")
|
||||
.action-buttons
|
||||
a.button.button-green.show-add-new(
|
||||
href=""
|
||||
title="{{ 'ADMIN.PROJECT_VALUES_TAGS.NEW_TAG'|translate }}"
|
||||
translate="ADMIN.PROJECT_VALUES_TAGS.NEW_TAG"
|
||||
)
|
||||
.table-main(
|
||||
ng-repeat="tag in projectTags | filter:tagsFilter"
|
||||
tg-bind-scope
|
||||
)
|
||||
form(
|
||||
tg-project-tag
|
||||
ng-model="tag"
|
||||
)
|
||||
.row.edition.no-draggable
|
||||
.color-column(
|
||||
tg-color-selection
|
||||
ng-model="tag"
|
||||
)
|
||||
.current-color(ng-style="{background: tag.color}")
|
||||
include ../includes/components/select-color
|
||||
|
||||
.color-name {{ tag.name }}
|
||||
.admin-attributes-section
|
||||
include ../includes/modules/admin/project-tags
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<% _.each(tags, function(tag) { %>
|
||||
span(class="tag", style!="<%- tag.style %>")
|
||||
span(
|
||||
class="tag"
|
||||
style!="<%- tag.style %>"
|
||||
)
|
||||
span.tag-name <%- tag.name %>
|
||||
a.remove-tag(href="", title="{{'COMMON.TAGS.DELETE' | translate}}")
|
||||
tg-svg(svg-icon="icon-close")
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<% if (tag.name == deleteTagLoading) { %>
|
||||
div(tg-loading="true")
|
||||
<% } else { %>
|
||||
span.tag(style!="border-left: 5px solid <%- tag.color %>;")
|
||||
span.tag(style!="border-left: 5px solid <%- tag.style %>;")
|
||||
span.tag-name <%- tag.name %>
|
||||
<% if (isEditable) { %>
|
||||
a.remove-tag(
|
||||
|
|
|
@ -20,7 +20,9 @@ div.popover.select-color
|
|||
li.color(style="background: #ef2929", data-color="#ef2929")
|
||||
li.color(style="background: #cc0000", data-color="#cc0000")
|
||||
li.color(style="background: #a40000", data-color="#a40000")
|
||||
li.color(style="background: #2e3436", data-color="#2e3436")
|
||||
li.color(style="background: #2e3436", data-color="#2e3436", ng-if="!allowEmpty")
|
||||
li.color(data-color="", ng-class="{'empty-color': allowEmpty}")
|
||||
|
||||
input(type="text", placeholder="personalized colors", ng-model="color")
|
||||
div.selected-color(ng-style="{'background-color': color}")
|
||||
div.selected-color(ng-style="{'background-color': color}", ng-if="color !== null")
|
||||
div.selected-color(ng-style="{'background-color': none}", ng-if="color === null")
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
section
|
||||
.admin-tags-section-wrapper-empty(
|
||||
ng-show="!projectTagsAll.length"
|
||||
tg-loading="ctrl.loading"
|
||||
)
|
||||
p(translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY")
|
||||
|
||||
.admin-tags-section-wrapper(
|
||||
ng-show="projectTagsAll.length"
|
||||
)
|
||||
form.add-tag-container.new-value.hidden
|
||||
tg-color-selection.color-column(
|
||||
tg-allow-empty="true"
|
||||
ng-model="newValue"
|
||||
)
|
||||
.current-color(
|
||||
ng-style="{background: newValue.color}"
|
||||
ng-if="newValue.color"
|
||||
)
|
||||
.current-color.empty-color(ng-if="!newValue.color")
|
||||
include ../../components/select-color
|
||||
|
||||
.tag-name
|
||||
input(
|
||||
name="name"
|
||||
type="text"
|
||||
placeholder="{{'ADMIN.TYPES.PLACEHOLDER_WRITE_NAME' | translate}}",
|
||||
ng-model="newValue.name"
|
||||
data-required="true"
|
||||
data-maxlength="255"
|
||||
)
|
||||
|
||||
.options-column
|
||||
a.add-new.e2e-save(href="")
|
||||
tg-svg(
|
||||
title="{{'COMMON.ADD' | translate}}",
|
||||
svg-icon="icon-save"
|
||||
)
|
||||
a.delete-new(href="")
|
||||
tg-svg(
|
||||
title="{{'COMMON.CANCEL' | translate}}",
|
||||
svg-icon="icon-close"
|
||||
)
|
||||
|
||||
.table-header.table-tags-editor
|
||||
div.row.header-tag-row
|
||||
.color-column(translate="COMMON.FIELDS.COLOR")
|
||||
.status-name(translate="COMMON.FIELDS.NAME")
|
||||
.color-filter
|
||||
input.e2e-tags-filter(
|
||||
type="text"
|
||||
name="name"
|
||||
ng-model="tagsFilter.name"
|
||||
ng-model-options="{debounce: 200}"
|
||||
)
|
||||
tg-svg(
|
||||
svg-icon="icon-search"
|
||||
)
|
||||
|
||||
.table-main.table-admin-tags
|
||||
div(ng-show="!mixingTags.toTag")
|
||||
.admin-attributes-section-wrapper-empty(
|
||||
ng-show="!projectTags.length"
|
||||
tg-loading="ctrl.loading"
|
||||
)
|
||||
p(translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY_SEARCH")
|
||||
p lalalaal
|
||||
|
||||
div(
|
||||
ng-repeat="tag in projectTags"
|
||||
tg-bind-scope
|
||||
)
|
||||
form(tg-bind-scope)
|
||||
.row.tag-row.table-main.visualization(ng-class="{{ ctrl.mixingClass(tag) }}")
|
||||
.color-column
|
||||
.current-color(
|
||||
ng-style="{background: tag.color}"
|
||||
ng-if="tag.color"
|
||||
)
|
||||
.current-color.empty-color(ng-if="!tag.color")
|
||||
|
||||
.status-name
|
||||
span(tg-bo-html="tag.name")
|
||||
|
||||
.options-column
|
||||
a.mix-tags(href="")
|
||||
tg-svg(
|
||||
title="{{'ADMIN.PROJECT_VALUES_TAGS.MIXING_MERGE' | translate}}"
|
||||
svg-icon="icon-merge"
|
||||
)
|
||||
div.popover.merge-explanation
|
||||
span(translate="ADMIN.PROJECT_VALUES_TAGS.MIXING_MERGE")
|
||||
|
||||
a.edit-value(href="")
|
||||
tg-svg(
|
||||
svg-icon="icon-edit"
|
||||
title="{{'ADMIN.COMMON.TITLE_ACTION_EDIT_VALUE' | translate}}"
|
||||
)
|
||||
|
||||
a.delete-tag(href="")
|
||||
tg-svg(
|
||||
svg-icon="icon-trash"
|
||||
title="{{'ADMIN.COMMON.TITLE_ACTION_DELETE_VALUE' | translate}}"
|
||||
)
|
||||
|
||||
.row.tag-row.table-main.edition.hidden
|
||||
.color-column(
|
||||
tg-color-selection
|
||||
tg-allow-empty="true"
|
||||
ng-model="tag"
|
||||
)
|
||||
.current-color(
|
||||
ng-style="{background: tag.color}"
|
||||
ng-if="tag.color"
|
||||
)
|
||||
.current-color.empty-color(ng-if="!tag.color")
|
||||
include ../../components/select-color
|
||||
|
||||
.status-name
|
||||
input(
|
||||
name="name"
|
||||
type="text"
|
||||
placeholder="{{'ADMIN.TYPES.PLACEHOLDER_WRITE_NAME' | translate}}",
|
||||
ng-model="tag.name"
|
||||
data-required="true"
|
||||
data-maxlength="255"
|
||||
)
|
||||
|
||||
.options-column
|
||||
a.save.e2e-save(href="")
|
||||
tg-svg(
|
||||
title="{{'COMMON.SAVE' | translate}}"
|
||||
svg-icon="icon-save"
|
||||
)
|
||||
a.cancel(href="")
|
||||
tg-svg(
|
||||
title="{{'COMMON.CANCEL' | translate}}"
|
||||
svg-icon="icon-close"
|
||||
)
|
||||
|
||||
div(ng-show="mixingTags.toTag")
|
||||
div(
|
||||
ng-repeat="tag in projectTags"
|
||||
tg-bind-scope
|
||||
)
|
||||
form(tg-bind-scope)
|
||||
.row.mixing-row.table-main.visualization(class="{{ ctrl.mixingClass(tag) }}")
|
||||
.color-column
|
||||
.current-color(ng-style="{background: tag.color}")
|
||||
|
||||
.status-name
|
||||
span(tg-bo-html="tag.name")
|
||||
|
||||
.mixing-options-column
|
||||
.mixing-help-text(
|
||||
ng-if="mixingTags.toTag === tag.name"
|
||||
translate="ADMIN.PROJECT_VALUES_TAGS.MIXING_HELP_TEXT"
|
||||
)
|
||||
a.mixing-confirm.button-green(
|
||||
href=""
|
||||
ng-if="mixingTags.toTag === tag.name && mixingTags.fromTags.length"
|
||||
translate="ADMIN.PROJECT_VALUES_TAGS.MIXING_MERGE"
|
||||
)
|
||||
a.mixing-cancel.button-gray(
|
||||
href=""
|
||||
ng-if="mixingTags.toTag === tag.name"
|
||||
translate="COMMON.CANCEL"
|
||||
)
|
||||
span.mixing-selected(
|
||||
ng-if="mixingTags.fromTags.indexOf(tag.name) !== -1"
|
||||
)
|
||||
tg-svg(
|
||||
title="{{'ADMIN.PROJECT_VALUES_TAGS.SELECTED' | translate}}"
|
||||
svg-icon="icon-merge"
|
||||
)
|
|
@ -29,9 +29,13 @@ form
|
|||
)
|
||||
|
||||
fieldset
|
||||
.tags-block(
|
||||
tg-lb-tag-line
|
||||
ng-model="issue.tags"
|
||||
tg-tag-line-common.tags-block(
|
||||
ng-if="project"
|
||||
project="project"
|
||||
tags="issue.tags"
|
||||
permissions="add_issue"
|
||||
on-add-tag="addTag(name, color)"
|
||||
on-delete-tag="deleteTag(tag)"
|
||||
)
|
||||
|
||||
fieldset
|
||||
|
|
|
@ -30,9 +30,13 @@ form
|
|||
)
|
||||
|
||||
fieldset
|
||||
div.tags-block(
|
||||
tg-lb-tag-line
|
||||
ng-model="task.tags"
|
||||
tg-tag-line-common.tags-block(
|
||||
ng-if="project"
|
||||
project="project"
|
||||
tags="task.tags"
|
||||
permissions="add_task"
|
||||
on-add-tag="addTag(name, color)"
|
||||
on-delete-tag="deleteTag(tag)"
|
||||
)
|
||||
|
||||
fieldset
|
||||
|
|
|
@ -24,9 +24,13 @@ form
|
|||
)
|
||||
|
||||
fieldset
|
||||
div.tags-block(
|
||||
tg-lb-tag-line
|
||||
ng-model="us.tags"
|
||||
tg-tag-line-common.tags-block(
|
||||
ng-if="project"
|
||||
project="project"
|
||||
tags="us.tags"
|
||||
permissions="add_us"
|
||||
on-add-tag="addTag(name, color)"
|
||||
on-delete-tag="deleteTag(tag)"
|
||||
)
|
||||
|
||||
fieldset
|
||||
|
|
|
@ -64,10 +64,11 @@ div.wrapper(
|
|||
svg-icon="icon-arrow-right"
|
||||
)
|
||||
.subheader
|
||||
.tags-block(
|
||||
tg-tag-line
|
||||
ng-model="issue"
|
||||
required-perm="modify_issue"
|
||||
tg-tag-line.tags-block(
|
||||
ng-if="issue && project"
|
||||
project="project"
|
||||
item="issue"
|
||||
permissions="modify_issue"
|
||||
)
|
||||
tg-created-by-display.ticket-created-by(ng-model="issue")
|
||||
|
||||
|
@ -92,7 +93,7 @@ div.wrapper(
|
|||
project-id="projectId"
|
||||
edit-permission = "modify_issue"
|
||||
)
|
||||
|
||||
|
||||
tg-history-section(
|
||||
ng-if="issue"
|
||||
type="issue"
|
||||
|
|
|
@ -75,7 +75,12 @@ div.wrapper(
|
|||
)
|
||||
tg-svg(svg-icon="icon-arrow-right")
|
||||
.subheader
|
||||
div.tags-block(tg-tag-line, ng-model="task", required-perm="modify_task")
|
||||
tg-tag-line.tags-block(
|
||||
ng-if="task && project"
|
||||
project="project"
|
||||
item="task"
|
||||
permissions="modify_task"
|
||||
)
|
||||
tg-created-by-display.ticket-created-by(ng-model="task")
|
||||
|
||||
section.duty-content(tg-editable-description, tg-editable-wysiwyg, ng-model="task", required-perm="modify_task")
|
||||
|
@ -94,7 +99,7 @@ div.wrapper(
|
|||
project-id="projectId"
|
||||
edit-permission = "modify_task"
|
||||
)
|
||||
|
||||
|
||||
tg-history-section(
|
||||
ng-if="task"
|
||||
type="task"
|
||||
|
|
|
@ -68,7 +68,12 @@ div.wrapper(
|
|||
)
|
||||
tg-svg(svg-icon="icon-arrow-right")
|
||||
.subheader
|
||||
.tags-block(tg-tag-line, ng-model="us", required-perm="modify_us")
|
||||
tg-tag-line.tags-block(
|
||||
ng-if="us && project"
|
||||
project="project"
|
||||
item="us"
|
||||
permissions="modify_us"
|
||||
)
|
||||
tg-created-by-display.ticket-created-by(ng-model="us")
|
||||
|
||||
section.duty-content(tg-editable-description, tg-editable-wysiwyg, ng-model="us", required-perm="modify_us")
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
height: 35px;
|
||||
width: 35px;
|
||||
}
|
||||
.empty-color {
|
||||
@include empty-color(33);
|
||||
}
|
||||
ul {
|
||||
float: left;
|
||||
margin-bottom: 1rem;
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
.tag {
|
||||
@include font-type(light);
|
||||
@include font-size(small);
|
||||
background: $mass-white;
|
||||
border-radius: 0 5px 5px 0;
|
||||
color: $grayer;
|
||||
display: inline-block;
|
||||
margin: 0 .5rem .5rem 0;
|
||||
padding: .5rem;
|
||||
text-align: center;
|
||||
.icon-delete {
|
||||
color: $gray-light;
|
||||
margin-left: 1rem;
|
||||
&:hover {
|
||||
color: $red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ui-autocomplete {
|
||||
background: $white;
|
||||
border: 1px solid $gray-light;
|
||||
z-index: 99910;
|
||||
.ui-state-focus {
|
||||
background: $primary-light;
|
||||
}
|
||||
li {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-helper-hidden-accessible {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tags-block {
|
||||
align-content: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
.tags-container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.add-tag-input {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
width: 250px;
|
||||
.icon-save {
|
||||
margin-top: .5rem;
|
||||
}
|
||||
}
|
||||
input {
|
||||
margin-right: .25rem;
|
||||
padding: .4rem;
|
||||
width: 14rem;
|
||||
}
|
||||
.save {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin-left: .5rem;
|
||||
}
|
||||
.icon-save {
|
||||
@include svg-size();
|
||||
fill: $grayer;
|
||||
&:hover {
|
||||
fill: $primary;
|
||||
transition: .2s linear;
|
||||
}
|
||||
}
|
||||
.loading-spinner {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
.tag {
|
||||
@include font-size(small);
|
||||
margin: 0 .5rem .5rem 0;
|
||||
padding: .5rem;
|
||||
}
|
||||
.icon-close {
|
||||
@include svg-size(.7rem);
|
||||
fill: $gray-light;
|
||||
margin-left: .25rem;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
fill: $red;
|
||||
}
|
||||
}
|
||||
.add-tag {
|
||||
color: $gray-light;
|
||||
display: inline-block;
|
||||
&:hover {
|
||||
color: $primary-light;
|
||||
}
|
||||
}
|
||||
.icon-add {
|
||||
@include svg-size(.9rem);
|
||||
margin-right: .25rem;
|
||||
margin-top: .5rem;
|
||||
}
|
||||
.add-tag-text {
|
||||
@include font-size(small);
|
||||
}
|
||||
.remove-tag {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
@function sqrt($r) {
|
||||
$x0: 1;
|
||||
$x1: $x0;
|
||||
|
||||
@for $i from 1 through 10 {
|
||||
$x1: $x0 - ($x0 * $x0 - abs($r)) / (2 * $x0);
|
||||
$x0: $x1;
|
||||
}
|
||||
|
||||
@return round($x1);
|
||||
}
|
||||
|
||||
@mixin empty-color($width) {
|
||||
background: $mass-white;
|
||||
border: 1px solid $whitish;
|
||||
position: relative;
|
||||
&:after {
|
||||
content: "";
|
||||
width: 2px;
|
||||
height: #{sqrt(2*$width*$width)}px;
|
||||
background: #ff8282;
|
||||
transform: rotate(-45deg);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform-origin: top;
|
||||
}
|
||||
&:before {
|
||||
content: "";
|
||||
width: 2px;
|
||||
height: #{sqrt(2*$width*$width)}px;
|
||||
background: #ff8282;
|
||||
transform: rotate(45deg);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
transform-origin: top;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,15 @@
|
|||
@mixin popover($width, $top: '', $left: '', $bottom: '', $right: '', $arrow-width: 0, $arrow-top: '', $arrow-left: '', $arrow-bottom: '', $arrow-height: 15px) {
|
||||
@mixin popover(
|
||||
$width,
|
||||
$top: '',
|
||||
$left: '',
|
||||
$bottom: '',
|
||||
$right: '',
|
||||
$arrow-width: 0,
|
||||
$arrow-top: '',
|
||||
$arrow-left: '',
|
||||
$arrow-bottom: '',
|
||||
$arrow-height: 15px
|
||||
) {
|
||||
@include font-type(light);
|
||||
@include font-size(small);
|
||||
background: $blackish;
|
||||
|
@ -14,6 +25,7 @@
|
|||
top: #{$top};
|
||||
width: $width;
|
||||
z-index: 99;
|
||||
text-align: center;
|
||||
a {
|
||||
@include font-size(small);
|
||||
border-bottom: 1px solid $grayer;
|
||||
|
|
|
@ -21,3 +21,4 @@
|
|||
@import '../dependencies/mixins/slide';
|
||||
@import '../dependencies/mixins/svg';
|
||||
@import '../dependencies/mixins/track-buttons';
|
||||
@import '../dependencies/mixins/empty-color';
|
||||
|
|
|
@ -1,20 +1,109 @@
|
|||
.table-tags-editor {
|
||||
input[type="text"] {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
border-bottom: 1px solid transparent;
|
||||
box-shadow: none;
|
||||
transition: border-bottom .2s linear;
|
||||
&:focus {
|
||||
border-bottom: 1px solid $gray;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
.color-filter {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
padding: 0 10px;
|
||||
.add-tag-container {
|
||||
align-items: center;
|
||||
background: $mass-white;
|
||||
display: flex;
|
||||
margin: .5rem 0;
|
||||
padding: 1rem;
|
||||
.color-column {
|
||||
cursor: pointer;
|
||||
flex-basis: 60px;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
}
|
||||
.tag-name {
|
||||
flex-basis: 80%;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
.options-column {
|
||||
display: flex;
|
||||
}
|
||||
.current-color {
|
||||
&.empty-color {
|
||||
@include empty-color(38);
|
||||
}
|
||||
}
|
||||
input[type="text"] {
|
||||
background: $white;
|
||||
}
|
||||
.icon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.tags-table {
|
||||
.table-tags-editor {
|
||||
input[type="text"] {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
border-bottom: 1px solid transparent;
|
||||
box-shadow: none;
|
||||
transition: border-bottom .2s linear;
|
||||
&:focus {
|
||||
border-bottom: 1px solid $gray;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
.row {
|
||||
&.header-tag-row {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
}
|
||||
.color-filter {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
padding: 0 10px;
|
||||
position: relative;
|
||||
input {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.row {
|
||||
&.tag-row {
|
||||
margin: .3rem 0;
|
||||
padding: .7rem;
|
||||
&:hover {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
.mix-tags {
|
||||
position: relative;
|
||||
.popover {
|
||||
@include popover(120px, '', '', 2rem, -85%, 1rem, '', 50%, -5px);
|
||||
}
|
||||
&:hover {
|
||||
.popover {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
.mixing-options-column {
|
||||
text-align: right;
|
||||
}
|
||||
.mixing-tags-from,
|
||||
.mixing-tags-to {
|
||||
background: lighten(rgba($primary-light, .2), 30%);
|
||||
}
|
||||
.mixing-confirm {
|
||||
margin: 0 .5rem;
|
||||
}
|
||||
.mixing-help-text {
|
||||
@include font-size(xsmall);
|
||||
color: $primary-dark;
|
||||
display: inline;
|
||||
padding-right: .5rem;
|
||||
text-align: center;
|
||||
@include breakpoint(laptop) {
|
||||
display: block;
|
||||
padding: .5rem;
|
||||
}
|
||||
}
|
||||
.current-color {
|
||||
&.empty-color {
|
||||
@include empty-color(38);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
width: 100%;
|
||||
}
|
||||
}
|
||||
.admin-attributes-section-wrapper-empty {
|
||||
.admin-tags-section-wrapper-empty {
|
||||
color: $gray-light;
|
||||
padding: 10vh 0 0;
|
||||
text-align: center;
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
}
|
||||
.row {
|
||||
align-items: center;
|
||||
border-bottom: 1px solid $whitish;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
|
@ -27,15 +26,11 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
&.edition,
|
||||
&.new-value {
|
||||
padding-left: 50px;
|
||||
}
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
&:hover {
|
||||
background: lighten($primary, 60%);
|
||||
background: lighten(rgba($primary-light, .2), 30%);
|
||||
cursor: move;
|
||||
transition: background .2s ease-in;
|
||||
.icon {
|
||||
|
@ -110,16 +105,10 @@
|
|||
}
|
||||
.options-column {
|
||||
a {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
form {
|
||||
&:last-child {
|
||||
.row {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.row-edit {
|
||||
.options-column {
|
||||
|
@ -133,13 +122,14 @@
|
|||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
cursor: pointer;
|
||||
fill: $gray-light;
|
||||
margin-right: 1rem;
|
||||
opacity: 0;
|
||||
&:hover {
|
||||
fill: $primary;
|
||||
fill: $primary-light;
|
||||
transition: all .2s ease-in;
|
||||
}
|
||||
&.icon-check {
|
||||
|
@ -147,6 +137,10 @@
|
|||
fill: $primary;
|
||||
opacity: 1;
|
||||
}
|
||||
&.icon-merge {
|
||||
cursor: default;
|
||||
opacity: 1;
|
||||
}
|
||||
&.icon-search {
|
||||
cursor: none;
|
||||
fill: $primary;
|
||||
|
@ -156,9 +150,7 @@
|
|||
cursor: move;
|
||||
}
|
||||
&.icon-trash {
|
||||
&:hover {
|
||||
fill: $red-light;
|
||||
}
|
||||
fill: $red-light;
|
||||
}
|
||||
}
|
||||
.gu-mirror {
|
||||
|
|
|
@ -429,6 +429,7 @@
|
|||
fill="#fff"
|
||||
d="M511.998 107.939c-222.856 0-404.061 181.204-404.061 404.061s181.205 404.061 404.061 404.061c222.856 0 404.061-181.203 404.061-404.061s-181.205-404.061-404.061-404.061zM511.998 158.447c88.671 0 169.621 32.484 231.616 86.222l-498.947 498.948c-53.74-61.998-86.223-142.945-86.223-231.617 0-195.561 157.992-353.553 353.553-353.553zM779.328 280.383c53.74 61.998 86.223 142.945 86.223 231.617 0 195.561-157.992 353.553-353.553 353.553-88.671 0-169.617-32.484-231.616-86.222l498.947-498.948z"></path>
|
||||
</symbol>
|
||||
<<<<<<< b929b5ecdaefcb0f2430ea5c8e41ce8fdcdbe761
|
||||
<symbol id="icon-add-user" viewBox="0 0 470 350">
|
||||
<title>Add user</title>
|
||||
<path
|
||||
|
@ -437,6 +438,13 @@
|
|||
<symbol id="icon-view-more" viewBox="0 0 66.3 16">
|
||||
<title>View more</title>
|
||||
<path d="M16 8a8 8 0 0 1-8 8 8 8 0 0 1-8-8 8 8 0 0 1 8-8 8 8 0 0 1 8 8zM41.2 8a8 8 0 0 1-8 8 8 8 0 0 1-8-8 8 8 0 0 1 8-8 8 8 0 0 1 8 8zM66.3 8a8 8 0 0 1-8 8 8 8 0 0 1-8-8 8 8 0 0 1 8-8 8 8 0 0 1 8 8z"/>
|
||||
<symbol id="icon-merge" viewBox="0 0 400 400">
|
||||
<title>Merge</title>
|
||||
<path d="M201.2 5.8l-100 100.7h81.4v126L45 371l23.3 22.7L216.8 243V106.4h85zm51 269.8l-24 24.2 102.6 94.4 24-24.2z" />
|
||||
</symbol>
|
||||
<symbol id="icon-fill" viewBox="0 0 400 400">
|
||||
<title>Fill</title>
|
||||
<path d="M106.3 1.4l-9.8 9.8L171 85.7 20.8 236l160 160 160-160-160-160-74.5-74.6zm74.5 94.2L321 236l-1 .8H41.6l-1-1L180.8 95.7zM341.2 291s-38 41.2-38 66.5c0 21 17 38 38 38s38-17 38-38c0-25.3-38-66.5-38-66.5z"/>
|
||||
</symbol>
|
||||
</defs>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
@ -19,8 +19,21 @@ helper.getCreateEditUsLightbox = function() {
|
|||
subject: function() {
|
||||
return el.$('input[name="subject"]');
|
||||
},
|
||||
tags: function() {
|
||||
return el.$('.tag-input');
|
||||
tags: async function() {
|
||||
$('.e2e-show-tag-input').click();
|
||||
$('.e2e-open-color-selector').click();
|
||||
|
||||
$$('.e2e-color-dropdown li').get(1).click();
|
||||
$('.e2e-add-tag-input')
|
||||
.sendKeys('xxxyy')
|
||||
.sendKeys(protractor.Key.ENTER);
|
||||
|
||||
$$('.e2e-delete-tag').last().click();
|
||||
|
||||
$('.e2e-add-tag-input')
|
||||
.sendKeys('a')
|
||||
.sendKeys(protractor.Key.ARROW_DOWN)
|
||||
.sendKeys(protractor.Key.ENTER);
|
||||
},
|
||||
description: function() {
|
||||
return el.$('textarea[name="description"]');
|
||||
|
|
|
@ -63,3 +63,20 @@ helper.lightboxAttachment = async function() {
|
|||
|
||||
expect(countAttachments + 1).to.be.equal(newCountAttachments);
|
||||
};
|
||||
|
||||
helper.tags = function() {
|
||||
$('.e2e-show-tag-input').click();
|
||||
$('.e2e-open-color-selector').click();
|
||||
|
||||
$$('.e2e-color-dropdown li').get(1).click();
|
||||
$('.e2e-add-tag-input')
|
||||
.sendKeys('xxxyy')
|
||||
.sendKeys(protractor.Key.ENTER);
|
||||
|
||||
$$('.e2e-delete-tag').last().click();
|
||||
|
||||
$('.e2e-add-tag-input')
|
||||
.sendKeys('a')
|
||||
.sendKeys(protractor.Key.ARROW_DOWN)
|
||||
.sendKeys(protractor.Key.ENTER);
|
||||
}
|
||||
|
|
|
@ -57,34 +57,35 @@ helper.description = function(){
|
|||
|
||||
|
||||
helper.tags = function() {
|
||||
let el = $('div[tg-tag-line]');
|
||||
let el = $('tg-tag-line-common');
|
||||
|
||||
let obj = {
|
||||
el:el,
|
||||
|
||||
clearTags: async function() {
|
||||
let tags = await el.$$('.icon-delete');
|
||||
let tags = await el.$$('.e2e-delete-tag');
|
||||
let totalTags = tags.length;
|
||||
let htmlChanges = null;
|
||||
while (totalTags > 0) {
|
||||
htmlChanges = await utils.common.outerHtmlChanges(el.$(".tags-container"));
|
||||
await el.$$('.icon-delete').first().click();
|
||||
await el.$$('.e2e-delete-tag').first().click();
|
||||
totalTags --;
|
||||
await htmlChanges();
|
||||
}
|
||||
},
|
||||
|
||||
getTagsText: function() {
|
||||
return el.$$('.tag-name').getText();
|
||||
return el.$$('tg-tag span').getText();
|
||||
},
|
||||
|
||||
addTags: async function(tags) {
|
||||
let htmlChanges = null
|
||||
|
||||
el.$('.add-tag').click();
|
||||
$('.e2e-show-tag-input').click();
|
||||
|
||||
for (let tag of tags){
|
||||
htmlChanges = await utils.common.outerHtmlChanges(el.$(".tags-container"));
|
||||
el.$('.tag-input').sendKeys(tag);
|
||||
el.$('.e2e-add-tag-input').sendKeys(tag);
|
||||
await browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
||||
await htmlChanges();
|
||||
}
|
||||
|
|
|
@ -49,11 +49,7 @@ describe('backlog', function() {
|
|||
createUSLightbox.status(2).click();
|
||||
|
||||
// tags
|
||||
createUSLightbox.tags().sendKeys('aaa');
|
||||
browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
||||
|
||||
createUSLightbox.tags().sendKeys('bbb');
|
||||
browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
||||
commonHelper.tags();
|
||||
|
||||
// description
|
||||
createUSLightbox.description().sendKeys('test test');
|
||||
|
@ -245,7 +241,7 @@ describe('backlog', function() {
|
|||
expect(elementRef1).to.be.equal(draggedRefs[1]);
|
||||
});
|
||||
|
||||
it.skip('drag multiple us to milestone', async function() {
|
||||
it('drag multiple us to milestone', async function() {
|
||||
let sprint = backlogHelper.sprints().get(0);
|
||||
let initUssSprintCount = await backlogHelper.getSprintUsertories(sprint).count();
|
||||
|
||||
|
|
|
@ -38,11 +38,7 @@ describe('issues list', function() {
|
|||
createIssueLightbox.subject().sendKeys('subject');
|
||||
|
||||
// tags
|
||||
await createIssueLightbox.tags().sendKeys('aaa');
|
||||
browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
||||
|
||||
await createIssueLightbox.tags().sendKeys('bbb');
|
||||
browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
||||
commonHelper.tags();
|
||||
});
|
||||
|
||||
it('upload attachments', commonHelper.lightboxAttachment);
|
||||
|
|
|
@ -74,11 +74,7 @@ describe('kanban', function() {
|
|||
expect(totalPoints).to.be.equal('4');
|
||||
|
||||
// tags
|
||||
createUSLightbox.tags().sendKeys('www');
|
||||
browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
||||
|
||||
createUSLightbox.tags().sendKeys('xxx');
|
||||
browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
||||
commonHelper.tags();
|
||||
|
||||
// description
|
||||
createUSLightbox.description().sendKeys(formFields.description);
|
||||
|
|
|
@ -66,11 +66,7 @@ describe('taskboard', function() {
|
|||
createTaskLightbox.subject().sendKeys(formFields.subject);
|
||||
createTaskLightbox.description().sendKeys(formFields.description);
|
||||
|
||||
createTaskLightbox.tags().sendKeys('aaa');
|
||||
browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
||||
|
||||
createTaskLightbox.tags().sendKeys('bbb');
|
||||
browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
||||
commonHelper.tags();
|
||||
|
||||
await createTaskLightbox.blocked().click();
|
||||
await createTaskLightbox.blockedNote().sendKeys(formFields.blockedNote);
|
||||
|
|
Loading…
Reference in New Issue