Add tags autocompletion directive

stable
Jesús Espino 2014-09-08 18:46:08 +02:00
parent 682f6492b3
commit 743d19ea6f
19 changed files with 168 additions and 120 deletions

View File

@ -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())

View File

@ -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])

View File

@ -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
#############################################################################

View File

@ -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")

View File

@ -35,11 +35,11 @@ block content
sidebar.menu-secondary.sidebar
include views/modules/sprints
div.lightbox.lightbox-generic-form(tg-lb-create-edit-userstory)
include views/modules/lightbox-us-create-edit
div.lightbox.lightbox-generic-form(tg-lb-create-edit-userstory)
include views/modules/lightbox-us-create-edit
div.lightbox.lightbox-generic-bulk(tg-lb-create-bulk-userstories)
include views/modules/lightbox-us-bulk
div.lightbox.lightbox-generic-bulk(tg-lb-create-bulk-userstories)
include views/modules/lightbox-us-bulk
div.lightbox.lightbox-sprint-add-edit(tg-lb-create-edit-sprint)
include views/modules/lightbox-sprint-add-edit
div.lightbox.lightbox-sprint-add-edit(tg-lb-create-edit-sprint)
include views/modules/lightbox-sprint-add-edit

View File

@ -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)

View File

@ -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")

View File

@ -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)

View File

@ -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")

View File

@ -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)

View File

@ -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")

View File

@ -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",

View File

@ -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")

View File

@ -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")

View File

@ -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",

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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 {

View File

@ -165,7 +165,7 @@
text-align: center;
}
.us-item-row {
.user-story-tags,
.tags-block,
.us-settings,
.status,
.icon-drag-v,