Add tags autocompletion directive
parent
682f6492b3
commit
743d19ea6f
|
@ -173,7 +173,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
|||
@scope.projectId = data.project
|
||||
return data
|
||||
|
||||
return promise.then(=> @loadProject())
|
||||
return promise.then(=> @.loadProject())
|
||||
.then(=> @.loadUsersAndRoles())
|
||||
.then(=> @.loadBacklog())
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
###
|
||||
|
||||
taiga = @.taiga
|
||||
trim = @.taiga.trim
|
||||
|
||||
module = angular.module("taigaBase")
|
||||
|
||||
|
@ -88,3 +89,117 @@ ColorizeTagsDirective = ->
|
|||
return {link: link}
|
||||
|
||||
module.directive("tgColorizeTags", ColorizeTagsDirective)
|
||||
|
||||
#############################################################################
|
||||
## TagLine (possible should be moved as generic directive)
|
||||
#############################################################################
|
||||
|
||||
TagLineDirective = ($log, $rs) ->
|
||||
# Main directive template (rendered by angular)
|
||||
template = """
|
||||
<div class="tags-container"></div>
|
||||
<input type="text" placeholder="Write tag..." class="tag-input" />
|
||||
"""
|
||||
|
||||
# Tags template (rendered manually using lodash)
|
||||
templateTags = _.template("""
|
||||
<% _.each(tags, function(tag) { %>
|
||||
<div class="tag" style="border-left: 5px solid <%- tag.color %>;">
|
||||
<span class="tag-name"><%- tag.name %></span>
|
||||
<% if (editable) { %>
|
||||
<a href="" title="delete tag" class="icon icon-delete"></a>
|
||||
<% } %>
|
||||
</div>
|
||||
<% }); %>""")
|
||||
|
||||
renderTags = ($el, tags, editable, tagsColors) ->
|
||||
ctx = {
|
||||
tags: _.map(tags, (t) -> {name: t, color: tagsColors[t]})
|
||||
editable: editable
|
||||
}
|
||||
html = templateTags(ctx)
|
||||
$el.find("div.tags-container").html(html)
|
||||
|
||||
normalizeTags = (tags) ->
|
||||
tags = _.map(tags, trim)
|
||||
tags = _.map(tags, (x) -> x.toLowerCase())
|
||||
return _.uniq(tags)
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
editable = if $attrs.editable == "true" then true else false
|
||||
$el.addClass('tags-block')
|
||||
|
||||
addValue = (value) ->
|
||||
value = trim(value)
|
||||
return if value.length <= 0
|
||||
|
||||
tags = _.clone($model.$modelValue, false)
|
||||
tags = [] if not tags?
|
||||
tags.push(value)
|
||||
|
||||
$scope.$apply ->
|
||||
$model.$setViewValue(normalizeTags(tags))
|
||||
|
||||
|
||||
$scope.$watch $attrs.ngModel, (val) ->
|
||||
return if not val
|
||||
renderTags($el, val, editable, $scope.project.tags_colors)
|
||||
|
||||
$scope.$watch "projectId", (val) ->
|
||||
return if not val?
|
||||
positioningFunction = (position, elements) ->
|
||||
menu = elements.element.element
|
||||
menu.css 'width', elements.target.width
|
||||
menu.css 'top', position.top
|
||||
menu.css 'left', position.left
|
||||
|
||||
promise = $rs.projects.tags($scope.projectId)
|
||||
promise.then (data) ->
|
||||
if editable
|
||||
$el.find("input").autocomplete({
|
||||
source: data,
|
||||
position:
|
||||
my: "left top",
|
||||
using: positioningFunction
|
||||
select: (event, ui) ->
|
||||
addValue(ui.item.value)
|
||||
ui.item.value = ""
|
||||
})
|
||||
|
||||
$el.find("input").remove() if not editable
|
||||
|
||||
$el.on "keypress", "input", (event) ->
|
||||
return if event.keyCode != 13
|
||||
event.preventDefault()
|
||||
|
||||
$el.on "keyup", "input", (event) ->
|
||||
return if event.keyCode != 13
|
||||
event.preventDefault()
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
addValue(target.val())
|
||||
target.val("")
|
||||
$el.find("input").autocomplete("close")
|
||||
|
||||
|
||||
$el.on "click", ".icon-delete", (event) ->
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
value = trim(target.siblings(".tag-name").text())
|
||||
|
||||
if value.length <= 0
|
||||
return
|
||||
|
||||
tags = _.clone($model.$modelValue, false)
|
||||
tags = _.pull(tags, value)
|
||||
|
||||
$scope.$apply ->
|
||||
$model.$setViewValue(normalizeTags(tags))
|
||||
|
||||
return {
|
||||
link:link,
|
||||
require:"ngModel"
|
||||
template: template
|
||||
}
|
||||
|
||||
module.directive("tgTagLine", ["$log", "$tgResources", TagLineDirective])
|
||||
|
|
|
@ -27,89 +27,6 @@ typeIsArray = @.taiga.typeIsArray
|
|||
module = angular.module("taigaCommon", [])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## TagLine (possible should be moved as generic directive)
|
||||
#############################################################################
|
||||
|
||||
TagLineDirective = ($log) ->
|
||||
# Main directive template (rendered by angular)
|
||||
template = """
|
||||
<div class="tags-container"></div>
|
||||
<input type="text" placeholder="Write tag..." class="hidden"/>
|
||||
"""
|
||||
|
||||
# Tags template (rendered manually using lodash)
|
||||
templateTags = _.template("""
|
||||
<% _.each(tags, function(tag) { %>
|
||||
<div class="tag" style="border-left: 5px solid <%- tag.color %>;">
|
||||
<span class="tag-name"><%- tag.name %></span>
|
||||
<% if (editable) { %>
|
||||
<a href="" title="delete tag" class="icon icon-delete"></a>
|
||||
<% } %>
|
||||
</div>
|
||||
<% }); %>""")
|
||||
|
||||
renderTags = ($el, tags, editable, tagsColors) ->
|
||||
ctx = {
|
||||
tags: _.map(tags, (t) -> {name: t, color: tagsColors[t]})
|
||||
editable: editable
|
||||
}
|
||||
html = templateTags(ctx)
|
||||
$el.find("div.tags-container").html(html)
|
||||
|
||||
normalizeTags = (tags) ->
|
||||
tags = _.map(tags, trim)
|
||||
tags = _.map(tags, (x) -> x.toLowerCase())
|
||||
return _.uniq(tags)
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
editable = if $attrs.editable == "true" then true else false
|
||||
|
||||
$scope.$watch $attrs.ngModel, (val) ->
|
||||
return if not val
|
||||
renderTags($el, val, editable, $scope.project.tags_colors)
|
||||
|
||||
$el.find("input").remove() if not editable
|
||||
|
||||
$el.on "keyup", "input", (event) ->
|
||||
return if event.keyCode != 13
|
||||
target = angular.element(event.currentTarget)
|
||||
value = trim(target.val())
|
||||
|
||||
if value.length <= 0
|
||||
return
|
||||
|
||||
tags = _.clone($model.$modelValue, false)
|
||||
tags = [] if not tags?
|
||||
tags.push(value)
|
||||
|
||||
target.val("")
|
||||
|
||||
$scope.$apply ->
|
||||
$model.$setViewValue(normalizeTags(tags))
|
||||
|
||||
$el.on "click", ".icon-delete", (event) ->
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
value = trim(target.siblings(".tag-name").text())
|
||||
|
||||
if value.length <= 0
|
||||
return
|
||||
|
||||
tags = _.clone($model.$modelValue, false)
|
||||
tags = _.pull(tags, value)
|
||||
|
||||
$scope.$apply ->
|
||||
$model.$setViewValue(normalizeTags(tags))
|
||||
|
||||
return {
|
||||
link:link,
|
||||
require:"ngModel"
|
||||
template: template
|
||||
}
|
||||
|
||||
module.directive("tgTagLine", ["$log", TagLineDirective])
|
||||
|
||||
#############################################################################
|
||||
## Change (comment and history mode) directive
|
||||
#############################################################################
|
||||
|
|
|
@ -45,6 +45,9 @@ resourceProvider = ($repo) ->
|
|||
service.stats = (projectId) ->
|
||||
return $repo.queryOneRaw("projects", "#{projectId}/stats")
|
||||
|
||||
service.tags = (projectId) ->
|
||||
return $repo.queryOneRaw("projects", "#{projectId}/tags")
|
||||
|
||||
service.tagsColors = (id) ->
|
||||
return $repo.queryOne("projects", "#{id}/tags_colors")
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ block content
|
|||
span.block-description(tg-bind-html="issue.blocked_note || 'This issue is blocked'")
|
||||
a.unblock(ng-click="ctrl.unblock()", href="", title="Unblock issue") Unblock
|
||||
|
||||
div.user-story-tags(tg-tag-line, editable="true", ng-model="issue.tags")
|
||||
div(tg-tag-line, editable="true", ng-model="issue.tags")
|
||||
|
||||
section.us-content
|
||||
textarea(placeholder="Write a description of your issue", ng-model="issue.description", tg-markitup)
|
||||
|
|
|
@ -24,7 +24,7 @@ block content
|
|||
a.icon.icon-arrow-left(ng-show="previousUrl",href="{{ previousUrl }}", title="previous issue")
|
||||
a.icon.icon-arrow-right(ng-show="nextUrl", href="{{ nextUrl }}", title="next issue")
|
||||
|
||||
div.user-story-tags(tg-tag-line, ng-model="issue.tags", ng-show="issue.tags")
|
||||
div(tg-tag-line, ng-model="issue.tags", ng-show="issue.tags")
|
||||
|
||||
section.us-content.wysiwyg(tg-bind-html="issue.description_html")
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ block content
|
|||
span.block-description(tg-bind-html="task.blocked_note || 'This task is blocked'")
|
||||
a.unblock(ng-click="ctrl.unblock()", href="", title="Unblock task") Unblock
|
||||
|
||||
div.user-story-tags(tg-tag-line, editable="true", ng-model="task.tags")
|
||||
div(tg-tag-line, editable="true", ng-model="task.tags")
|
||||
|
||||
section.us-content
|
||||
textarea(placeholder="Write a description of your task", ng-model="task.description", tg-markitup)
|
||||
|
|
|
@ -24,7 +24,7 @@ block content
|
|||
a.icon.icon-arrow-left(ng-show="previousUrl",href="{{ previousUrl }}", title="previous task")
|
||||
a.icon.icon-arrow-right(ng-show="nextUrl", href="{{ nextUrl }}", title="next task")
|
||||
|
||||
div.user-story-tags(tg-tag-line, ng-model="task.tags", ng-show="task.tags")
|
||||
div(tg-tag-line, ng-model="task.tags", ng-show="task.tags")
|
||||
|
||||
section.us-content.wysiwyg(tg-bind-html="task.description_html")
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ block content
|
|||
span.block-description(tg-bind-html="us.blocked_note || 'This US is blocked'")
|
||||
a.unblock(ng-click="ctrl.unblock()", href="", title="Unblock US") Unblock
|
||||
|
||||
div.user-story-tags(tg-tag-line, editable="true", ng-model="us.tags")
|
||||
div(tg-tag-line, editable="true", ng-model="us.tags")
|
||||
|
||||
section.us-content
|
||||
textarea(placeholder="Write a description of your user story", ng-model="us.description", tg-markitup)
|
||||
|
|
|
@ -26,7 +26,7 @@ block content
|
|||
title="previous user story")
|
||||
a.icon.icon-arrow-right(ng-show="nextUrl", href="{{ nextUrl }}", title="next user story")
|
||||
|
||||
div.user-story-tags(tg-tag-line, ng-model="us.tags", ng-show="us.tags")
|
||||
div(tg-tag-line, ng-model="us.tags", ng-show="us.tags")
|
||||
|
||||
section.us-content.wysiwyg(tg-bind-html="us.description_html")
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
div.row.us-item-row(ng-repeat="us in visibleUserstories|orderBy:order track by us.id", tg-draggable, ng-class="{blocked: us.is_blocked}")
|
||||
div.user-stories
|
||||
div.user-story-tags(tg-colorize-tags="us.tags", tg-colorize-tags-type="backlog")
|
||||
div.tags-block(tg-colorize-tags="us.tags", tg-colorize-tags-type="backlog")
|
||||
div.user-story-name
|
||||
input(tg-check-permission, permission="modify_us", type="checkbox", name="")
|
||||
a.clickable(tg-nav="project-userstories-detail:project=project.slug,ref=us.ref",
|
||||
|
|
|
@ -14,7 +14,7 @@ form
|
|||
select.severity(ng-model="issue.severity", ng-options="s.id as s.name for s in severityList")
|
||||
|
||||
fieldset
|
||||
input(type="text", placeholder="Tags", tg-tags, ng-model="issue.tags")
|
||||
div(tg-tag-line, editable="true", ng-model="issue.tags")
|
||||
|
||||
fieldset
|
||||
textarea.description(placeholder="Description", ng-model="issue.description")
|
||||
|
|
|
@ -16,7 +16,7 @@ form
|
|||
option(value="") Unassigned
|
||||
|
||||
fieldset
|
||||
input(type="text", placeholder="Tags", tg-tags, ng-model="task.tags")
|
||||
div(tg-tag-line, editable="true", ng-model="task.tags")
|
||||
|
||||
fieldset
|
||||
textarea.description(placeholder="Type a short description", ng-model="task.description")
|
||||
|
|
|
@ -9,7 +9,7 @@ form
|
|||
select(name="status", ng-model="us.status", ng-options="s.id as s.name for s in usStatusList",
|
||||
tg-i18n="placeholder:common.status")
|
||||
fieldset
|
||||
input(type="text", name="tags", placeholder="Tags", tg-tags, ng-model="us.tags")
|
||||
div(tg-tag-line, editable="true", ng-model="us.tags")
|
||||
|
||||
fieldset
|
||||
textarea.description(name="description", ng-model="us.description",
|
||||
|
|
|
@ -13,3 +13,33 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ui-autocomplete {
|
||||
z-index: 99910;
|
||||
background: white;
|
||||
border: 1px solid $gray-light;
|
||||
.ui-state-focus {
|
||||
background: $fresh-taiga;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-helper-hidden-accessible {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tags-block {
|
||||
.tags-container {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
input {
|
||||
display: inline-block;
|
||||
padding: .4rem;
|
||||
width: 14rem;
|
||||
}
|
||||
.tag {
|
||||
@extend %small;
|
||||
margin: 0 .5rem .5rem 0;
|
||||
padding: .5rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,23 +114,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.user-story-tags {
|
||||
.tags-container {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
input {
|
||||
display: inline-block;
|
||||
padding: .4rem;
|
||||
width: 14rem;
|
||||
}
|
||||
.tag {
|
||||
@extend %small;
|
||||
margin: 0 .5rem .5rem 0;
|
||||
padding: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.us-activity-tabs {
|
||||
@extend %title;
|
||||
border-bottom: 3px solid $gray-light;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
@include table-flex();
|
||||
width: 100%;
|
||||
&.show-tags {
|
||||
.user-story-tags {
|
||||
.tags-block {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
@ -156,7 +156,7 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
.user-story-tags {
|
||||
.tags-block {
|
||||
display: none;
|
||||
margin-bottom: .3rem;
|
||||
.tag {
|
||||
|
|
|
@ -165,7 +165,7 @@
|
|||
text-align: center;
|
||||
}
|
||||
.us-item-row {
|
||||
.user-story-tags,
|
||||
.tags-block,
|
||||
.us-settings,
|
||||
.status,
|
||||
.icon-drag-v,
|
||||
|
|
Loading…
Reference in New Issue