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
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
.#*
|
.#*
|
||||||
tags
|
/tags
|
||||||
tmp/
|
tmp/
|
||||||
app/config/main.coffee
|
app/config/main.coffee
|
||||||
scss-lint.log
|
scss-lint.log
|
||||||
|
|
|
@ -62,6 +62,7 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
@scope.project = {}
|
@scope.project = {}
|
||||||
|
|
||||||
promise = @.loadInitialData()
|
promise = @.loadInitialData()
|
||||||
|
@scope.projectTags = []
|
||||||
|
|
||||||
promise.then =>
|
promise.then =>
|
||||||
sectionName = @translate.instant( @scope.sectionName)
|
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.issueTypesList = _.sortBy(project.issue_types, "order")
|
||||||
@scope.issueStatusList = _.sortBy(project.issue_statuses, "order")
|
@scope.issueStatusList = _.sortBy(project.issue_statuses, "order")
|
||||||
@scope.$emit('project:loaded', project)
|
@scope.$emit('project:loaded', project)
|
||||||
|
|
||||||
|
|
||||||
|
@scope.projectTags = _.map @scope.project.tags, (it) =>
|
||||||
|
return [it, @scope.project.tags_colors[it]]
|
||||||
|
|
||||||
return project
|
return project
|
||||||
|
|
||||||
loadInitialData: ->
|
loadInitialData: ->
|
||||||
|
@ -107,6 +113,21 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
openDeleteLightbox: ->
|
openDeleteLightbox: ->
|
||||||
@rootscope.$broadcast("deletelightbox:new", @scope.project)
|
@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)
|
module.controller("ProjectProfileController", ProjectProfileController)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -331,6 +331,10 @@ ColorSelectionDirective = () ->
|
||||||
## Color selection Link
|
## Color selection Link
|
||||||
|
|
||||||
link = ($scope, $el, $attrs, $model) ->
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
$scope.allowEmpty = false
|
||||||
|
if $attrs.tgAllowEmpty
|
||||||
|
$scope.allowEmpty = true
|
||||||
|
|
||||||
$ctrl = $el.controller()
|
$ctrl = $el.controller()
|
||||||
|
|
||||||
$scope.$watch $attrs.ngModel, (element) ->
|
$scope.$watch $attrs.ngModel, (element) ->
|
||||||
|
@ -696,59 +700,268 @@ module.directive("tgProjectCustomAttributes", ["$log", "$tgConfirm", "animationF
|
||||||
## Tags Controller
|
## Tags Controller
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
class ProjectTagsController extends mixOf(taiga.Controller, taiga.PageMixin)
|
class ProjectTagsController extends taiga.Controller
|
||||||
@.$inject = [
|
@.$inject = [
|
||||||
"$scope",
|
"$scope",
|
||||||
"$rootScope",
|
"$rootScope",
|
||||||
"$tgRepo",
|
"$tgRepo",
|
||||||
"tgAppMetaService",
|
"$tgConfirm",
|
||||||
"$translate"
|
"$tgResources",
|
||||||
|
"$tgModel",
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @appMetaService, @translate) ->
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @model) ->
|
||||||
@.loading = true
|
@.loading = true
|
||||||
@rootscope.$on "project:loaded", =>
|
@rootscope.$on("project:loaded", @.loadTags)
|
||||||
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)
|
|
||||||
|
|
||||||
|
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
|
@.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) ->
|
filterAndSortTags: =>
|
||||||
tags_colors = angular.copy(@scope.project.tags_colors)
|
@scope.projectTags = _.filter(
|
||||||
tags_colors[tag.name] = tag.color
|
_.sortBy(@scope.projectTagsAll, "name"),
|
||||||
@scope.project.tags_colors = tags_colors
|
(tag) => tag.name.indexOf(@scope.tagsFilter.name) != -1
|
||||||
return @repo.save(@scope.project)
|
)
|
||||||
|
|
||||||
|
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)
|
module.controller("ProjectTagsController", ProjectTagsController)
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Tags Directive
|
## Tags directive
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
ProjectTagDirective = () ->
|
ProjectTagsDirective = ($log, $repo, $confirm, $location, animationFrame, $translate, $rootscope) ->
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
$el.color = $scope.tag.color
|
$window = $(window)
|
||||||
$ctrl = $el.controller()
|
$ctrl = $el.controller()
|
||||||
|
valueType = $attrs.type
|
||||||
|
objName = $attrs.objname
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
initializeNewValue = ->
|
||||||
$el.off()
|
$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()
|
||||||
|
|
||||||
$scope.$watch "tag.color", (newColor) =>
|
|
||||||
if $el.color != newColor
|
|
||||||
promise = $ctrl.updateTag($scope.tag)
|
|
||||||
promise.then null, (data) ->
|
promise.then null, (data) ->
|
||||||
form.setErrors(data)
|
form.setErrors(data)
|
||||||
|
|
||||||
$el.color = newColor
|
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()
|
||||||
|
|
||||||
return {link:link}
|
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
|
timeout = @.taiga.timeout
|
||||||
debounce = @.taiga.debounce
|
debounce = @.taiga.debounce
|
||||||
sizeFormat = @.taiga.sizeFormat
|
sizeFormat = @.taiga.sizeFormat
|
||||||
|
trim = @.taiga.trim
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Common Lightbox Services
|
## Common Lightbox Services
|
||||||
|
@ -295,6 +296,42 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService,
|
||||||
if attachment.get("id")
|
if attachment.get("id")
|
||||||
attachmentsToDelete = attachmentsToDelete.push(attachment)
|
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) ->
|
$scope.$on "usform:new", (ctx, projectId, status, statusList) ->
|
||||||
form.reset() if form
|
form.reset() if form
|
||||||
$scope.isNew = true
|
$scope.isNew = true
|
||||||
|
|
|
@ -26,6 +26,7 @@ taiga = @.taiga
|
||||||
trim = @.taiga.trim
|
trim = @.taiga.trim
|
||||||
bindOnce = @.taiga.bindOnce
|
bindOnce = @.taiga.bindOnce
|
||||||
|
|
||||||
|
|
||||||
module = angular.module("taigaCommon")
|
module = angular.module("taigaCommon")
|
||||||
|
|
||||||
# Directive that parses/format tags inputfield.
|
# Directive that parses/format tags inputfield.
|
||||||
|
@ -61,28 +62,38 @@ ColorizeTagsDirective = ->
|
||||||
templates = {
|
templates = {
|
||||||
backlog: _.template("""
|
backlog: _.template("""
|
||||||
<% _.each(tags, function(tag) { %>
|
<% _.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("""
|
kanban: _.template("""
|
||||||
<% _.each(tags, function(tag) { %>
|
<% _.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("""
|
taskboard: _.template("""
|
||||||
<% _.each(tags, function(tag) { %>
|
<% _.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) ->
|
link = ($scope, $el, $attrs, $ctrl) ->
|
||||||
render = (srcTags) ->
|
render = (tags) ->
|
||||||
template = templates[$attrs.tgColorizeTagsType]
|
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})
|
html = template({tags: tags})
|
||||||
$el.html(html)
|
$el.html(html)
|
||||||
|
@ -111,15 +122,18 @@ LbTagLineDirective = ($rs, $template, $compile) ->
|
||||||
autocomplete = null
|
autocomplete = null
|
||||||
|
|
||||||
link = ($scope, $el, $attrs, $model) ->
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
withoutColors = _.has($attrs, "withoutColors")
|
||||||
|
|
||||||
## Render
|
## Render
|
||||||
renderTags = (tags, tagsColors = []) ->
|
renderTags = (tags, tagsColors = []) ->
|
||||||
ctx = {
|
color = if not withoutColors then tagsColors[t] else null
|
||||||
tags: _.map(tags, (t) -> {name: t, color: tagsColors[t]})
|
|
||||||
}
|
|
||||||
|
|
||||||
_.map ctx.tags, (tag) =>
|
ctx = {
|
||||||
if tag.color
|
tags: _.map(tags, (t) -> {
|
||||||
tag.style = "border-left: 5px solid #{tag.color}"
|
name: t,
|
||||||
|
style: if color then "border-left: 5px solid #{color}" else ""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
html = $compile(templateTags(ctx))($scope)
|
html = $compile(templateTags(ctx))($scope)
|
||||||
$el.find(".tags-container").html(html)
|
$el.find(".tags-container").html(html)
|
||||||
|
@ -196,7 +210,7 @@ LbTagLineDirective = ($rs, $template, $compile) ->
|
||||||
|
|
||||||
autocomplete = new Awesomplete(input[0], {
|
autocomplete = new Awesomplete(input[0], {
|
||||||
list: _.keys(project.tags_colors)
|
list: _.keys(project.tags_colors)
|
||||||
});
|
})
|
||||||
|
|
||||||
input.on "awesomplete-selectcomplete", () ->
|
input.on "awesomplete-selectcomplete", () ->
|
||||||
addValue(input.val())
|
addValue(input.val())
|
||||||
|
@ -216,204 +230,3 @@ LbTagLineDirective = ($rs, $template, $compile) ->
|
||||||
}
|
}
|
||||||
|
|
||||||
module.directive("tgLbTagLine", ["$tgResources", "$tgTemplate", "$compile", LbTagLineDirective])
|
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
|
taiga = @.taiga
|
||||||
bindOnce = @.taiga.bindOnce
|
bindOnce = @.taiga.bindOnce
|
||||||
debounce = @.taiga.debounce
|
debounce = @.taiga.debounce
|
||||||
|
trim = @.taiga.trim
|
||||||
|
|
||||||
module = angular.module("taigaIssues")
|
module = angular.module("taigaIssues")
|
||||||
|
|
||||||
|
@ -76,6 +77,42 @@ CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService, $loading,
|
||||||
$scope.addAttachment = (attachment) ->
|
$scope.addAttachment = (attachment) ->
|
||||||
attachmentsToAdd = attachmentsToAdd.push(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) =>
|
submit = debounce 2000, (event) =>
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
|
@ -101,7 +138,6 @@ CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService, $loading,
|
||||||
currentLoading.finish()
|
currentLoading.finish()
|
||||||
$confirm.notify("error")
|
$confirm.notify("error")
|
||||||
|
|
||||||
|
|
||||||
submitButton = $el.find(".submit-button")
|
submitButton = $el.find(".submit-button")
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
|
|
|
@ -83,6 +83,34 @@ resourceProvider = ($config, $repo, $http, $urls, $auth, $q, $translate) ->
|
||||||
service.tagsColors = (projectId) ->
|
service.tagsColors = (projectId) ->
|
||||||
return $repo.queryOne("projects", "#{projectId}/tags_colors")
|
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) ->
|
service.export = (projectId) ->
|
||||||
url = "#{$urls.resolve("exporter")}/#{projectId}"
|
url = "#{$urls.resolve("exporter")}/#{projectId}"
|
||||||
return $http.get(url)
|
return $http.get(url)
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
taiga = @.taiga
|
taiga = @.taiga
|
||||||
bindOnce = @.taiga.bindOnce
|
bindOnce = @.taiga.bindOnce
|
||||||
debounce = @.taiga.debounce
|
debounce = @.taiga.debounce
|
||||||
|
trim = @.taiga.trim
|
||||||
|
|
||||||
CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxService, $translate, $q, attachmentsService) ->
|
CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxService, $translate, $q, attachmentsService) ->
|
||||||
link = ($scope, $el, attrs) ->
|
link = ($scope, $el, attrs) ->
|
||||||
|
@ -56,6 +57,45 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxSer
|
||||||
|
|
||||||
return $q.all(promises)
|
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.$on "taskform:new", (ctx, sprintId, usId) ->
|
||||||
$scope.task = {
|
$scope.task = {
|
||||||
project: $scope.projectId
|
project: $scope.projectId
|
||||||
|
|
|
@ -28,10 +28,12 @@ addClass = (el, className) ->
|
||||||
else
|
else
|
||||||
el.className += ' ' + className
|
el.className += ' ' + className
|
||||||
|
|
||||||
|
|
||||||
nl2br = (str) =>
|
nl2br = (str) =>
|
||||||
breakTag = '<br />'
|
breakTag = '<br />'
|
||||||
return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + breakTag + '$2')
|
return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + breakTag + '$2')
|
||||||
|
|
||||||
|
|
||||||
bindMethods = (object) =>
|
bindMethods = (object) =>
|
||||||
dependencies = _.keys(object)
|
dependencies = _.keys(object)
|
||||||
|
|
||||||
|
@ -43,6 +45,7 @@ bindMethods = (object) =>
|
||||||
|
|
||||||
_.bindAll(object, methods)
|
_.bindAll(object, methods)
|
||||||
|
|
||||||
|
|
||||||
bindOnce = (scope, attr, continuation) =>
|
bindOnce = (scope, attr, continuation) =>
|
||||||
val = scope.$eval(attr)
|
val = scope.$eval(attr)
|
||||||
if val != undefined
|
if val != undefined
|
||||||
|
@ -75,6 +78,7 @@ slugify = (data) ->
|
||||||
.replace(/[^\w\-]+/g, '')
|
.replace(/[^\w\-]+/g, '')
|
||||||
.replace(/\-\-+/g, '-')
|
.replace(/\-\-+/g, '-')
|
||||||
|
|
||||||
|
|
||||||
unslugify = (data) ->
|
unslugify = (data) ->
|
||||||
if data
|
if data
|
||||||
return _.capitalize(data.replace(/-/g, ' '))
|
return _.capitalize(data.replace(/-/g, ' '))
|
||||||
|
@ -165,6 +169,7 @@ sizeFormat = (input, precision=1) ->
|
||||||
size = (input / Math.pow(1024, number)).toFixed(precision)
|
size = (input / Math.pow(1024, number)).toFixed(precision)
|
||||||
return "#{size} #{units[number]}"
|
return "#{size} #{units[number]}"
|
||||||
|
|
||||||
|
|
||||||
stripTags = (str, exception) ->
|
stripTags = (str, exception) ->
|
||||||
if exception
|
if exception
|
||||||
pattern = new RegExp('<(?!' + exception + '\s*\/?)[^>]+>', 'gi')
|
pattern = new RegExp('<(?!' + exception + '\s*\/?)[^>]+>', 'gi')
|
||||||
|
@ -172,6 +177,7 @@ stripTags = (str, exception) ->
|
||||||
else
|
else
|
||||||
return String(str).replace(/<\/?[^>]+>/g, '')
|
return String(str).replace(/<\/?[^>]+>/g, '')
|
||||||
|
|
||||||
|
|
||||||
replaceTags = (str, tags, replace) ->
|
replaceTags = (str, tags, replace) ->
|
||||||
# open tag
|
# open tag
|
||||||
pattern = new RegExp('<(' + tags + ')>', 'gi')
|
pattern = new RegExp('<(' + tags + ')>', 'gi')
|
||||||
|
@ -183,6 +189,7 @@ replaceTags = (str, tags, replace) ->
|
||||||
|
|
||||||
return str
|
return str
|
||||||
|
|
||||||
|
|
||||||
defineImmutableProperty = (obj, name, fn) =>
|
defineImmutableProperty = (obj, name, fn) =>
|
||||||
Object.defineProperty obj, name, {
|
Object.defineProperty obj, name, {
|
||||||
get: () =>
|
get: () =>
|
||||||
|
@ -197,6 +204,7 @@ defineImmutableProperty = (obj, name, fn) =>
|
||||||
return fn_result
|
return fn_result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_.mixin
|
_.mixin
|
||||||
removeKeys: (obj, keys) ->
|
removeKeys: (obj, keys) ->
|
||||||
_.chain([keys]).flatten().reduce(
|
_.chain([keys]).flatten().reduce(
|
||||||
|
@ -211,13 +219,14 @@ _.mixin
|
||||||
, [ [] ])
|
, [ [] ])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
isImage = (name) ->
|
isImage = (name) ->
|
||||||
return name.match(/\.(jpe?g|png|gif|gifv|webm)/i) != null
|
return name.match(/\.(jpe?g|png|gif|gifv|webm)/i) != null
|
||||||
|
|
||||||
|
|
||||||
isPdf = (name) ->
|
isPdf = (name) ->
|
||||||
return name.match(/\.(pdf)/i) != null
|
return name.match(/\.(pdf)/i) != null
|
||||||
|
|
||||||
|
|
||||||
patch = (oldImmutable, newImmutable) ->
|
patch = (oldImmutable, newImmutable) ->
|
||||||
pathObj = {}
|
pathObj = {}
|
||||||
|
|
||||||
|
@ -230,6 +239,7 @@ patch = (oldImmutable, newImmutable) ->
|
||||||
|
|
||||||
return pathObj
|
return pathObj
|
||||||
|
|
||||||
|
|
||||||
taiga = @.taiga
|
taiga = @.taiga
|
||||||
taiga.addClass = addClass
|
taiga.addClass = addClass
|
||||||
taiga.nl2br = nl2br
|
taiga.nl2br = nl2br
|
||||||
|
|
|
@ -426,7 +426,8 @@
|
||||||
"ADMIN": {
|
"ADMIN": {
|
||||||
"COMMON": {
|
"COMMON": {
|
||||||
"TITLE_ACTION_EDIT_VALUE": "Edit value",
|
"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!",
|
"HELP": "Do you need help? Check out our support page!",
|
||||||
"PROJECT_DEFAULT_VALUES": {
|
"PROJECT_DEFAULT_VALUES": {
|
||||||
|
@ -582,9 +583,14 @@
|
||||||
},
|
},
|
||||||
"PROJECT_VALUES_TAGS": {
|
"PROJECT_VALUES_TAGS": {
|
||||||
"TITLE": "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": "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": {
|
"ROLES": {
|
||||||
"PAGE_TITLE": "Roles - {{projectName}}",
|
"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"
|
tg-repeat="project in vm.projects"
|
||||||
ng-class="{'blocked-project': project.get('blocked_code')}"
|
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(
|
.project-card-inner(
|
||||||
href="#"
|
href="#"
|
||||||
tg-nav="project:project=project.get('slug')"
|
tg-nav="project:project=project.get('slug')"
|
||||||
|
|
|
@ -43,11 +43,8 @@ div.wrapper
|
||||||
p.description {{vm.project.get('description')}}
|
p.description {{vm.project.get('description')}}
|
||||||
|
|
||||||
div.single-project-tags.tags-container(ng-if="::vm.project.get('tags').size")
|
div.single-project-tags.tags-container(ng-if="::vm.project.get('tags').size")
|
||||||
span.tag(
|
span.tag(tg-repeat="tag in ::vm.project.get('tags')")
|
||||||
style='border-left: 5px solid {{::tag.get("color")}};',
|
span.tag-name {{::tag}}
|
||||||
tg-repeat="tag in ::vm.project.get('colorized_tags')"
|
|
||||||
)
|
|
||||||
span.tag-name {{::tag.get('name')}}
|
|
||||||
|
|
||||||
div.project-data
|
div.project-data
|
||||||
section.timeline(ng-if="vm.project")
|
section.timeline(ng-if="vm.project")
|
||||||
|
@ -60,7 +57,9 @@ div.wrapper
|
||||||
title="{{'PROJECT.LOOKING_FOR_PEOPLE' | translate}}"
|
title="{{'PROJECT.LOOKING_FOR_PEOPLE' | translate}}"
|
||||||
)
|
)
|
||||||
h3 {{'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}}
|
h2.title {{"PROJECT.SECTION.TEAM" | translate}}
|
||||||
ul.involved-team
|
ul.involved-team
|
||||||
li(tg-repeat="member in vm.members")
|
li(tg-repeat="member in vm.members")
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
taiga = @.taiga
|
taiga = @.taiga
|
||||||
groupBy = @.taiga.groupBy
|
groupBy = @.taiga.groupBy
|
||||||
|
|
||||||
|
|
||||||
class ProjectsService extends taiga.Service
|
class ProjectsService extends taiga.Service
|
||||||
@.$inject = ["tgResources", "$projectUrl", "tgLightboxFactory"]
|
@.$inject = ["tgResources", "$projectUrl", "tgLightboxFactory"]
|
||||||
|
|
||||||
|
@ -42,16 +43,6 @@ class ProjectsService extends taiga.Service
|
||||||
url = @projectUrl.get(project.toJS())
|
url = @projectUrl.get(project.toJS())
|
||||||
|
|
||||||
project = project.set("url", url)
|
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
|
return project
|
||||||
|
|
||||||
|
|
|
@ -129,8 +129,7 @@ describe "tgProjectsService", ->
|
||||||
id: 2,
|
id: 2,
|
||||||
url: 'url-2',
|
url: 'url-2',
|
||||||
tags: ['xx', 'yy', 'aa'],
|
tags: ['xx', 'yy', 'aa'],
|
||||||
tags_colors: {xx: "red", yy: "blue", aa: "white"},
|
tags_colors: {xx: "red", yy: "blue", aa: "white"}
|
||||||
colorized_tags: [{name: 'aa', color: 'white'}, {name: 'xx', color: 'red'}, {name: 'yy', color: 'blue'}]
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -157,8 +156,7 @@ describe "tgProjectsService", ->
|
||||||
id: 2,
|
id: 2,
|
||||||
url: 'url-2',
|
url: 'url-2',
|
||||||
tags: ['xx', 'yy', 'aa'],
|
tags: ['xx', 'yy', 'aa'],
|
||||||
tags_colors: {xx: "red", yy: "blue", aa: "white"},
|
tags_colors: {xx: "red", yy: "blue", aa: "white"}
|
||||||
colorized_tags: [{name: 'aa', color: 'white'}, {name: 'xx', color: 'red'}, {name: 'yy', color: 'blue'}]
|
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
|
@ -72,10 +72,15 @@ div.wrapper(
|
||||||
)
|
)
|
||||||
fieldset
|
fieldset
|
||||||
label(for="tags") {{ 'ADMIN.PROJECT_PROFILE.TAGS' | translate }}
|
label(for="tags") {{ 'ADMIN.PROJECT_PROFILE.TAGS' | translate }}
|
||||||
div.tags-block(
|
|
||||||
ng-if="project.id"
|
tg-tag-line-common.tags-block(
|
||||||
tg-lb-tag-line
|
disable-color-selection
|
||||||
ng-model="project.tags"
|
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")
|
fieldset(ng-if="project.owner.id != user.id")
|
||||||
|
|
|
@ -13,55 +13,20 @@ div.wrapper(
|
||||||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-tags")
|
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-tags")
|
||||||
include ../includes/modules/admin-submenu-project-values
|
include ../includes/modules/admin-submenu-project-values
|
||||||
|
|
||||||
section.main.admin-common.admin-attributes.colors-table
|
section.main.admin-common.admin-attributes.colors-table.tags-table(
|
||||||
include ../includes/components/mainTitle
|
tg-project-tags,
|
||||||
p.admin-subtitle(translate="ADMIN.PROJECT_VALUES_TAGS.SUBTITLE")
|
|
||||||
|
|
||||||
.admin-attributes-section(
|
|
||||||
ng-controller="ProjectTagsController as ctrl"
|
ng-controller="ProjectTagsController as ctrl"
|
||||||
)
|
)
|
||||||
.admin-attributes-section-wrapper-empty(
|
header.header-with-actions
|
||||||
ng-if="!projectTags.length"
|
.title
|
||||||
tg-loading="ctrl.loading"
|
include ../includes/components/mainTitle
|
||||||
)
|
p.admin-subtitle(translate="ADMIN.PROJECT_VALUES_TAGS.SUBTITLE")
|
||||||
p(translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY")
|
.action-buttons
|
||||||
.admin-attributes-section-wrapper(
|
a.button.button-green.show-add-new(
|
||||||
ng-if="projectTags.length"
|
href=""
|
||||||
)
|
title="{{ 'ADMIN.PROJECT_VALUES_TAGS.NEW_TAG'|translate }}"
|
||||||
.table-header.table-tags-editor
|
translate="ADMIN.PROJECT_VALUES_TAGS.NEW_TAG"
|
||||||
.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(
|
.admin-attributes-section
|
||||||
tg-loading="ctrl.loading"
|
include ../includes/modules/admin/project-tags
|
||||||
translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY_SEARCH"
|
|
||||||
ng-if="!(projectTags | filter:tagsFilter).length"
|
|
||||||
)
|
|
||||||
.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 }}
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
<% _.each(tags, function(tag) { %>
|
<% _.each(tags, function(tag) { %>
|
||||||
span(class="tag", style!="<%- tag.style %>")
|
span(
|
||||||
|
class="tag"
|
||||||
|
style!="<%- tag.style %>"
|
||||||
|
)
|
||||||
span.tag-name <%- tag.name %>
|
span.tag-name <%- tag.name %>
|
||||||
a.remove-tag(href="", title="{{'COMMON.TAGS.DELETE' | translate}}")
|
a.remove-tag(href="", title="{{'COMMON.TAGS.DELETE' | translate}}")
|
||||||
tg-svg(svg-icon="icon-close")
|
tg-svg(svg-icon="icon-close")
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<% if (tag.name == deleteTagLoading) { %>
|
<% if (tag.name == deleteTagLoading) { %>
|
||||||
div(tg-loading="true")
|
div(tg-loading="true")
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
span.tag(style!="border-left: 5px solid <%- tag.color %>;")
|
span.tag(style!="border-left: 5px solid <%- tag.style %>;")
|
||||||
span.tag-name <%- tag.name %>
|
span.tag-name <%- tag.name %>
|
||||||
<% if (isEditable) { %>
|
<% if (isEditable) { %>
|
||||||
a.remove-tag(
|
a.remove-tag(
|
||||||
|
|
|
@ -20,7 +20,9 @@ div.popover.select-color
|
||||||
li.color(style="background: #ef2929", data-color="#ef2929")
|
li.color(style="background: #ef2929", data-color="#ef2929")
|
||||||
li.color(style="background: #cc0000", data-color="#cc0000")
|
li.color(style="background: #cc0000", data-color="#cc0000")
|
||||||
li.color(style="background: #a40000", data-color="#a40000")
|
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")
|
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
|
fieldset
|
||||||
.tags-block(
|
tg-tag-line-common.tags-block(
|
||||||
tg-lb-tag-line
|
ng-if="project"
|
||||||
ng-model="issue.tags"
|
project="project"
|
||||||
|
tags="issue.tags"
|
||||||
|
permissions="add_issue"
|
||||||
|
on-add-tag="addTag(name, color)"
|
||||||
|
on-delete-tag="deleteTag(tag)"
|
||||||
)
|
)
|
||||||
|
|
||||||
fieldset
|
fieldset
|
||||||
|
|
|
@ -30,9 +30,13 @@ form
|
||||||
)
|
)
|
||||||
|
|
||||||
fieldset
|
fieldset
|
||||||
div.tags-block(
|
tg-tag-line-common.tags-block(
|
||||||
tg-lb-tag-line
|
ng-if="project"
|
||||||
ng-model="task.tags"
|
project="project"
|
||||||
|
tags="task.tags"
|
||||||
|
permissions="add_task"
|
||||||
|
on-add-tag="addTag(name, color)"
|
||||||
|
on-delete-tag="deleteTag(tag)"
|
||||||
)
|
)
|
||||||
|
|
||||||
fieldset
|
fieldset
|
||||||
|
|
|
@ -24,9 +24,13 @@ form
|
||||||
)
|
)
|
||||||
|
|
||||||
fieldset
|
fieldset
|
||||||
div.tags-block(
|
tg-tag-line-common.tags-block(
|
||||||
tg-lb-tag-line
|
ng-if="project"
|
||||||
ng-model="us.tags"
|
project="project"
|
||||||
|
tags="us.tags"
|
||||||
|
permissions="add_us"
|
||||||
|
on-add-tag="addTag(name, color)"
|
||||||
|
on-delete-tag="deleteTag(tag)"
|
||||||
)
|
)
|
||||||
|
|
||||||
fieldset
|
fieldset
|
||||||
|
|
|
@ -64,10 +64,11 @@ div.wrapper(
|
||||||
svg-icon="icon-arrow-right"
|
svg-icon="icon-arrow-right"
|
||||||
)
|
)
|
||||||
.subheader
|
.subheader
|
||||||
.tags-block(
|
tg-tag-line.tags-block(
|
||||||
tg-tag-line
|
ng-if="issue && project"
|
||||||
ng-model="issue"
|
project="project"
|
||||||
required-perm="modify_issue"
|
item="issue"
|
||||||
|
permissions="modify_issue"
|
||||||
)
|
)
|
||||||
tg-created-by-display.ticket-created-by(ng-model="issue")
|
tg-created-by-display.ticket-created-by(ng-model="issue")
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,12 @@ div.wrapper(
|
||||||
)
|
)
|
||||||
tg-svg(svg-icon="icon-arrow-right")
|
tg-svg(svg-icon="icon-arrow-right")
|
||||||
.subheader
|
.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")
|
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")
|
section.duty-content(tg-editable-description, tg-editable-wysiwyg, ng-model="task", required-perm="modify_task")
|
||||||
|
|
|
@ -68,7 +68,12 @@ div.wrapper(
|
||||||
)
|
)
|
||||||
tg-svg(svg-icon="icon-arrow-right")
|
tg-svg(svg-icon="icon-arrow-right")
|
||||||
.subheader
|
.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")
|
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")
|
section.duty-content(tg-editable-description, tg-editable-wysiwyg, ng-model="us", required-perm="modify_us")
|
||||||
|
|
|
@ -19,6 +19,9 @@
|
||||||
height: 35px;
|
height: 35px;
|
||||||
width: 35px;
|
width: 35px;
|
||||||
}
|
}
|
||||||
|
.empty-color {
|
||||||
|
@include empty-color(33);
|
||||||
|
}
|
||||||
ul {
|
ul {
|
||||||
float: left;
|
float: left;
|
||||||
margin-bottom: 1rem;
|
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-type(light);
|
||||||
@include font-size(small);
|
@include font-size(small);
|
||||||
background: $blackish;
|
background: $blackish;
|
||||||
|
@ -14,6 +25,7 @@
|
||||||
top: #{$top};
|
top: #{$top};
|
||||||
width: $width;
|
width: $width;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
|
text-align: center;
|
||||||
a {
|
a {
|
||||||
@include font-size(small);
|
@include font-size(small);
|
||||||
border-bottom: 1px solid $grayer;
|
border-bottom: 1px solid $grayer;
|
||||||
|
|
|
@ -21,3 +21,4 @@
|
||||||
@import '../dependencies/mixins/slide';
|
@import '../dependencies/mixins/slide';
|
||||||
@import '../dependencies/mixins/svg';
|
@import '../dependencies/mixins/svg';
|
||||||
@import '../dependencies/mixins/track-buttons';
|
@import '../dependencies/mixins/track-buttons';
|
||||||
|
@import '../dependencies/mixins/empty-color';
|
||||||
|
|
|
@ -1,3 +1,37 @@
|
||||||
|
.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 {
|
.table-tags-editor {
|
||||||
input[type="text"] {
|
input[type="text"] {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
@ -10,11 +44,66 @@
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.row {
|
||||||
|
&.header-tag-row {
|
||||||
|
padding-left: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
.color-filter {
|
.color-filter {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
position: relative;
|
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%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.admin-attributes-section-wrapper-empty {
|
.admin-tags-section-wrapper-empty {
|
||||||
color: $gray-light;
|
color: $gray-light;
|
||||||
padding: 10vh 0 0;
|
padding: 10vh 0 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
}
|
}
|
||||||
.row {
|
.row {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-bottom: 1px solid $whitish;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
@ -27,15 +26,11 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.edition,
|
|
||||||
&.new-value {
|
|
||||||
padding-left: 50px;
|
|
||||||
}
|
|
||||||
&.hidden {
|
&.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
background: lighten($primary, 60%);
|
background: lighten(rgba($primary-light, .2), 30%);
|
||||||
cursor: move;
|
cursor: move;
|
||||||
transition: background .2s ease-in;
|
transition: background .2s ease-in;
|
||||||
.icon {
|
.icon {
|
||||||
|
@ -110,16 +105,10 @@
|
||||||
}
|
}
|
||||||
.options-column {
|
.options-column {
|
||||||
a {
|
a {
|
||||||
|
cursor: pointer;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
form {
|
|
||||||
&:last-child {
|
|
||||||
.row {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-edit {
|
.row-edit {
|
||||||
.options-column {
|
.options-column {
|
||||||
|
@ -133,13 +122,14 @@
|
||||||
height: 40px;
|
height: 40px;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
fill: $gray-light;
|
fill: $gray-light;
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
&:hover {
|
&:hover {
|
||||||
fill: $primary;
|
fill: $primary-light;
|
||||||
transition: all .2s ease-in;
|
transition: all .2s ease-in;
|
||||||
}
|
}
|
||||||
&.icon-check {
|
&.icon-check {
|
||||||
|
@ -147,6 +137,10 @@
|
||||||
fill: $primary;
|
fill: $primary;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
&.icon-merge {
|
||||||
|
cursor: default;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
&.icon-search {
|
&.icon-search {
|
||||||
cursor: none;
|
cursor: none;
|
||||||
fill: $primary;
|
fill: $primary;
|
||||||
|
@ -156,11 +150,9 @@
|
||||||
cursor: move;
|
cursor: move;
|
||||||
}
|
}
|
||||||
&.icon-trash {
|
&.icon-trash {
|
||||||
&:hover {
|
|
||||||
fill: $red-light;
|
fill: $red-light;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.gu-mirror {
|
.gu-mirror {
|
||||||
background: lighten($primary, 60%);
|
background: lighten($primary, 60%);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
|
@ -429,6 +429,7 @@
|
||||||
fill="#fff"
|
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>
|
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>
|
</symbol>
|
||||||
|
<<<<<<< b929b5ecdaefcb0f2430ea5c8e41ce8fdcdbe761
|
||||||
<symbol id="icon-add-user" viewBox="0 0 470 350">
|
<symbol id="icon-add-user" viewBox="0 0 470 350">
|
||||||
<title>Add user</title>
|
<title>Add user</title>
|
||||||
<path
|
<path
|
||||||
|
@ -437,6 +438,13 @@
|
||||||
<symbol id="icon-view-more" viewBox="0 0 66.3 16">
|
<symbol id="icon-view-more" viewBox="0 0 66.3 16">
|
||||||
<title>View more</title>
|
<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"/>
|
<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>
|
</symbol>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
@ -19,8 +19,21 @@ helper.getCreateEditUsLightbox = function() {
|
||||||
subject: function() {
|
subject: function() {
|
||||||
return el.$('input[name="subject"]');
|
return el.$('input[name="subject"]');
|
||||||
},
|
},
|
||||||
tags: function() {
|
tags: async function() {
|
||||||
return el.$('.tag-input');
|
$('.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() {
|
description: function() {
|
||||||
return el.$('textarea[name="description"]');
|
return el.$('textarea[name="description"]');
|
||||||
|
|
|
@ -63,3 +63,20 @@ helper.lightboxAttachment = async function() {
|
||||||
|
|
||||||
expect(countAttachments + 1).to.be.equal(newCountAttachments);
|
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() {
|
helper.tags = function() {
|
||||||
let el = $('div[tg-tag-line]');
|
let el = $('tg-tag-line-common');
|
||||||
|
|
||||||
let obj = {
|
let obj = {
|
||||||
el:el,
|
el:el,
|
||||||
|
|
||||||
clearTags: async function() {
|
clearTags: async function() {
|
||||||
let tags = await el.$$('.icon-delete');
|
let tags = await el.$$('.e2e-delete-tag');
|
||||||
let totalTags = tags.length;
|
let totalTags = tags.length;
|
||||||
let htmlChanges = null;
|
let htmlChanges = null;
|
||||||
while (totalTags > 0) {
|
while (totalTags > 0) {
|
||||||
htmlChanges = await utils.common.outerHtmlChanges(el.$(".tags-container"));
|
htmlChanges = await utils.common.outerHtmlChanges(el.$(".tags-container"));
|
||||||
await el.$$('.icon-delete').first().click();
|
await el.$$('.e2e-delete-tag').first().click();
|
||||||
totalTags --;
|
totalTags --;
|
||||||
await htmlChanges();
|
await htmlChanges();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getTagsText: function() {
|
getTagsText: function() {
|
||||||
return el.$$('.tag-name').getText();
|
return el.$$('tg-tag span').getText();
|
||||||
},
|
},
|
||||||
|
|
||||||
addTags: async function(tags) {
|
addTags: async function(tags) {
|
||||||
let htmlChanges = null
|
let htmlChanges = null
|
||||||
|
|
||||||
el.$('.add-tag').click();
|
$('.e2e-show-tag-input').click();
|
||||||
|
|
||||||
for (let tag of tags){
|
for (let tag of tags){
|
||||||
htmlChanges = await utils.common.outerHtmlChanges(el.$(".tags-container"));
|
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 browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
||||||
await htmlChanges();
|
await htmlChanges();
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,11 +49,7 @@ describe('backlog', function() {
|
||||||
createUSLightbox.status(2).click();
|
createUSLightbox.status(2).click();
|
||||||
|
|
||||||
// tags
|
// tags
|
||||||
createUSLightbox.tags().sendKeys('aaa');
|
commonHelper.tags();
|
||||||
browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
|
||||||
|
|
||||||
createUSLightbox.tags().sendKeys('bbb');
|
|
||||||
browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
|
||||||
|
|
||||||
// description
|
// description
|
||||||
createUSLightbox.description().sendKeys('test test');
|
createUSLightbox.description().sendKeys('test test');
|
||||||
|
@ -245,7 +241,7 @@ describe('backlog', function() {
|
||||||
expect(elementRef1).to.be.equal(draggedRefs[1]);
|
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 sprint = backlogHelper.sprints().get(0);
|
||||||
let initUssSprintCount = await backlogHelper.getSprintUsertories(sprint).count();
|
let initUssSprintCount = await backlogHelper.getSprintUsertories(sprint).count();
|
||||||
|
|
||||||
|
|
|
@ -38,11 +38,7 @@ describe('issues list', function() {
|
||||||
createIssueLightbox.subject().sendKeys('subject');
|
createIssueLightbox.subject().sendKeys('subject');
|
||||||
|
|
||||||
// tags
|
// tags
|
||||||
await createIssueLightbox.tags().sendKeys('aaa');
|
commonHelper.tags();
|
||||||
browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
|
||||||
|
|
||||||
await createIssueLightbox.tags().sendKeys('bbb');
|
|
||||||
browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('upload attachments', commonHelper.lightboxAttachment);
|
it('upload attachments', commonHelper.lightboxAttachment);
|
||||||
|
|
|
@ -74,11 +74,7 @@ describe('kanban', function() {
|
||||||
expect(totalPoints).to.be.equal('4');
|
expect(totalPoints).to.be.equal('4');
|
||||||
|
|
||||||
// tags
|
// tags
|
||||||
createUSLightbox.tags().sendKeys('www');
|
commonHelper.tags();
|
||||||
browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
|
||||||
|
|
||||||
createUSLightbox.tags().sendKeys('xxx');
|
|
||||||
browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
|
||||||
|
|
||||||
// description
|
// description
|
||||||
createUSLightbox.description().sendKeys(formFields.description);
|
createUSLightbox.description().sendKeys(formFields.description);
|
||||||
|
|
|
@ -66,11 +66,7 @@ describe('taskboard', function() {
|
||||||
createTaskLightbox.subject().sendKeys(formFields.subject);
|
createTaskLightbox.subject().sendKeys(formFields.subject);
|
||||||
createTaskLightbox.description().sendKeys(formFields.description);
|
createTaskLightbox.description().sendKeys(formFields.description);
|
||||||
|
|
||||||
createTaskLightbox.tags().sendKeys('aaa');
|
commonHelper.tags();
|
||||||
browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
|
||||||
|
|
||||||
createTaskLightbox.tags().sendKeys('bbb');
|
|
||||||
browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
|
||||||
|
|
||||||
await createTaskLightbox.blocked().click();
|
await createTaskLightbox.blocked().click();
|
||||||
await createTaskLightbox.blockedNote().sendKeys(formFields.blockedNote);
|
await createTaskLightbox.blockedNote().sendKeys(formFields.blockedNote);
|
||||||
|
|
Loading…
Reference in New Issue