Merge pull request #102 from taigaio/us/1286/us-task-issue-details-refactor
US #1286: US/Task/Issue Visualization and edition refactorstable
|
@ -3,6 +3,7 @@
|
||||||
## 1.2.0 (Unreleased)
|
## 1.2.0 (Unreleased)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
- US/Task/Issue visualization and edition refactor. Now only one view for both.
|
||||||
- Multiple User stories Drag & Drop in the backlog.
|
- Multiple User stories Drag & Drop in the backlog.
|
||||||
- Add visual difference to closed USs in backlog panel.
|
- Add visual difference to closed USs in backlog panel.
|
||||||
- Show crerated date of attachments in the hover of the filename.
|
- Show crerated date of attachments in the hover of the filename.
|
||||||
|
|
|
@ -52,30 +52,22 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
||||||
# User stories
|
# User stories
|
||||||
$routeProvider.when("/project/:pslug/us/:usref",
|
$routeProvider.when("/project/:pslug/us/:usref",
|
||||||
{templateUrl: "/partials/us-detail.html", resolve: {loader: tgLoaderProvider.add()}})
|
{templateUrl: "/partials/us-detail.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||||
$routeProvider.when("/project/:pslug/us/:usref/edit",
|
|
||||||
{templateUrl: "/partials/us-detail-edit.html"})
|
|
||||||
|
|
||||||
# Tasks
|
# Tasks
|
||||||
$routeProvider.when("/project/:pslug/task/:taskref",
|
$routeProvider.when("/project/:pslug/task/:taskref",
|
||||||
{templateUrl: "/partials/task-detail.html", resolve: {loader: tgLoaderProvider.add()}})
|
{templateUrl: "/partials/task-detail.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||||
$routeProvider.when("/project/:pslug/task/:taskref/edit",
|
|
||||||
{templateUrl: "/partials/task-detail-edit.html"})
|
|
||||||
|
|
||||||
# Wiki
|
# Wiki
|
||||||
$routeProvider.when("/project/:pslug/wiki",
|
$routeProvider.when("/project/:pslug/wiki",
|
||||||
{redirectTo: (params) -> "/project/#{params.pslug}/wiki/home"}, )
|
{redirectTo: (params) -> "/project/#{params.pslug}/wiki/home"}, )
|
||||||
$routeProvider.when("/project/:pslug/wiki/:slug",
|
$routeProvider.when("/project/:pslug/wiki/:slug",
|
||||||
{templateUrl: "/partials/wiki.html", resolve: {loader: tgLoaderProvider.add()}})
|
{templateUrl: "/partials/wiki.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||||
$routeProvider.when("/project/:pslug/wiki/:slug/edit",
|
|
||||||
{templateUrl: "/partials/wiki-edit.html"})
|
|
||||||
|
|
||||||
# Issues
|
# Issues
|
||||||
$routeProvider.when("/project/:pslug/issues",
|
$routeProvider.when("/project/:pslug/issues",
|
||||||
{templateUrl: "/partials/issues.html", resolve: {loader: tgLoaderProvider.add()}})
|
{templateUrl: "/partials/issues.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||||
$routeProvider.when("/project/:pslug/issue/:issueref",
|
$routeProvider.when("/project/:pslug/issue/:issueref",
|
||||||
{templateUrl: "/partials/issues-detail.html"})
|
{templateUrl: "/partials/issues-detail.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||||
$routeProvider.when("/project/:pslug/issue/:issueref/edit",
|
|
||||||
{templateUrl: "/partials/issues-detail-edit.html"})
|
|
||||||
|
|
||||||
# Admin
|
# Admin
|
||||||
$routeProvider.when("/project/:pslug/admin/project-profile/details",
|
$routeProvider.when("/project/:pslug/admin/project-profile/details",
|
||||||
|
|
|
@ -67,17 +67,13 @@ urls = {
|
||||||
"project-search": "/project/:project/search"
|
"project-search": "/project/:project/search"
|
||||||
|
|
||||||
"project-userstories-detail": "/project/:project/us/:ref"
|
"project-userstories-detail": "/project/:project/us/:ref"
|
||||||
"project-userstories-detail-edit": "/project/:project/us/:ref/edit"
|
|
||||||
|
|
||||||
"project-tasks-detail": "/project/:project/task/:ref"
|
"project-tasks-detail": "/project/:project/task/:ref"
|
||||||
"project-tasks-detail-edit": "/project/:project/task/:ref/edit"
|
|
||||||
|
|
||||||
"project-issues-detail": "/project/:project/issue/:ref"
|
"project-issues-detail": "/project/:project/issue/:ref"
|
||||||
"project-issues-detail-edit": "/project/:project/issue/:ref/edit"
|
|
||||||
|
|
||||||
"project-wiki": "/project/:project/wiki",
|
"project-wiki": "/project/:project/wiki",
|
||||||
"project-wiki-page": "/project/:project/wiki/:slug",
|
"project-wiki-page": "/project/:project/wiki/:slug",
|
||||||
"project-wiki-page-edit": "/project/:project/wiki/:slug/edit",
|
|
||||||
|
|
||||||
# Admin
|
# Admin
|
||||||
"project-admin-home": "/project/:project/admin/project-profile/details"
|
"project-admin-home": "/project/:project/admin/project-profile/details"
|
||||||
|
|
|
@ -24,6 +24,7 @@ bindOnce = @.taiga.bindOnce
|
||||||
|
|
||||||
module = angular.module("taigaCommon")
|
module = angular.module("taigaCommon")
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Date Range Directive (used mainly for sprint date range)
|
## Date Range Directive (used mainly for sprint date range)
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
@ -46,6 +47,33 @@ DateRangeDirective = ->
|
||||||
module.directive("tgDateRange", DateRangeDirective)
|
module.directive("tgDateRange", DateRangeDirective)
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## Date Selector Directive (using pikaday)
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
DateSelectorDirective =->
|
||||||
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
selectedDate = null
|
||||||
|
$el.picker = new Pikaday({
|
||||||
|
field: $el[0]
|
||||||
|
format: "DD MMM YYYY"
|
||||||
|
onSelect: (date) =>
|
||||||
|
selectedDate = date
|
||||||
|
onOpen: =>
|
||||||
|
$el.picker.setDate(selectedDate) if selectedDate?
|
||||||
|
})
|
||||||
|
|
||||||
|
$scope.$watch $attrs.ngModel, (val) ->
|
||||||
|
$el.picker.setDate(val) if val?
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: link
|
||||||
|
require: "ngModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgDateSelector", DateSelectorDirective)
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Sprint Progress Bar Directive
|
## Sprint Progress Bar Directive
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
@ -76,92 +104,126 @@ module.directive("tgSprintProgressbar", SprintProgressBarDirective)
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Date Selector Directive (using pikaday)
|
## Created-by display directive
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
DateSelectorDirective =->
|
CreatedByDisplayDirective = ->
|
||||||
link = ($scope, $el, $attrs, $model) ->
|
# Display the owner information (full name and photo) and the date of
|
||||||
selectedDate = null
|
# creation of an object (like USs, tasks and issues).
|
||||||
$el.picker = new Pikaday({
|
#
|
||||||
field: $el[0]
|
# Example:
|
||||||
format: "DD MMM YYYY"
|
# div.us-created-by(tg-created-by-display, ng-model="us")
|
||||||
onSelect: (date) =>
|
#
|
||||||
selectedDate = date
|
# Requirements:
|
||||||
onOpen: =>
|
# - model object must have the attributes 'created_date' and
|
||||||
$el.picker.setDate(selectedDate) if selectedDate?
|
# 'owner'(ng-model)
|
||||||
})
|
# - scope.usersById object is required.
|
||||||
|
|
||||||
$scope.$watch $attrs.ngModel, (val) ->
|
template = _.template("""
|
||||||
$el.picker.setDate(val) if val?
|
<div class="user-avatar">
|
||||||
|
<img src="<%= owner.photo %>" alt="<%- owner.full_name_display %>" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="created-by">
|
||||||
|
<span class="created-title">Created by <%- owner.full_name_display %></span>
|
||||||
|
<span class="created-date"><%- date %></span>
|
||||||
|
</div>
|
||||||
|
""") # TODO: i18n
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs) ->
|
||||||
|
render = (model) ->
|
||||||
|
html = template({
|
||||||
|
owner: $scope.usersById?[model.owner]
|
||||||
|
date: moment(model.created_date).format("DD MMM YYYY HH:mm")
|
||||||
|
})
|
||||||
|
$el.html(html)
|
||||||
|
|
||||||
|
bindOnce $scope, $attrs.ngModel, (model) ->
|
||||||
|
render(model) if model?
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
link: link
|
link: link
|
||||||
|
restrict: "EA"
|
||||||
require: "ngModel"
|
require: "ngModel"
|
||||||
}
|
}
|
||||||
|
|
||||||
module.directive("tgDateSelector", DateSelectorDirective)
|
module.directive("tgCreatedByDisplay", CreatedByDisplayDirective)
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Watchers directive
|
## Watchers directive
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
WatchersDirective = ($rootscope, $confirm) ->
|
WatchersDirective = ($rootscope, $confirm, $repo) ->
|
||||||
|
# You have to include a div with the tg-lb-watchers directive in the page
|
||||||
|
# where use this directive
|
||||||
|
#
|
||||||
# TODO: i18n
|
# TODO: i18n
|
||||||
template = _.template("""
|
template = _.template("""
|
||||||
|
<% if(isEditable){ %>
|
||||||
<div class="watchers-header">
|
<div class="watchers-header">
|
||||||
<span class="title">watchers</span>
|
<span class="title">watchers</span>
|
||||||
<% if (editable) { %>
|
|
||||||
<a href="" title="Add watcher" class="icon icon-plus add-watcher"></a>
|
<a href="" title="Add watcher" class="icon icon-plus add-watcher"></a>
|
||||||
<% } %>
|
|
||||||
</div>
|
</div>
|
||||||
|
<% } else if(watchers.length > 0){ %>
|
||||||
|
<div class="watchers-header">
|
||||||
|
<span class="title">watchers</span>
|
||||||
|
</div>
|
||||||
|
<% }; %>
|
||||||
|
|
||||||
<% _.each(watchers, function(watcher) { %>
|
<% _.each(watchers, function(watcher) { %>
|
||||||
<div class="watcher-single">
|
<div class="watcher-single">
|
||||||
<div class="watcher-avatar">
|
<div class="watcher-avatar">
|
||||||
<a class="avatar" href="" title="Assigned to">
|
<span class="avatar" href="" title="<%- watcher.full_name_display %>">
|
||||||
<img src="<%= watcher.photo %>" alt="<%- watcher.full_name_display %>">
|
<img src="<%= watcher.photo %>" alt="<%- watcher.full_name_display %>">
|
||||||
</a>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="watcher-name">
|
<div class="watcher-name">
|
||||||
<span>
|
<span><%- watcher.full_name_display %></span>
|
||||||
<%- watcher.full_name_display %>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<% if (editable) { %>
|
<% if(isEditable){ %>
|
||||||
<a class="icon icon-delete"
|
<a class="icon icon-delete"
|
||||||
data-watcher-id="<%= watcher.id %>" href="" title="delete-watcher">
|
data-watcher-id="<%= watcher.id %>" href="" title="delete-watcher">
|
||||||
</a>
|
</a>
|
||||||
<% } %>
|
<% }; %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% }); %>
|
<% }); %>
|
||||||
""")
|
""")
|
||||||
|
|
||||||
link = ($scope, $el, $attrs, $model) ->
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
editable = $attrs.editable?
|
isEditable = ->
|
||||||
|
return $scope.project?.my_permissions?.indexOf($attrs.requiredPerm) != -1
|
||||||
|
|
||||||
|
save = (model) ->
|
||||||
|
promise = $repo.save($model.$modelValue)
|
||||||
|
promise.then ->
|
||||||
|
$confirm.notify("success")
|
||||||
|
watchers = _.map(model.watchers, (watcherId) -> $scope.usersById[watcherId])
|
||||||
|
renderWatchers(watchers)
|
||||||
|
$rootscope.$broadcast("history:reload")
|
||||||
|
promise.then null, ->
|
||||||
|
model.revert()
|
||||||
|
$confirm.notify("error")
|
||||||
|
|
||||||
renderWatchers = (watchers) ->
|
renderWatchers = (watchers) ->
|
||||||
html = template({watchers: watchers, editable:editable})
|
ctx = {
|
||||||
|
watchers: watchers
|
||||||
|
isEditable: isEditable()
|
||||||
|
}
|
||||||
|
html = template(ctx)
|
||||||
$el.html(html)
|
$el.html(html)
|
||||||
|
|
||||||
if watchers.length == 0
|
if isEditable() and watchers.length == 0
|
||||||
if editable
|
$el.find(".title").text("Add watchers")
|
||||||
$el.find(".title").text("Add watchers")
|
$el.find(".watchers-header").addClass("no-watchers")
|
||||||
$el.find(".watchers-header").addClass("no-watchers")
|
|
||||||
else
|
|
||||||
$el.find(".watchers-header").hide()
|
|
||||||
|
|
||||||
$scope.$watch $attrs.ngModel, (item) ->
|
|
||||||
return if not item?
|
|
||||||
watchers = _.map(item.watchers, (watcherId) -> $scope.usersById[watcherId])
|
|
||||||
renderWatchers(watchers)
|
|
||||||
|
|
||||||
if not editable
|
|
||||||
$el.find(".add-watcher").remove()
|
|
||||||
|
|
||||||
$el.on "click", ".icon-delete", (event) ->
|
$el.on "click", ".icon-delete", (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
return if not isEditable()
|
||||||
target = angular.element(event.currentTarget)
|
target = angular.element(event.currentTarget)
|
||||||
watcherId = target.data("watcher-id")
|
watcherId = target.data("watcher-id")
|
||||||
|
|
||||||
|
@ -176,9 +238,11 @@ WatchersDirective = ($rootscope, $confirm) ->
|
||||||
item = $model.$modelValue.clone()
|
item = $model.$modelValue.clone()
|
||||||
item.watchers = watcherIds
|
item.watchers = watcherIds
|
||||||
$model.$setViewValue(item)
|
$model.$setViewValue(item)
|
||||||
|
save(item)
|
||||||
|
|
||||||
$el.on "click", ".add-watcher", (event) ->
|
$el.on "click", ".add-watcher", (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
return if not isEditable()
|
||||||
$scope.$apply ->
|
$scope.$apply ->
|
||||||
$rootscope.$broadcast("watcher:add", $model.$modelValue)
|
$rootscope.$broadcast("watcher:add", $model.$modelValue)
|
||||||
|
|
||||||
|
@ -189,19 +253,30 @@ WatchersDirective = ($rootscope, $confirm) ->
|
||||||
|
|
||||||
item = $model.$modelValue.clone()
|
item = $model.$modelValue.clone()
|
||||||
item.watchers = watchers
|
item.watchers = watchers
|
||||||
|
|
||||||
$model.$setViewValue(item)
|
$model.$setViewValue(item)
|
||||||
|
save(item)
|
||||||
|
|
||||||
|
$scope.$watch $attrs.ngModel, (item) ->
|
||||||
|
return if not item?
|
||||||
|
watchers = _.map(item.watchers, (watcherId) -> $scope.usersById[watcherId])
|
||||||
|
renderWatchers(watchers)
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
return {link:link, require:"ngModel"}
|
return {link:link, require:"ngModel"}
|
||||||
|
|
||||||
module.directive("tgWatchers", ["$rootScope", "$tgConfirm", WatchersDirective])
|
module.directive("tgWatchers", ["$rootScope", "$tgConfirm", "$tgRepo", WatchersDirective])
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Assigned to directive
|
## Assigned to directive
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
AssignedToDirective = ($rootscope, $confirm) ->
|
AssignedToDirective = ($rootscope, $confirm, $repo, $loading) ->
|
||||||
|
# You have to include a div with the tg-lb-assignedto directive in the page
|
||||||
|
# where use this directive
|
||||||
|
#
|
||||||
# TODO: i18n
|
# TODO: i18n
|
||||||
template = _.template("""
|
template = _.template("""
|
||||||
<% if (assignedTo) { %>
|
<% if (assignedTo) { %>
|
||||||
|
@ -213,62 +288,360 @@ AssignedToDirective = ($rootscope, $confirm) ->
|
||||||
<div class="assigned-to">
|
<div class="assigned-to">
|
||||||
<span class="assigned-title">Assigned to</span>
|
<span class="assigned-title">Assigned to</span>
|
||||||
|
|
||||||
<a href="" title="edit assignment" class="user-assigned <% if (editable) { %> editable <% } %>">
|
<a href="" title="edit assignment" class="user-assigned <% if(isEditable){ %>editable<% }; %>">
|
||||||
<% if (assignedTo) { %>
|
<span class="assigned-name">
|
||||||
<%- assignedTo.full_name_display %>
|
<% if (assignedTo) { %>
|
||||||
<% } else { %>
|
<%- assignedTo.full_name_display %>
|
||||||
Not assigned
|
<% } else { %>
|
||||||
<% } %>
|
Not assigned
|
||||||
<% if (editable) { %>
|
<% } %>
|
||||||
<span class="icon icon-arrow-bottom"></span>
|
</span>
|
||||||
<% } %>
|
<% if(isEditable){ %><span class="icon icon-arrow-bottom"></span><% }; %>
|
||||||
</a>
|
</a>
|
||||||
<% if (editable && assignedTo!==null) { %>
|
<% if (assignedTo!==null && isEditable) { %>
|
||||||
<a href="" title="delete assignment" class="icon icon-delete"></a>
|
<a href="" title="delete assignment" class="icon icon-delete"></a>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
""")
|
""") # TODO: i18n
|
||||||
|
|
||||||
link = ($scope, $el, $attrs, $model) ->
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
editable = $attrs.editable?
|
isEditable = ->
|
||||||
|
return $scope.project?.my_permissions?.indexOf($attrs.requiredPerm) != -1
|
||||||
|
|
||||||
|
save = (model) ->
|
||||||
|
$loading.start($el)
|
||||||
|
|
||||||
|
promise = $repo.save($model.$modelValue)
|
||||||
|
promise.then ->
|
||||||
|
$loading.finish($el)
|
||||||
|
$confirm.notify("success")
|
||||||
|
renderAssignedTo(model)
|
||||||
|
$rootscope.$broadcast("history:reload")
|
||||||
|
promise.then null, ->
|
||||||
|
model.revert()
|
||||||
|
$confirm.notify("error")
|
||||||
|
$loading.finish($el)
|
||||||
|
|
||||||
renderAssignedTo = (issue) ->
|
renderAssignedTo = (issue) ->
|
||||||
assignedToId = issue?.assigned_to
|
assignedToId = issue?.assigned_to
|
||||||
assignedTo = null
|
assignedTo = if assignedToId? then $scope.usersById[assignedToId] else null
|
||||||
assignedTo = $scope.usersById[assignedToId] if assignedToId?
|
|
||||||
html = template({assignedTo: assignedTo, editable:editable})
|
ctx = {
|
||||||
|
assignedTo: assignedTo
|
||||||
|
isEditable: isEditable()
|
||||||
|
}
|
||||||
|
html = template(ctx)
|
||||||
$el.html(html)
|
$el.html(html)
|
||||||
|
|
||||||
|
$el.on "click", ".user-assigned", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
return if not isEditable()
|
||||||
|
$scope.$apply ->
|
||||||
|
$rootscope.$broadcast("assigned-to:add", $model.$modelValue)
|
||||||
|
|
||||||
|
$el.on "click", ".icon-delete", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
return if not isEditable()
|
||||||
|
title = "Are you sure you want to leave it unassigned?" # TODO: i18n
|
||||||
|
|
||||||
|
$confirm.ask(title).then (finish) =>
|
||||||
|
finish()
|
||||||
|
$model.$modelValue.assigned_to = null
|
||||||
|
save($model.$modelValue)
|
||||||
|
|
||||||
|
$scope.$on "assigned-to:added", (ctx, userId) ->
|
||||||
|
$model.$modelValue.assigned_to = userId
|
||||||
|
save($model.$modelValue)
|
||||||
|
|
||||||
$scope.$watch $attrs.ngModel, (instance) ->
|
$scope.$watch $attrs.ngModel, (instance) ->
|
||||||
renderAssignedTo(instance)
|
renderAssignedTo(instance)
|
||||||
|
|
||||||
if editable
|
$scope.$on "$destroy", ->
|
||||||
$el.on "click", ".user-assigned", (event) ->
|
$el.off()
|
||||||
event.preventDefault()
|
|
||||||
$scope.$apply ->
|
|
||||||
$rootscope.$broadcast("assigned-to:add", $model.$modelValue)
|
|
||||||
|
|
||||||
$el.on "click", ".icon-delete", (event) ->
|
|
||||||
event.preventDefault()
|
|
||||||
title = "Delete assignetion"
|
|
||||||
message = ""
|
|
||||||
|
|
||||||
$confirm.askOnDelete(title, message).then (finish) =>
|
|
||||||
finish()
|
|
||||||
$model.$modelValue.assigned_to = null
|
|
||||||
renderAssignedTo($model.$modelValue)
|
|
||||||
|
|
||||||
$scope.$on "assigned-to:added", (ctx, userId) ->
|
|
||||||
$model.$modelValue.assigned_to = userId
|
|
||||||
renderAssignedTo($model.$modelValue)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
link:link,
|
link:link,
|
||||||
require:"ngModel"
|
require:"ngModel"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.directive("tgAssignedTo", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgLoading", AssignedToDirective])
|
||||||
|
|
||||||
module.directive("tgAssignedTo", ["$rootScope", "$tgConfirm", AssignedToDirective])
|
|
||||||
|
#############################################################################
|
||||||
|
## Block Button directive
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
BlockButtonDirective = ($rootscope, $loading) ->
|
||||||
|
template = """
|
||||||
|
<a href="#" class="button button-gray item-block">Block</a>
|
||||||
|
<a href="#" class="button button-red item-unblock">Unblock</a>
|
||||||
|
"""
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
isEditable = ->
|
||||||
|
return $scope.project.my_permissions.indexOf("modify_us") != -1
|
||||||
|
|
||||||
|
$scope.$watch $attrs.ngModel, (item) ->
|
||||||
|
return if not item
|
||||||
|
|
||||||
|
if isEditable()
|
||||||
|
$el.find('.item-block').addClass('editable')
|
||||||
|
|
||||||
|
if item.is_blocked
|
||||||
|
$el.find('.item-block').hide()
|
||||||
|
$el.find('.item-unblock').show()
|
||||||
|
else
|
||||||
|
$el.find('.item-block').show()
|
||||||
|
$el.find('.item-unblock').hide()
|
||||||
|
|
||||||
|
$el.on "click", ".item-block", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
$rootscope.$broadcast("block", $model.$modelValue)
|
||||||
|
|
||||||
|
$el.on "click", ".item-unblock", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
$loading.start($el.find(".item-unblock"))
|
||||||
|
finish = ->
|
||||||
|
$loading.finish($el.find(".item-unblock"))
|
||||||
|
|
||||||
|
$rootscope.$broadcast("unblock", $model.$modelValue, finish)
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: link
|
||||||
|
restrict: "EA"
|
||||||
|
require: "ngModel"
|
||||||
|
template: template
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgBlockButton", ["$rootScope", "$tgLoading", BlockButtonDirective])
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## Delete Button directive
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
DeleteButtonDirective = ($log, $repo, $confirm, $location) ->
|
||||||
|
template = """
|
||||||
|
<a href="" class="button button-red">Delete</a>
|
||||||
|
""" #TODO: i18n
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
if not $attrs.onDeleteGoToUrl
|
||||||
|
return $log.error "DeleteButtonDirective requires on-delete-go-to-url set in scope."
|
||||||
|
if not $attrs.onDeleteTitle
|
||||||
|
return $log.error "DeleteButtonDirective requires on-delete-title set in scope."
|
||||||
|
|
||||||
|
$el.on "click", ".button", (event) ->
|
||||||
|
title = $scope.$eval($attrs.onDeleteTitle)
|
||||||
|
subtitle = $model.$modelValue.subject
|
||||||
|
|
||||||
|
$confirm.askOnDelete(title, subtitle).then (finish) =>
|
||||||
|
promise = $repo.remove($model.$modelValue)
|
||||||
|
promise.then =>
|
||||||
|
finish()
|
||||||
|
url = $scope.$eval($attrs.onDeleteGoToUrl)
|
||||||
|
$location.path(url)
|
||||||
|
promise.then null, =>
|
||||||
|
finish(false)
|
||||||
|
$confirm.notify("error")
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: link
|
||||||
|
restrict: "EA"
|
||||||
|
require: "ngModel"
|
||||||
|
template: template
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgDeleteButton", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", DeleteButtonDirective])
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## Editable subject directive
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading) ->
|
||||||
|
template = """
|
||||||
|
<div class="view-subject">
|
||||||
|
{{ item.subject }}
|
||||||
|
<a class="edit icon icon-edit" href="" title="Edit" />
|
||||||
|
</div>
|
||||||
|
<div class="edit-subject">
|
||||||
|
<input type="text" ng-model="item.subject" data-required="true" data-maxlength="500"/>
|
||||||
|
<span class="save-container">
|
||||||
|
<a class="save icon icon-floppy" href="" title="Save" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
|
||||||
|
isEditable = ->
|
||||||
|
return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1
|
||||||
|
|
||||||
|
save = ->
|
||||||
|
$model.$modelValue.subject = $scope.item.subject
|
||||||
|
$loading.start($el.find('.save-container'))
|
||||||
|
promise = $repo.save($model.$modelValue)
|
||||||
|
promise.then ->
|
||||||
|
$confirm.notify("success")
|
||||||
|
$rootscope.$broadcast("history:reload")
|
||||||
|
$el.find('.edit-subject').hide()
|
||||||
|
$el.find('.view-subject').show()
|
||||||
|
promise.then null, ->
|
||||||
|
$confirm.notify("error")
|
||||||
|
promise.finally ->
|
||||||
|
$loading.finish($el.find('.save-container'))
|
||||||
|
|
||||||
|
$el.click ->
|
||||||
|
return if not isEditable()
|
||||||
|
$el.find('.edit-subject').show()
|
||||||
|
$el.find('.view-subject').hide()
|
||||||
|
$el.find('input').focus()
|
||||||
|
|
||||||
|
$el.on "click", ".save", ->
|
||||||
|
save()
|
||||||
|
|
||||||
|
$el.on "keyup", "input", ->
|
||||||
|
if event.keyCode == 13
|
||||||
|
save()
|
||||||
|
else if event.keyCode == 27
|
||||||
|
$model.$modelValue.revert()
|
||||||
|
$el.find('div.edit-subject').hide()
|
||||||
|
$el.find('div.view-subject').show()
|
||||||
|
|
||||||
|
$el.find('div.edit-subject').hide()
|
||||||
|
$el.find('div.view-subject span.edit').hide()
|
||||||
|
|
||||||
|
|
||||||
|
$scope.$watch $attrs.ngModel, (value) ->
|
||||||
|
return if not value
|
||||||
|
$scope.item = value
|
||||||
|
|
||||||
|
if not isEditable()
|
||||||
|
$el.find('.view-subject .edit').remove()
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: link
|
||||||
|
restrict: "EA"
|
||||||
|
require: "ngModel"
|
||||||
|
template: template
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading",
|
||||||
|
EditableSubjectDirective])
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## Editable subject directive
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
EditableDescriptionDirective = ($window, $document, $rootscope, $repo, $confirm, $compile, $loading) ->
|
||||||
|
template = """
|
||||||
|
<div class="view-description">
|
||||||
|
<section class="us-content wysiwyg"
|
||||||
|
tg-bind-html="item.description_html || noDescriptionMsg"></section>
|
||||||
|
<span class="edit icon icon-edit" href="" title="Edit" />
|
||||||
|
</div>
|
||||||
|
<div class="edit-description">
|
||||||
|
<textarea placeholder="Write a description of your user story"
|
||||||
|
ng-model="item.description"
|
||||||
|
tg-markitup="tg-markitup"></textarea>
|
||||||
|
<span class="save-container">
|
||||||
|
<a class="save icon icon-floppy" href="" title="Save" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
""" # TODO: i18n
|
||||||
|
noDescriptionMegEditMode = """
|
||||||
|
<p class="no-description editable">
|
||||||
|
No description yet, why don't you add a good one clicking here?
|
||||||
|
</p>
|
||||||
|
""" # TODO: i18n
|
||||||
|
noDescriptionMegReadMode = """
|
||||||
|
<p class="no-description">
|
||||||
|
No description
|
||||||
|
</p>
|
||||||
|
""" # TODO: i18n
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
$el.find('.edit-description').hide()
|
||||||
|
$el.find('.view-description .edit').hide()
|
||||||
|
|
||||||
|
isEditable = ->
|
||||||
|
return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1
|
||||||
|
|
||||||
|
getSelectedText = ->
|
||||||
|
if $window.getSelection
|
||||||
|
return $window.getSelection().toString()
|
||||||
|
else if $document.selection
|
||||||
|
return $document.selection.createRange().text
|
||||||
|
return null
|
||||||
|
|
||||||
|
$el.on "mouseup", ".view-description", (event) ->
|
||||||
|
# We want to dettect the a inside the div so we use the target and
|
||||||
|
# not the currentTarget
|
||||||
|
target = angular.element(event.target)
|
||||||
|
return if not isEditable()
|
||||||
|
return if target.is('a')
|
||||||
|
return if getSelectedText()
|
||||||
|
|
||||||
|
$el.find('.edit-description').show()
|
||||||
|
$el.find('.view-description').hide()
|
||||||
|
$el.find('textarea').focus()
|
||||||
|
|
||||||
|
$el.on "click", ".save", ->
|
||||||
|
$model.$modelValue.description = $scope.item.description
|
||||||
|
|
||||||
|
$loading.start($el.find('.save-container'))
|
||||||
|
promise = $repo.save($model.$modelValue)
|
||||||
|
promise.then ->
|
||||||
|
$confirm.notify("success")
|
||||||
|
$rootscope.$broadcast("history:reload")
|
||||||
|
$el.find('.edit-description').hide()
|
||||||
|
$el.find('.view-description').show()
|
||||||
|
promise.then null, ->
|
||||||
|
$confirm.notify("error")
|
||||||
|
promise.finally ->
|
||||||
|
$loading.finish($el.find('.save-container'))
|
||||||
|
|
||||||
|
$el.on "keyup", "textarea", ->
|
||||||
|
if event.keyCode == 27
|
||||||
|
$scope.item.revert()
|
||||||
|
$el.find('.edit-description').hide()
|
||||||
|
$el.find('.view-description').show()
|
||||||
|
|
||||||
|
$scope.$watch $attrs.ngModel, (value) ->
|
||||||
|
return if not value
|
||||||
|
$scope.item = value
|
||||||
|
|
||||||
|
if isEditable()
|
||||||
|
$el.find('.view-description .edit').show()
|
||||||
|
$el.find('.view-description .us-content').addClass('editable')
|
||||||
|
$scope.noDescriptionMsg = noDescriptionMegEditMode
|
||||||
|
else
|
||||||
|
$scope.noDescriptionMsg = noDescriptionMegReadMode
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: link
|
||||||
|
restrict: "EA"
|
||||||
|
require: "ngModel"
|
||||||
|
template: template
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgEditableDescription", ["$window", "$document", "$rootScope", "$tgRepo", "$tgConfirm",
|
||||||
|
"$compile", "$tgLoading", EditableDescriptionDirective])
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
@ -377,6 +750,7 @@ ListItemSeverityDirective = ->
|
||||||
template: template
|
template: template
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ListItemTypeDirective = ->
|
ListItemTypeDirective = ->
|
||||||
template = """
|
template = """
|
||||||
<div class="level"></div>
|
<div class="level"></div>
|
||||||
|
|
|
@ -127,16 +127,33 @@ module.directive("lightbox", ["lightboxService", LightboxDirective])
|
||||||
|
|
||||||
# Issue/Userstory blocking message lightbox directive.
|
# Issue/Userstory blocking message lightbox directive.
|
||||||
|
|
||||||
BlockLightboxDirective = (lightboxService) ->
|
BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loading) ->
|
||||||
link = ($scope, $el, $attrs, $model) ->
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
$el.find("h2.title").text($attrs.title)
|
$el.find("h2.title").text($attrs.title)
|
||||||
|
|
||||||
$scope.$on "block", ->
|
$scope.$on "block", ->
|
||||||
|
$el.find(".reason").val($model.$modelValue.blocked_note)
|
||||||
lightboxService.open($el)
|
lightboxService.open($el)
|
||||||
|
|
||||||
$scope.$on "unblock", ->
|
$scope.$on "unblock", (event, model, finishCallback) ->
|
||||||
$model.$modelValue.is_blocked = false
|
item = $model.$modelValue.clone()
|
||||||
$model.$modelValue.blocked_note_html = ""
|
item.is_blocked = false
|
||||||
|
item.blocked_note = ""
|
||||||
|
|
||||||
|
promise = $tgrepo.save(item)
|
||||||
|
promise.then ->
|
||||||
|
$confirm.notify("success")
|
||||||
|
$rootscope.$broadcast("history:reload")
|
||||||
|
$model.$setViewValue(item)
|
||||||
|
finishCallback()
|
||||||
|
|
||||||
|
promise.then null, ->
|
||||||
|
$confirm.notify("error")
|
||||||
|
item.revert()
|
||||||
|
$model.$setViewValue(item)
|
||||||
|
|
||||||
|
promise.finally ->
|
||||||
|
finishCallback()
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
$el.off()
|
$el.off()
|
||||||
|
@ -144,19 +161,34 @@ BlockLightboxDirective = (lightboxService) ->
|
||||||
$el.on "click", ".button-green", (event) ->
|
$el.on "click", ".button-green", (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
$scope.$apply ->
|
item = $model.$modelValue.clone()
|
||||||
$model.$modelValue.is_blocked = true
|
item.is_blocked = true
|
||||||
$model.$modelValue.blocked_note = $el.find(".reason").val()
|
item.blocked_note = $el.find(".reason").val()
|
||||||
|
$model.$setViewValue(item)
|
||||||
|
|
||||||
lightboxService.close($el)
|
$loading.start($el.find(".button-green"))
|
||||||
|
|
||||||
|
promise = $tgrepo.save($model.$modelValue)
|
||||||
|
promise.then ->
|
||||||
|
$confirm.notify("success")
|
||||||
|
$rootscope.$broadcast("history:reload")
|
||||||
|
|
||||||
|
promise.then null, ->
|
||||||
|
$confirm.notify("error")
|
||||||
|
item.revert()
|
||||||
|
$model.$setViewValue(item)
|
||||||
|
|
||||||
|
promise.finally ->
|
||||||
|
$loading.finish($el.find(".button-green"))
|
||||||
|
lightboxService.close($el)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
templateUrl: "/partials/views/modules/lightbox-block.html"
|
templateUrl: "/partials/views/modules/lightbox-block.html"
|
||||||
link:link,
|
link: link
|
||||||
require:"ngModel"
|
require: "ngModel"
|
||||||
}
|
}
|
||||||
|
|
||||||
module.directive("tgLbBlock", ["lightboxService", BlockLightboxDirective])
|
module.directive("tgLbBlock", ["$rootScope", "$tgRepo", "$tgConfirm", "lightboxService", "$tgLoading", BlockLightboxDirective])
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
@ -202,10 +234,10 @@ module.directive("tgBlockingMessageInput", ["$log", BlockingMessageInputDirectiv
|
||||||
|
|
||||||
CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService, $loading) ->
|
CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService, $loading) ->
|
||||||
link = ($scope, $el, attrs) ->
|
link = ($scope, $el, attrs) ->
|
||||||
isNew = true
|
$scope.isNew = true
|
||||||
|
|
||||||
$scope.$on "usform:new", (ctx, projectId, status, statusList) ->
|
$scope.$on "usform:new", (ctx, projectId, status, statusList) ->
|
||||||
isNew = true
|
$scope.isNew = true
|
||||||
$scope.usStatusList = statusList
|
$scope.usStatusList = statusList
|
||||||
|
|
||||||
$scope.us = {
|
$scope.us = {
|
||||||
|
@ -229,7 +261,7 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService,
|
||||||
|
|
||||||
$scope.$on "usform:edit", (ctx, us) ->
|
$scope.$on "usform:edit", (ctx, us) ->
|
||||||
$scope.us = us
|
$scope.us = us
|
||||||
isNew = false
|
$scope.isNew = false
|
||||||
|
|
||||||
# Update texts for edition
|
# Update texts for edition
|
||||||
$el.find(".button-green span").html("Save") #TODO: i18n
|
$el.find(".button-green span").html("Save") #TODO: i18n
|
||||||
|
@ -264,7 +296,7 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService,
|
||||||
|
|
||||||
$loading.start(target)
|
$loading.start(target)
|
||||||
|
|
||||||
if isNew
|
if $scope.isNew
|
||||||
promise = $repo.create("userstories", $scope.us)
|
promise = $repo.create("userstories", $scope.us)
|
||||||
broadcastEvent = "usform:new:success"
|
broadcastEvent = "usform:new:success"
|
||||||
else
|
else
|
||||||
|
|
|
@ -43,6 +43,9 @@ TagsDirective = ->
|
||||||
$ctrl.$formatters.push(formatter)
|
$ctrl.$formatters.push(formatter)
|
||||||
$ctrl.$parsers.push(parser)
|
$ctrl.$parsers.push(parser)
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
require: "ngModel"
|
require: "ngModel"
|
||||||
link: link
|
link: link
|
||||||
|
@ -83,127 +86,133 @@ ColorizeTagsDirective = ->
|
||||||
$scope.$watch $attrs.tgColorizeTags, (tags) ->
|
$scope.$watch $attrs.tgColorizeTags, (tags) ->
|
||||||
render(tags) if tags?
|
render(tags) if tags?
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
return {link: link}
|
return {link: link}
|
||||||
|
|
||||||
module.directive("tgColorizeTags", ColorizeTagsDirective)
|
module.directive("tgColorizeTags", ColorizeTagsDirective)
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## TagLine (possible should be moved as generic directive)
|
## TagLine Directive (for Lightboxes)
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
TagLineDirective = ($log, $rs) ->
|
LbTagLineDirective = ($rs) ->
|
||||||
# Main directive template (rendered by angular)
|
ENTER_KEY = 13
|
||||||
|
|
||||||
template = """
|
template = """
|
||||||
<div class="tags-container"></div>
|
<div class="tags-container"></div>
|
||||||
<input type="text" placeholder="Write tag..." class="tag-input" />
|
<input type="text" placeholder="Write tag..." class="tag-input" />
|
||||||
<a href="" title="Save" class="save icon icon-floppy"></a>
|
<a href="" title="Save" class="save icon icon-floppy hidden"></a>
|
||||||
"""
|
""" # TODO: i18n
|
||||||
|
|
||||||
# Tags template (rendered manually using lodash)
|
# Tags template (rendered manually using lodash)
|
||||||
templateTags = _.template("""
|
templateTags = _.template("""
|
||||||
<% _.each(tags, function(tag) { %>
|
<% _.each(tags, function(tag) { %>
|
||||||
<div class="tag" style="border-left: 5px solid <%- tag.color %>;">
|
<span class="tag" style="border-left: 5px solid <%- tag.color %>;">
|
||||||
<span class="tag-name"><%- tag.name %></span>
|
<span class="tag-name"><%- tag.name %></span>
|
||||||
<% if (editable) { %>
|
|
||||||
<a href="" title="delete tag" class="icon icon-delete"></a>
|
<a href="" title="delete tag" class="icon icon-delete"></a>
|
||||||
<% } %>
|
</span>
|
||||||
</div>
|
<% }); %>
|
||||||
<% }); %>""")
|
""") # TODO: i18n
|
||||||
|
|
||||||
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) ->
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
editable = if $attrs.editable == "true" then true else false
|
## Render
|
||||||
$el.addClass("tags-block")
|
renderTags = (tags, tagsColors) ->
|
||||||
|
ctx = {
|
||||||
|
tags: _.map(tags, (t) -> {name: t, color: tagsColors[t]})
|
||||||
|
}
|
||||||
|
html = templateTags(ctx)
|
||||||
|
$el.find("div.tags-container").html(html)
|
||||||
|
|
||||||
|
showSaveButton = -> $el.find(".save").removeClass("hidden")
|
||||||
|
hideSaveButton = -> $el.find(".save").addClass("hidden")
|
||||||
|
|
||||||
|
resetInput = ->
|
||||||
|
$el.find("input").val("")
|
||||||
|
$el.find("input").autocomplete("close")
|
||||||
|
|
||||||
|
## Aux methods
|
||||||
addValue = (value) ->
|
addValue = (value) ->
|
||||||
value = trim(value)
|
value = trim(value.toLowerCase())
|
||||||
return if value.length <= 0
|
return if value.length == 0
|
||||||
|
|
||||||
tags = _.clone($model.$modelValue, false)
|
tags = _.clone($model.$modelValue, false)
|
||||||
tags = [] if not tags?
|
tags = [] if not tags?
|
||||||
tags.push(value)
|
tags.push(value) if value not in tags
|
||||||
|
|
||||||
$scope.$apply ->
|
$scope.$apply ->
|
||||||
$model.$setViewValue(normalizeTags(tags))
|
$model.$setViewValue(tags)
|
||||||
|
|
||||||
|
deleteValue = (value) ->
|
||||||
|
value = trim(value.toLowerCase())
|
||||||
|
return if value.length == 0
|
||||||
|
|
||||||
|
tags = _.clone($model.$modelValue, false)
|
||||||
|
tags = _.pull(tags, value)
|
||||||
|
|
||||||
|
$scope.$apply ->
|
||||||
|
$model.$setViewValue(tags)
|
||||||
|
|
||||||
saveInputTag = () ->
|
saveInputTag = () ->
|
||||||
input = $el.find('input')
|
value = $el.find("input").val()
|
||||||
|
|
||||||
addValue(input.val())
|
addValue(value)
|
||||||
input.val("")
|
resetInput()
|
||||||
input.autocomplete("close")
|
hideSaveButton()
|
||||||
$el.find('.save').hide()
|
|
||||||
|
|
||||||
$scope.$watch $attrs.ngModel, (val) ->
|
## Events
|
||||||
tags_colors = if $scope.project?.tags_colors? then $scope.project.tags_colors else []
|
$el.on "keypress", "input", (event) ->
|
||||||
renderTags($el, val, editable, tags_colors)
|
return if event.keyCode != ENTER_KEY
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
bindOnce $scope, "projectId", (projectId) ->
|
$el.on "keyup", "input", (event) ->
|
||||||
# If not editable, no tags preloading is needed.
|
target = angular.element(event.currentTarget)
|
||||||
return if not editable
|
|
||||||
|
|
||||||
|
if event.keyCode == ENTER_KEY
|
||||||
|
saveInputTag()
|
||||||
|
else
|
||||||
|
if target.val().length
|
||||||
|
showSaveButton()
|
||||||
|
else
|
||||||
|
hideSaveButton()
|
||||||
|
|
||||||
|
$el.on "click", ".save", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
saveInputTag()
|
||||||
|
|
||||||
|
$el.on "click", ".icon-delete", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
|
||||||
|
value = target.siblings(".tag-name").text()
|
||||||
|
deleteValue(value)
|
||||||
|
|
||||||
|
bindOnce $scope, "project", (project) ->
|
||||||
positioningFunction = (position, elements) ->
|
positioningFunction = (position, elements) ->
|
||||||
menu = elements.element.element
|
menu = elements.element.element
|
||||||
menu.css("width", elements.target.width)
|
menu.css("width", elements.target.width)
|
||||||
menu.css("top", position.top)
|
menu.css("top", position.top)
|
||||||
menu.css("left", position.left)
|
menu.css("left", position.left)
|
||||||
|
|
||||||
$rs.projects.tags(projectId).then (data) ->
|
$el.find("input").autocomplete({
|
||||||
$el.find("input").autocomplete({
|
source: _.keys(project.tags_colors)
|
||||||
source: data
|
position: {
|
||||||
position: {
|
my: "left top",
|
||||||
my: "left top",
|
using: positioningFunction
|
||||||
using: positioningFunction
|
}
|
||||||
}
|
select: (event, ui) ->
|
||||||
select: (event, ui) ->
|
addValue(ui.item.value)
|
||||||
addValue(ui.item.value)
|
ui.item.value = ""
|
||||||
ui.item.value = ""
|
})
|
||||||
})
|
|
||||||
|
|
||||||
if not editable
|
$scope.$watch $attrs.ngModel, (tags) ->
|
||||||
$el.find("input").remove()
|
tagsColors = $scope.project?.tags_colors or []
|
||||||
|
renderTags(tags, tagsColors)
|
||||||
|
|
||||||
$el.on "keypress", "input", (event) ->
|
$scope.$on "$destroy", ->
|
||||||
return if event.keyCode != 13
|
$el.off()
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
$el.on "keyup", "input", (event) ->
|
|
||||||
target = angular.element(event.currentTarget)
|
|
||||||
|
|
||||||
if event.keyCode == 13
|
|
||||||
saveInputTag()
|
|
||||||
else if target.val().length
|
|
||||||
$el.find('.save').show()
|
|
||||||
else
|
|
||||||
$el.find('.save').hide()
|
|
||||||
|
|
||||||
$el.on "click", ".save", saveInputTag
|
|
||||||
|
|
||||||
$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 {
|
return {
|
||||||
link:link,
|
link:link,
|
||||||
|
@ -211,4 +220,198 @@ TagLineDirective = ($log, $rs) ->
|
||||||
template: template
|
template: template
|
||||||
}
|
}
|
||||||
|
|
||||||
module.directive("tgTagLine", ["$log", "$tgResources", TagLineDirective])
|
module.directive("tgLbTagLine", ["$tgResources", LbTagLineDirective])
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## TagLine Directive (for detail pages)
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
TagLineDirective = ($rootScope, $repo, $rs, $confirm) ->
|
||||||
|
ENTER_KEY = 13
|
||||||
|
ESC_KEY = 27
|
||||||
|
|
||||||
|
template = """
|
||||||
|
<div class="tags-container"></div>
|
||||||
|
<a href="#" class="add-tag hidden" title="Add tag">
|
||||||
|
<span class="icon icon-plus"></span>
|
||||||
|
<span class="add-tag-text">Add tag</span>
|
||||||
|
</a>
|
||||||
|
<input type="text" placeholder="Write tag..." class="tag-input hidden" />
|
||||||
|
<a href="" title="Save" class="save icon icon-floppy hidden"></a>
|
||||||
|
""" # TODO: i18n
|
||||||
|
|
||||||
|
# Tags template (rendered manually using lodash)
|
||||||
|
templateTags = _.template("""
|
||||||
|
<% _.each(tags, function(tag) { %>
|
||||||
|
<span class="tag" style="border-left: 5px solid <%- tag.color %>;">
|
||||||
|
<span class="tag-name"><%- tag.name %></span>
|
||||||
|
<% if (isEditable) { %>
|
||||||
|
<a href="" title="delete tag" class="icon icon-delete"></a>
|
||||||
|
<% } %>
|
||||||
|
</span>
|
||||||
|
<% }); %>
|
||||||
|
""") # TODO: i18n
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
isEditable = ->
|
||||||
|
return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1
|
||||||
|
|
||||||
|
## Render
|
||||||
|
renderTags = (tags, tagsColors) ->
|
||||||
|
ctx = {
|
||||||
|
tags: _.map(tags, (t) -> {name: t, color: tagsColors[t]})
|
||||||
|
isEditable: isEditable()
|
||||||
|
}
|
||||||
|
html = templateTags(ctx)
|
||||||
|
$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")
|
||||||
|
hideInput = -> $el.find("input").addClass("hidden")
|
||||||
|
resetInput = ->
|
||||||
|
$el.find("input").val("")
|
||||||
|
$el.find("input").autocomplete("close")
|
||||||
|
|
||||||
|
## Aux methods
|
||||||
|
addValue = (value) ->
|
||||||
|
value = trim(value.toLowerCase())
|
||||||
|
return if value.length == 0
|
||||||
|
|
||||||
|
tags = _.clone($model.$modelValue.tags, false)
|
||||||
|
tags = [] if not tags?
|
||||||
|
tags.push(value) if value not in tags
|
||||||
|
|
||||||
|
model = $model.$modelValue.clone()
|
||||||
|
model.tags = tags
|
||||||
|
$model.$setViewValue(model)
|
||||||
|
|
||||||
|
onSuccess = ->
|
||||||
|
$rootScope.$broadcast("history:reload")
|
||||||
|
onError = ->
|
||||||
|
$confirm.notify("error")
|
||||||
|
model.revert()
|
||||||
|
$model.$setViewValue(model)
|
||||||
|
$repo.save(model).then(onSuccess, onError)
|
||||||
|
|
||||||
|
deleteValue = (value) ->
|
||||||
|
value = trim(value.toLowerCase())
|
||||||
|
return if value.length == 0
|
||||||
|
|
||||||
|
tags = _.clone($model.$modelValue.tags, false)
|
||||||
|
tags = _.pull(tags, value)
|
||||||
|
|
||||||
|
model = $model.$modelValue.clone()
|
||||||
|
model.tags = tags
|
||||||
|
$model.$setViewValue(model)
|
||||||
|
|
||||||
|
onSuccess = ->
|
||||||
|
$rootScope.$broadcast("history:reload")
|
||||||
|
onError = ->
|
||||||
|
$confirm.notify("error")
|
||||||
|
model.revert()
|
||||||
|
$model.$setViewValue(model)
|
||||||
|
$repo.save(model).then(onSuccess, onError)
|
||||||
|
|
||||||
|
saveInputTag = () ->
|
||||||
|
value = $el.find("input").val()
|
||||||
|
|
||||||
|
addValue(value)
|
||||||
|
resetInput()
|
||||||
|
hideSaveButton()
|
||||||
|
|
||||||
|
## Events
|
||||||
|
$el.on "keypress", "input", (event) ->
|
||||||
|
return if event.keyCode not in [ENTER_KEY, ESC_KEY]
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
$el.on "keyup", "input", (event) ->
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
|
||||||
|
if event.keyCode == ENTER_KEY
|
||||||
|
saveInputTag()
|
||||||
|
else if event.keyCode == ESC_KEY
|
||||||
|
resetInput()
|
||||||
|
hideInput()
|
||||||
|
hideSaveButton()
|
||||||
|
showAddTagButton()
|
||||||
|
else
|
||||||
|
if target.val().length
|
||||||
|
showSaveButton()
|
||||||
|
else
|
||||||
|
hideSaveButton()
|
||||||
|
|
||||||
|
$el.on "click", ".save", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
saveInputTag()
|
||||||
|
|
||||||
|
$el.on "click", ".add-tag", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
hideAddTagButton()
|
||||||
|
showInput()
|
||||||
|
|
||||||
|
$el.on "click", ".icon-delete", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
|
||||||
|
value = target.siblings(".tag-name").text()
|
||||||
|
deleteValue(value)
|
||||||
|
|
||||||
|
bindOnce $scope, "project", (project) ->
|
||||||
|
if not isEditable()
|
||||||
|
renderInReadModeOnly()
|
||||||
|
return
|
||||||
|
|
||||||
|
showAddTagButton()
|
||||||
|
|
||||||
|
positioningFunction = (position, elements) ->
|
||||||
|
menu = elements.element.element
|
||||||
|
menu.css("width", elements.target.width)
|
||||||
|
menu.css("top", position.top)
|
||||||
|
menu.css("left", position.left)
|
||||||
|
|
||||||
|
$el.find("input").autocomplete({
|
||||||
|
source: _.keys(project.tags_colors)
|
||||||
|
position: {
|
||||||
|
my: "left top",
|
||||||
|
using: positioningFunction
|
||||||
|
}
|
||||||
|
select: (event, ui) ->
|
||||||
|
addValue(ui.item.value)
|
||||||
|
ui.item.value = ""
|
||||||
|
})
|
||||||
|
|
||||||
|
$scope.$watch $attrs.ngModel, (model) ->
|
||||||
|
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"
|
||||||
|
template: template
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgTagLine", ["$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", TagLineDirective])
|
||||||
|
|
|
@ -32,7 +32,7 @@ tgMarkitupDirective = ($rootscope, $rs, $tr) ->
|
||||||
previewTemplate = _.template("""
|
previewTemplate = _.template("""
|
||||||
<div class="preview">
|
<div class="preview">
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a href="#" title="Edit">Edit</a>
|
<a href="#" title="Edit" class="icon icon-edit edit"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="content wysiwyg">
|
<div class="content wysiwyg">
|
||||||
<%= data %>
|
<%= data %>
|
||||||
|
|
|
@ -46,11 +46,12 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
"$log",
|
"$log",
|
||||||
"$appTitle",
|
"$appTitle",
|
||||||
"$tgAnalytics",
|
"$tgAnalytics",
|
||||||
"$tgNavUrls"
|
"$tgNavUrls",
|
||||||
|
"tgLoader"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
|
||||||
@log, @appTitle, @analytics, @navUrls) ->
|
@log, @appTitle, @analytics, @navUrls, tgLoader) ->
|
||||||
@scope.issueRef = @params.issueref
|
@scope.issueRef = @params.issueref
|
||||||
@scope.sectionName = "Issue Details"
|
@scope.sectionName = "Issue Details"
|
||||||
@.initializeEventHandlers()
|
@.initializeEventHandlers()
|
||||||
|
@ -60,11 +61,12 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
# On Success
|
# On Success
|
||||||
promise.then =>
|
promise.then =>
|
||||||
@appTitle.set(@scope.issue.subject + " - " + @scope.project.name)
|
@appTitle.set(@scope.issue.subject + " - " + @scope.project.name)
|
||||||
|
@.initializeOnDeleteGoToUrl()
|
||||||
|
tgLoader.pageLoaded()
|
||||||
|
|
||||||
# On Error
|
# On Error
|
||||||
promise.then null, @.onInitialDataError.bind(@)
|
promise.then null, @.onInitialDataError.bind(@)
|
||||||
|
|
||||||
|
|
||||||
initializeEventHandlers: ->
|
initializeEventHandlers: ->
|
||||||
@scope.$on "attachment:create", =>
|
@scope.$on "attachment:create", =>
|
||||||
@rootscope.$broadcast("history:reload")
|
@rootscope.$broadcast("history:reload")
|
||||||
|
@ -81,6 +83,13 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
@rootscope.$broadcast("history:reload")
|
@rootscope.$broadcast("history:reload")
|
||||||
@.loadIssue()
|
@.loadIssue()
|
||||||
|
|
||||||
|
initializeOnDeleteGoToUrl: ->
|
||||||
|
ctx = {project: @scope.project.slug}
|
||||||
|
if @scope.project.is_issues_activated
|
||||||
|
@scope.onDeleteGoToUrl = @navUrls.resolve("project-issues", ctx)
|
||||||
|
else
|
||||||
|
@scope.onDeleteGoToUrl = @navUrls.resolve("project", ctx)
|
||||||
|
|
||||||
loadProject: ->
|
loadProject: ->
|
||||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||||
@scope.project = project
|
@scope.project = project
|
||||||
|
@ -130,250 +139,432 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
.then(=> @.loadUsersAndRoles())
|
.then(=> @.loadUsersAndRoles())
|
||||||
.then(=> @.loadIssue())
|
.then(=> @.loadIssue())
|
||||||
|
|
||||||
block: ->
|
|
||||||
@rootscope.$broadcast("block", @scope.issue)
|
|
||||||
|
|
||||||
unblock: ->
|
|
||||||
@rootscope.$broadcast("unblock", @scope.issue)
|
|
||||||
|
|
||||||
delete: ->
|
|
||||||
# TODO: i18n
|
|
||||||
title = "Delete Issue"
|
|
||||||
message = @scope.issue.subject
|
|
||||||
|
|
||||||
@confirm.askOnDelete(title, message).then (finish) =>
|
|
||||||
promise = @.repo.remove(@scope.issue)
|
|
||||||
promise.then =>
|
|
||||||
finish()
|
|
||||||
@location.path(@navUrls.resolve("project-issues", {project: @scope.project.slug}))
|
|
||||||
promise.then null, =>
|
|
||||||
finish(false)
|
|
||||||
@confirm.notify("error")
|
|
||||||
|
|
||||||
module.controller("IssueDetailController", IssueDetailController)
|
module.controller("IssueDetailController", IssueDetailController)
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Issue Main Directive
|
## Issue status display directive
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
IssueDirective = ($tgrepo, $log, $location, $confirm, $navUrls, $loading) ->
|
IssueStatusDisplayDirective = ->
|
||||||
linkSidebar = ($scope, $el, $attrs, $ctrl) ->
|
# Display if a Issue is open or closed and its issueboard status.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# tg-issue-status-display(ng-model="issue")
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# - Issue object (ng-model)
|
||||||
|
# - scope.statusById object
|
||||||
|
|
||||||
|
template = _.template("""
|
||||||
|
<span>
|
||||||
|
<% if (status.is_closed) { %>
|
||||||
|
Closed
|
||||||
|
<% } else { %>
|
||||||
|
Open
|
||||||
|
<% } %>
|
||||||
|
</span>
|
||||||
|
<span class="us-detail-status" style="color:<%= status.color %>">
|
||||||
|
<%= status.name %>
|
||||||
|
</span>
|
||||||
|
""") # TODO: i18n
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
$ctrl = $el.controller()
|
render = (issue) ->
|
||||||
linkSidebar($scope, $el, $attrs, $ctrl)
|
|
||||||
|
|
||||||
if $el.is("form")
|
|
||||||
form = $el.checksley()
|
|
||||||
|
|
||||||
$el.on "click", ".save-issue", (event) ->
|
|
||||||
if not form.validate()
|
|
||||||
return
|
|
||||||
|
|
||||||
onSuccess = ->
|
|
||||||
$loading.finish(target)
|
|
||||||
$confirm.notify("success")
|
|
||||||
ctx = {
|
|
||||||
project: $scope.project.slug
|
|
||||||
ref: $scope.issue.ref
|
|
||||||
}
|
|
||||||
$location.path($navUrls.resolve("project-issues-detail", ctx))
|
|
||||||
|
|
||||||
onError = ->
|
|
||||||
$loading.finish(target)
|
|
||||||
$confirm.notify("error")
|
|
||||||
|
|
||||||
target = angular.element(event.currentTarget)
|
|
||||||
$loading.start(target)
|
|
||||||
$tgrepo.save($scope.issue).then(onSuccess, onError)
|
|
||||||
|
|
||||||
return {link:link}
|
|
||||||
|
|
||||||
module.directive("tgIssueDetail", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm", "$tgNavUrls",
|
|
||||||
"$tgLoading", IssueDirective])
|
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
## Issue status directive
|
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
IssueStatusDirective = () ->
|
|
||||||
# TODO: i18n
|
|
||||||
template = _.template("""
|
|
||||||
<h1>
|
|
||||||
<span>
|
|
||||||
<% if (status.is_closed) { %>
|
|
||||||
Closed
|
|
||||||
<% } else { %>
|
|
||||||
Open
|
|
||||||
<% } %>
|
|
||||||
</span>
|
|
||||||
<span class="us-detail-status" style="color:<%= status.color %>"><%= status.name %></span>
|
|
||||||
</h1>
|
|
||||||
<div class="us-created-by">
|
|
||||||
<div class="user-avatar">
|
|
||||||
<img src="<%= owner.photo %>" alt="<%- owner.full_name_display %>" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="created-by">
|
|
||||||
<span class="created-title">Created by <%- owner.full_name_display %></span>
|
|
||||||
<span class="created-date"><%- date %></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="issue-data">
|
|
||||||
<div class="type-data <% if (editable) { %>clickable<% } %>">
|
|
||||||
<span class="level" style="background-color:<%= type.color %>"></span>
|
|
||||||
<span class="type-status"><%= type.name %></span>
|
|
||||||
<% if (editable) { %>
|
|
||||||
<span class="icon icon-arrow-bottom"></span>
|
|
||||||
<% } %>
|
|
||||||
<span class="level-name">type</span>
|
|
||||||
</div>
|
|
||||||
<div class="severity-data <% if (editable) { %>clickable<% } %>">
|
|
||||||
<span class="level" style="background-color:<%= severity.color %>"></span>
|
|
||||||
<span class="severity-status"><%= severity.name %></span>
|
|
||||||
<% if (editable) { %>
|
|
||||||
<span class="icon icon-arrow-bottom"></span>
|
|
||||||
<% } %>
|
|
||||||
<span class="level-name">severity</span>
|
|
||||||
</div>
|
|
||||||
<div class="priority-data <% if (editable) { %>clickable<% } %>">
|
|
||||||
<span class="level" style="background-color:<%= priority.color %>"></span>
|
|
||||||
<span class="priority-status"><%= priority.name %></span>
|
|
||||||
<% if (editable) { %>
|
|
||||||
<span class="icon icon-arrow-bottom"></span>
|
|
||||||
<% } %>
|
|
||||||
<span class="level-name">priority</span>
|
|
||||||
</div>
|
|
||||||
<div class="status-data <% if (editable) { %>clickable<% } %>">
|
|
||||||
<span class="level" style="background-color:<%= status.color %>"></span>
|
|
||||||
<span class="status-status"><%= status.name %></span>
|
|
||||||
<% if (editable) { %>
|
|
||||||
<span class="icon icon-arrow-bottom"></span>
|
|
||||||
<% } %>
|
|
||||||
<span class="level-name">status</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
""")
|
|
||||||
selectionTypeTemplate = _.template("""
|
|
||||||
<ul class="popover pop-type">
|
|
||||||
<% _.each(types, function(type) { %>
|
|
||||||
<li><a href="" class="type" title="<%- type.name %>"
|
|
||||||
data-type-id="<%- type.id %>"><%- type.name %></a></li>
|
|
||||||
<% }); %>
|
|
||||||
</ul>
|
|
||||||
""")
|
|
||||||
selectionSeverityTemplate = _.template("""
|
|
||||||
<ul class="popover pop-severity">
|
|
||||||
<% _.each(severities, function(severity) { %>
|
|
||||||
<li><a href="" class="severity" title="<%- severity.name %>"
|
|
||||||
data-severity-id="<%- severity.id %>"><%- severity.name %></a></li>
|
|
||||||
<% }); %>
|
|
||||||
</ul>
|
|
||||||
""")
|
|
||||||
selectionPriorityTemplate = _.template("""
|
|
||||||
<ul class="popover pop-priority">
|
|
||||||
<% _.each(priorities, function(priority) { %>
|
|
||||||
<li><a href="" class="priority" title="<%- priority.name %>"
|
|
||||||
data-priority-id="<%- priority.id %>"><%- priority.name %></a></li>
|
|
||||||
<% }); %>
|
|
||||||
</ul>
|
|
||||||
""")
|
|
||||||
selectionStatusTemplate = _.template("""
|
|
||||||
<ul class="popover pop-status">
|
|
||||||
<% _.each(statuses, function(status) { %>
|
|
||||||
<li><a href="" class="status" title="<%- status.name %>"
|
|
||||||
data-status-id="<%- status.id %>"><%- status.name %></a></li>
|
|
||||||
<% }); %>
|
|
||||||
</ul>
|
|
||||||
""")
|
|
||||||
|
|
||||||
link = ($scope, $el, $attrs, $model) ->
|
|
||||||
editable = $attrs.editable?
|
|
||||||
|
|
||||||
renderIssuestatus = (issue) ->
|
|
||||||
owner = $scope.usersById?[issue.owner]
|
|
||||||
date = moment(issue.created_date).format("DD MMM YYYY HH:mm")
|
|
||||||
type = $scope.typeById[issue.type]
|
|
||||||
status = $scope.statusById[issue.status]
|
|
||||||
severity = $scope.severityById[issue.severity]
|
|
||||||
priority = $scope.priorityById[issue.priority]
|
|
||||||
html = template({
|
html = template({
|
||||||
owner: owner
|
status: $scope.statusById[issue.status]
|
||||||
date: date
|
|
||||||
editable: editable
|
|
||||||
status: status
|
|
||||||
severity: severity
|
|
||||||
priority: priority
|
|
||||||
type: type
|
|
||||||
})
|
})
|
||||||
$el.html(html)
|
$el.html(html)
|
||||||
$el.find(".type-data").append(selectionTypeTemplate({types:$scope.typeList}))
|
|
||||||
$el.find(".severity-data").append(selectionSeverityTemplate({severities:$scope.severityList}))
|
|
||||||
$el.find(".priority-data").append(selectionPriorityTemplate({priorities:$scope.priorityList}))
|
|
||||||
$el.find(".status-data").append(selectionStatusTemplate({statuses:$scope.statusList}))
|
|
||||||
|
|
||||||
$scope.$watch $attrs.ngModel, (issue) ->
|
$scope.$watch $attrs.ngModel, (issue) ->
|
||||||
if issue?
|
render(issue) if issue?
|
||||||
renderIssuestatus(issue)
|
|
||||||
|
|
||||||
if editable
|
$scope.$on "$destroy", ->
|
||||||
$el.on "click", ".type-data", (event) ->
|
$el.off()
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
$el.find(".pop-type").popover().open()
|
|
||||||
|
|
||||||
$el.on "click", ".type", (event) ->
|
return {
|
||||||
event.preventDefault()
|
link: link
|
||||||
event.stopPropagation()
|
restrict: "EA"
|
||||||
target = angular.element(event.currentTarget)
|
require: "ngModel"
|
||||||
$model.$modelValue.type = target.data("type-id")
|
}
|
||||||
renderIssuestatus($model.$modelValue)
|
|
||||||
$.fn.popover().closeAll()
|
|
||||||
|
|
||||||
$el.on "click", ".severity-data", (event) ->
|
module.directive("tgIssueStatusDisplay", IssueStatusDisplayDirective)
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
$el.find(".pop-severity").popover().open()
|
|
||||||
|
|
||||||
$el.on "click", ".severity", (event) ->
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
target = angular.element(event.currentTarget)
|
|
||||||
$model.$modelValue.severity = target.data("severity-id")
|
|
||||||
renderIssuestatus($model.$modelValue)
|
|
||||||
$.fn.popover().closeAll()
|
|
||||||
|
|
||||||
$el.on "click", ".priority-data", (event) ->
|
#############################################################################
|
||||||
event.preventDefault()
|
## Issue status button directive
|
||||||
event.stopPropagation()
|
#############################################################################
|
||||||
$el.find(".pop-priority").popover().open()
|
|
||||||
|
|
||||||
$el.on "click", ".priority", (event) ->
|
IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
||||||
event.preventDefault()
|
# Display the status of Issue and you can edit it.
|
||||||
event.stopPropagation()
|
#
|
||||||
target = angular.element(event.currentTarget)
|
# Example:
|
||||||
$model.$modelValue.priority = target.data("priority-id")
|
# tg-issue-status-button(ng-model="issue")
|
||||||
renderIssuestatus($model.$modelValue)
|
#
|
||||||
$.fn.popover().closeAll()
|
# Requirements:
|
||||||
|
# - Issue object (ng-model)
|
||||||
|
# - scope.statusById object
|
||||||
|
# - $scope.project.my_permissions
|
||||||
|
|
||||||
$el.on "click", ".status-data", (event) ->
|
template = _.template("""
|
||||||
event.preventDefault()
|
<div class="status-data <% if(editable){ %>clickable<% }%>">
|
||||||
event.stopPropagation()
|
<span class="level" style="background-color:<%= status.color %>"></span>
|
||||||
$el.find(".pop-status").popover().open()
|
<span class="status-status"><%= status.name %></span>
|
||||||
|
<% if(editable){ %><span class="icon icon-arrow-bottom"></span><% }%>
|
||||||
|
<span class="level-name">status</span>
|
||||||
|
|
||||||
$el.on "click", ".status", (event) ->
|
<ul class="popover pop-status">
|
||||||
event.preventDefault()
|
<% _.each(statuses, function(st) { %>
|
||||||
event.stopPropagation()
|
<li><a href="" class="status" title="<%- st.name %>"
|
||||||
target = angular.element(event.currentTarget)
|
data-status-id="<%- st.id %>"><%- st.name %></a></li>
|
||||||
$model.$modelValue.status = target.data("status-id")
|
<% }); %>
|
||||||
renderIssuestatus($model.$modelValue)
|
</ul>
|
||||||
$.fn.popover().closeAll()
|
</div>
|
||||||
|
""") #TODO: i18n
|
||||||
|
|
||||||
return {link:link, require:"ngModel"}
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
isEditable = ->
|
||||||
|
return $scope.project.my_permissions.indexOf("modify_issue") != -1
|
||||||
|
|
||||||
module.directive("tgIssueStatus", IssueStatusDirective)
|
render = (issue) =>
|
||||||
|
status = $scope.statusById[issue.status]
|
||||||
|
|
||||||
|
html = template({
|
||||||
|
status: status
|
||||||
|
statuses: $scope.statusList
|
||||||
|
editable: isEditable()
|
||||||
|
})
|
||||||
|
$el.html(html)
|
||||||
|
|
||||||
|
$el.on "click", ".status-data", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return if not isEditable()
|
||||||
|
|
||||||
|
$el.find(".pop-status").popover().open()
|
||||||
|
|
||||||
|
$el.on "click", ".status", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return if not isEditable()
|
||||||
|
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
|
||||||
|
$.fn.popover().closeAll()
|
||||||
|
|
||||||
|
issue = $model.$modelValue.clone()
|
||||||
|
issue.status = target.data("status-id")
|
||||||
|
$model.$setViewValue(issue)
|
||||||
|
|
||||||
|
$scope.$apply()
|
||||||
|
|
||||||
|
onSuccess = ->
|
||||||
|
$confirm.notify("success")
|
||||||
|
$rootScope.$broadcast("history:reload")
|
||||||
|
$loading.finish($el.find(".level-name"))
|
||||||
|
onError = ->
|
||||||
|
$confirm.notify("error")
|
||||||
|
issue.revert()
|
||||||
|
$model.$setViewValue(issue)
|
||||||
|
$loading.finish($el.find(".level-name"))
|
||||||
|
|
||||||
|
$loading.start($el.find(".level-name"))
|
||||||
|
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||||
|
|
||||||
|
$scope.$watch $attrs.ngModel, (issue) ->
|
||||||
|
render(issue) if issue
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: link
|
||||||
|
restrict: "EA"
|
||||||
|
require: "ngModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgIssueStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", IssueStatusButtonDirective])
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## Issue type button directive
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
||||||
|
# Display the type of Issue and you can edit it.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# tg-issue-type-button(ng-model="issue")
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# - Issue object (ng-model)
|
||||||
|
# - scope.typeById object
|
||||||
|
# - $scope.project.my_permissions
|
||||||
|
|
||||||
|
template = _.template("""
|
||||||
|
<div class="type-data <% if(editable){ %>clickable<% }%>">
|
||||||
|
<span class="level" style="background-color:<%= type.color %>"></span>
|
||||||
|
<span class="type-type"><%= type.name %></span>
|
||||||
|
<% if(editable){ %><span class="icon icon-arrow-bottom"></span><% }%>
|
||||||
|
<span class="level-name">type</span>
|
||||||
|
|
||||||
|
<ul class="popover pop-type">
|
||||||
|
<% _.each(typees, function(tp) { %>
|
||||||
|
<li><a href="" class="type" title="<%- tp.name %>"
|
||||||
|
data-type-id="<%- tp.id %>"><%- tp.name %></a></li>
|
||||||
|
<% }); %>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
""") #TODO: i18n
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
isEditable = ->
|
||||||
|
return $scope.project.my_permissions.indexOf("modify_issue") != -1
|
||||||
|
|
||||||
|
render = (issue) =>
|
||||||
|
type = $scope.typeById[issue.type]
|
||||||
|
|
||||||
|
html = template({
|
||||||
|
type: type
|
||||||
|
typees: $scope.typeList
|
||||||
|
editable: isEditable()
|
||||||
|
})
|
||||||
|
$el.html(html)
|
||||||
|
|
||||||
|
$el.on "click", ".type-data", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return if not isEditable()
|
||||||
|
|
||||||
|
$el.find(".pop-type").popover().open()
|
||||||
|
|
||||||
|
$el.on "click", ".type", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return if not isEditable()
|
||||||
|
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
|
||||||
|
$.fn.popover().closeAll()
|
||||||
|
|
||||||
|
issue = $model.$modelValue.clone()
|
||||||
|
issue.type = target.data("type-id")
|
||||||
|
$model.$setViewValue(issue)
|
||||||
|
|
||||||
|
$scope.$apply()
|
||||||
|
|
||||||
|
onSuccess = ->
|
||||||
|
$confirm.notify("success")
|
||||||
|
$rootScope.$broadcast("history:reload")
|
||||||
|
$loading.finish($el.find(".level-name"))
|
||||||
|
onError = ->
|
||||||
|
$confirm.notify("error")
|
||||||
|
issue.revert()
|
||||||
|
$model.$setViewValue(issue)
|
||||||
|
$loading.finish($el.find(".level-name"))
|
||||||
|
$loading.start($el.find(".level-name"))
|
||||||
|
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||||
|
|
||||||
|
$scope.$watch $attrs.ngModel, (issue) ->
|
||||||
|
render(issue) if issue
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: link
|
||||||
|
restrict: "EA"
|
||||||
|
require: "ngModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgIssueTypeButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", IssueTypeButtonDirective])
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## Issue severity button directive
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
||||||
|
# Display the severity of Issue and you can edit it.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# tg-issue-severity-button(ng-model="issue")
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# - Issue object (ng-model)
|
||||||
|
# - scope.severityById object
|
||||||
|
# - $scope.project.my_permissions
|
||||||
|
|
||||||
|
template = _.template("""
|
||||||
|
<div class="severity-data <% if(editable){ %>clickable<% }%>">
|
||||||
|
<span class="level" style="background-color:<%= severity.color %>"></span>
|
||||||
|
<span class="severity-severity"><%= severity.name %></span>
|
||||||
|
<% if(editable){ %><span class="icon icon-arrow-bottom"></span><% }%>
|
||||||
|
<span class="level-name">severity</span>
|
||||||
|
|
||||||
|
<ul class="popover pop-severity">
|
||||||
|
<% _.each(severityes, function(sv) { %>
|
||||||
|
<li><a href="" class="severity" title="<%- sv.name %>"
|
||||||
|
data-severity-id="<%- sv.id %>"><%- sv.name %></a></li>
|
||||||
|
<% }); %>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
""") #TODO: i18n
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
isEditable = ->
|
||||||
|
return $scope.project.my_permissions.indexOf("modify_issue") != -1
|
||||||
|
|
||||||
|
render = (issue) =>
|
||||||
|
severity = $scope.severityById[issue.severity]
|
||||||
|
|
||||||
|
html = template({
|
||||||
|
severity: severity
|
||||||
|
severityes: $scope.severityList
|
||||||
|
editable: isEditable()
|
||||||
|
})
|
||||||
|
$el.html(html)
|
||||||
|
|
||||||
|
$el.on "click", ".severity-data", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return if not isEditable()
|
||||||
|
|
||||||
|
$el.find(".pop-severity").popover().open()
|
||||||
|
|
||||||
|
$el.on "click", ".severity", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return if not isEditable()
|
||||||
|
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
|
||||||
|
$.fn.popover().closeAll()
|
||||||
|
|
||||||
|
issue = $model.$modelValue.clone()
|
||||||
|
issue.severity = target.data("severity-id")
|
||||||
|
$model.$setViewValue(issue)
|
||||||
|
|
||||||
|
$scope.$apply()
|
||||||
|
|
||||||
|
onSuccess = ->
|
||||||
|
$confirm.notify("success")
|
||||||
|
$rootScope.$broadcast("history:reload")
|
||||||
|
$loading.finish($el.find(".level-name"))
|
||||||
|
onError = ->
|
||||||
|
$confirm.notify("error")
|
||||||
|
issue.revert()
|
||||||
|
$model.$setViewValue(issue)
|
||||||
|
$loading.finish($el.find(".level-name"))
|
||||||
|
$loading.start($el.find(".level-name"))
|
||||||
|
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||||
|
|
||||||
|
$scope.$watch $attrs.ngModel, (issue) ->
|
||||||
|
render(issue) if issue
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: link
|
||||||
|
restrict: "EA"
|
||||||
|
require: "ngModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgIssueSeverityButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", IssueSeverityButtonDirective])
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## Issue priority button directive
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
||||||
|
# Display the priority of Issue and you can edit it.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# tg-issue-priority-button(ng-model="issue")
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# - Issue object (ng-model)
|
||||||
|
# - scope.priorityById object
|
||||||
|
# - $scope.project.my_permissions
|
||||||
|
|
||||||
|
template = _.template("""
|
||||||
|
<div class="priority-data <% if(editable){ %>clickable<% }%>">
|
||||||
|
<span class="level" style="background-color:<%= priority.color %>"></span>
|
||||||
|
<span class="priority-priority"><%= priority.name %></span>
|
||||||
|
<% if(editable){ %><span class="icon icon-arrow-bottom"></span><% }%>
|
||||||
|
<span class="level-name">priority</span>
|
||||||
|
|
||||||
|
<ul class="popover pop-priority">
|
||||||
|
<% _.each(priorityes, function(pr) { %>
|
||||||
|
<li><a href="" class="priority" title="<%- pr.name %>"
|
||||||
|
data-priority-id="<%- pr.id %>"><%- pr.name %></a></li>
|
||||||
|
<% }); %>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
""") #TODO: i18n
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
isEditable = ->
|
||||||
|
return $scope.project.my_permissions.indexOf("modify_issue") != -1
|
||||||
|
|
||||||
|
render = (issue) =>
|
||||||
|
priority = $scope.priorityById[issue.priority]
|
||||||
|
|
||||||
|
html = template({
|
||||||
|
priority: priority
|
||||||
|
priorityes: $scope.priorityList
|
||||||
|
editable: isEditable()
|
||||||
|
})
|
||||||
|
$el.html(html)
|
||||||
|
|
||||||
|
$el.on "click", ".priority-data", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return if not isEditable()
|
||||||
|
|
||||||
|
$el.find(".pop-priority").popover().open()
|
||||||
|
|
||||||
|
$el.on "click", ".priority", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return if not isEditable()
|
||||||
|
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
|
||||||
|
$.fn.popover().closeAll()
|
||||||
|
|
||||||
|
issue = $model.$modelValue.clone()
|
||||||
|
issue.priority = target.data("priority-id")
|
||||||
|
$model.$setViewValue(issue)
|
||||||
|
|
||||||
|
$scope.$apply()
|
||||||
|
|
||||||
|
onSuccess = ->
|
||||||
|
$confirm.notify("success")
|
||||||
|
$rootScope.$broadcast("history:reload")
|
||||||
|
$loading.finish($el.find(".level-name"))
|
||||||
|
onError = ->
|
||||||
|
$confirm.notify("error")
|
||||||
|
issue.revert()
|
||||||
|
$model.$setViewValue(issue)
|
||||||
|
$loading.finish($el.find(".level-name"))
|
||||||
|
$loading.start($el.find(".level-name"))
|
||||||
|
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||||
|
|
||||||
|
$scope.$watch $attrs.ngModel, (issue) ->
|
||||||
|
render(issue) if issue
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: link
|
||||||
|
restrict: "EA"
|
||||||
|
require: "ngModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgIssuePriorityButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", IssuePriorityButtonDirective])
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
|
@ -53,7 +53,7 @@ RelatedTaskRowDirective = ($repo, $compile, $confirm, $rootscope, $loading) ->
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div tg-related-task-assigned-to-inline-edition="task" class="assigned-to">
|
<div tg-related-task-assigned-to-inline-edition="task" class="assigned-to">
|
||||||
<div title="Assigned to" class="task-assignedto">
|
<div title="Assigned to" class="task-assignedto <% if(perms.modify_task) { %>editable<% } %>">
|
||||||
<figure class="avatar"></figure>
|
<figure class="avatar"></figure>
|
||||||
<% if(perms.modify_task) { %>
|
<% if(perms.modify_task) { %>
|
||||||
<span class="icon icon-arrow-bottom"></span>
|
<span class="icon icon-arrow-bottom"></span>
|
||||||
|
@ -166,7 +166,7 @@ RelatedTaskRowDirective = ($repo, $compile, $confirm, $rootscope, $loading) ->
|
||||||
|
|
||||||
return {link:link, require:"ngModel"}
|
return {link:link, require:"ngModel"}
|
||||||
|
|
||||||
module.directive("tgRelatedTaskRow", ["$tgRepo", "$compile", "$tgConfirm", "$rootScope", "$tgLoading", RelatedTaskRowDirective])
|
module.directive("tgRelatedTaskRow", ["$tgRepo", "$compile", "$tgConfirm", "$rootScope", "$tgLoading", "$tgAnalytics", RelatedTaskRowDirective])
|
||||||
|
|
||||||
RelatedTaskCreateFormDirective = ($repo, $compile, $confirm, $tgmodel, $loading, $analytics) ->
|
RelatedTaskCreateFormDirective = ($repo, $compile, $confirm, $tgmodel, $loading, $analytics) ->
|
||||||
template = _.template("""
|
template = _.template("""
|
||||||
|
|
|
@ -45,9 +45,6 @@ resourceProvider = ($repo) ->
|
||||||
service.stats = (projectId) ->
|
service.stats = (projectId) ->
|
||||||
return $repo.queryOneRaw("projects", "#{projectId}/stats")
|
return $repo.queryOneRaw("projects", "#{projectId}/stats")
|
||||||
|
|
||||||
service.tags = (projectId) ->
|
|
||||||
return $repo.queryOneRaw("projects", "#{projectId}/tags")
|
|
||||||
|
|
||||||
service.tagsColors = (id) ->
|
service.tagsColors = (id) ->
|
||||||
return $repo.queryOne("projects", "#{id}/tags_colors")
|
return $repo.queryOne("projects", "#{id}/tags_colors")
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ debounce = @.taiga.debounce
|
||||||
|
|
||||||
CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, lightboxService) ->
|
CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, lightboxService) ->
|
||||||
link = ($scope, $el, attrs) ->
|
link = ($scope, $el, attrs) ->
|
||||||
isNew = true
|
$scope.isNew = true
|
||||||
|
|
||||||
$scope.$on "taskform:new", (ctx, sprintId, usId) ->
|
$scope.$on "taskform:new", (ctx, sprintId, usId) ->
|
||||||
$scope.task = {
|
$scope.task = {
|
||||||
|
@ -37,7 +37,7 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, lightboxService) ->
|
||||||
assigned_to: null
|
assigned_to: null
|
||||||
tags: []
|
tags: []
|
||||||
}
|
}
|
||||||
isNew = true
|
$scope.isNew = true
|
||||||
|
|
||||||
# Update texts for creation
|
# Update texts for creation
|
||||||
$el.find(".button-green span").html("Create") #TODO: i18n
|
$el.find(".button-green span").html("Create") #TODO: i18n
|
||||||
|
@ -46,7 +46,7 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, lightboxService) ->
|
||||||
|
|
||||||
$scope.$on "taskform:edit", (ctx, task) ->
|
$scope.$on "taskform:edit", (ctx, task) ->
|
||||||
$scope.task = task
|
$scope.task = task
|
||||||
isNew = false
|
$scope.isNew = false
|
||||||
|
|
||||||
# Update texts for edition
|
# Update texts for edition
|
||||||
$el.find(".button-green span").html("Save") #TODO: i18n
|
$el.find(".button-green span").html("Save") #TODO: i18n
|
||||||
|
@ -60,7 +60,7 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, lightboxService) ->
|
||||||
if not form.validate()
|
if not form.validate()
|
||||||
return
|
return
|
||||||
|
|
||||||
if isNew
|
if $scope.isNew
|
||||||
promise = $repo.create("tasks", $scope.task)
|
promise = $repo.create("tasks", $scope.task)
|
||||||
broadcastEvent = "taskform:new:success"
|
broadcastEvent = "taskform:new:success"
|
||||||
else
|
else
|
||||||
|
|
|
@ -57,6 +57,7 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
|
|
||||||
promise.then () =>
|
promise.then () =>
|
||||||
@appTitle.set(@scope.task.subject + " - " + @scope.project.name)
|
@appTitle.set(@scope.task.subject + " - " + @scope.project.name)
|
||||||
|
@.initializeOnDeleteGoToUrl()
|
||||||
tgLoader.pageLoaded()
|
tgLoader.pageLoaded()
|
||||||
|
|
||||||
promise.then null, @.onInitialDataError.bind(@)
|
promise.then null, @.onInitialDataError.bind(@)
|
||||||
|
@ -70,6 +71,21 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
@scope.$on "attachment:delete", =>
|
@scope.$on "attachment:delete", =>
|
||||||
@rootscope.$broadcast("history:reload")
|
@rootscope.$broadcast("history:reload")
|
||||||
|
|
||||||
|
initializeOnDeleteGoToUrl: ->
|
||||||
|
ctx = {project: @scope.project.slug}
|
||||||
|
@scope.onDeleteGoToUrl = @navUrls.resolve("project", ctx)
|
||||||
|
if @scope.project.is_backlog_activated
|
||||||
|
if @scope.task.milestone
|
||||||
|
ctx.sprint = @scope.sprint.slug
|
||||||
|
@scope.onDeleteGoToUrl = @navUrls.resolve("project-taskboard", ctx)
|
||||||
|
else if @scope.task.us
|
||||||
|
ctx.ref = @scope.us.ref
|
||||||
|
@scope.onDeleteGoToUrl = @navUrls.resolve("project-userstories-detail", ctx)
|
||||||
|
else if @scope.project.is_kanban_activated
|
||||||
|
if @scope.us
|
||||||
|
ctx.ref = @scope.us.ref
|
||||||
|
@scope.onDeleteGoToUrl = @navUrls.resolve("project-userstories-detail", ctx)
|
||||||
|
|
||||||
loadProject: ->
|
loadProject: ->
|
||||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||||
@scope.project = project
|
@scope.project = project
|
||||||
|
@ -97,14 +113,19 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
ref: @scope.task.neighbors.next.ref
|
ref: @scope.task.neighbors.next.ref
|
||||||
}
|
}
|
||||||
@scope.nextUrl = @navUrls.resolve("project-tasks-detail", ctx)
|
@scope.nextUrl = @navUrls.resolve("project-tasks-detail", ctx)
|
||||||
|
return task
|
||||||
|
|
||||||
if task.milestone
|
loadSprint: ->
|
||||||
@rs.sprints.get(task.project, task.milestone).then (sprint) =>
|
if @scope.task.milestone
|
||||||
@scope.sprint = sprint
|
return @rs.sprints.get(@scope.task.project, @scope.task.milestone).then (sprint) =>
|
||||||
|
@scope.sprint = sprint
|
||||||
|
return sprint
|
||||||
|
|
||||||
if task.user_story
|
loadUserStory: ->
|
||||||
@rs.userstories.get(task.project, task.user_story).then (us) =>
|
if @scope.task.user_story
|
||||||
@scope.us = us
|
return @rs.userstories.get(@scope.task.project, @scope.task.user_story).then (us) =>
|
||||||
|
@scope.us = us
|
||||||
|
return us
|
||||||
|
|
||||||
loadInitialData: ->
|
loadInitialData: ->
|
||||||
params = {
|
params = {
|
||||||
|
@ -119,157 +140,216 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
|
|
||||||
return promise.then(=> @.loadProject())
|
return promise.then(=> @.loadProject())
|
||||||
.then(=> @.loadUsersAndRoles())
|
.then(=> @.loadUsersAndRoles())
|
||||||
.then(=> @.loadTask())
|
.then(=> @.loadTask().then(=> @q.all([@.loadUserStory(),
|
||||||
|
@.loadSprint()])))
|
||||||
block: ->
|
|
||||||
@rootscope.$broadcast("block", @scope.task)
|
|
||||||
|
|
||||||
unblock: ->
|
|
||||||
@rootscope.$broadcast("unblock", @scope.task)
|
|
||||||
|
|
||||||
delete: ->
|
|
||||||
#TODO: i18n
|
|
||||||
title = "Delete Task"
|
|
||||||
message = @scope.task.subject
|
|
||||||
|
|
||||||
@confirm.askOnDelete(title, message).then (finish) =>
|
|
||||||
promise = @.repo.remove(@scope.task)
|
|
||||||
promise.then =>
|
|
||||||
finish()
|
|
||||||
|
|
||||||
if @scope.task.milestone
|
|
||||||
@location.path(@navUrls.resolve("project-taskboard", {project: @scope.project.slug, sprint: @scope.sprint.slug}))
|
|
||||||
else if @scope.us
|
|
||||||
@location.path(@navUrls.resolve("project-userstories-detail", {project: @scope.project.slug, ref: @scope.us.ref}))
|
|
||||||
|
|
||||||
promise.then null, =>
|
|
||||||
finish(false)
|
|
||||||
@confirm.notify("error")
|
|
||||||
|
|
||||||
module.controller("TaskDetailController", TaskDetailController)
|
module.controller("TaskDetailController", TaskDetailController)
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Task Main Directive
|
## Task status display directive
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
TaskDirective = ($tgrepo, $log, $location, $confirm, $navUrls, $loading) ->
|
TaskStatusDisplayDirective = ->
|
||||||
linkSidebar = ($scope, $el, $attrs, $ctrl) ->
|
# Display if a Task is open or closed and its taskboard status.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# tg-task-status-display(ng-model="task")
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# - Task object (ng-model)
|
||||||
|
# - scope.statusById object
|
||||||
|
|
||||||
|
template = _.template("""
|
||||||
|
<span>
|
||||||
|
<% if (status.is_closed) { %>
|
||||||
|
Closed
|
||||||
|
<% } else { %>
|
||||||
|
Open
|
||||||
|
<% } %>
|
||||||
|
</span>
|
||||||
|
<span class="us-detail-status" style="color:<%= status.color %>">
|
||||||
|
<%= status.name %>
|
||||||
|
</span>
|
||||||
|
""") # TODO: i18n
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
$ctrl = $el.controller()
|
render = (task) ->
|
||||||
linkSidebar($scope, $el, $attrs, $ctrl)
|
html = template({
|
||||||
|
status: $scope.statusById[task.status]
|
||||||
|
})
|
||||||
|
$el.html(html)
|
||||||
|
|
||||||
if $el.is("form")
|
$scope.$watch $attrs.ngModel, (task) ->
|
||||||
form = $el.checksley()
|
render(task) if task?
|
||||||
|
|
||||||
$el.on "click", ".save-task", (event) ->
|
$scope.$on "$destroy", ->
|
||||||
if not form.validate()
|
$el.off()
|
||||||
return
|
|
||||||
|
|
||||||
onSuccess = ->
|
return {
|
||||||
$loading.finish(target)
|
link: link
|
||||||
$confirm.notify("success")
|
restrict: "EA"
|
||||||
ctx = {
|
require: "ngModel"
|
||||||
project: $scope.project.slug
|
}
|
||||||
ref: $scope.task.ref
|
|
||||||
}
|
|
||||||
$location.path($navUrls.resolve("project-tasks-detail", ctx))
|
|
||||||
|
|
||||||
onError = ->
|
module.directive("tgTaskStatusDisplay", TaskStatusDisplayDirective)
|
||||||
$loading.finish(target)
|
|
||||||
$confirm.notify("error")
|
|
||||||
|
#############################################################################
|
||||||
|
## Task status button directive
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
||||||
|
# Display the status of Task and you can edit it.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# tg-task-status-button(ng-model="task")
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# - Task object (ng-model)
|
||||||
|
# - scope.statusById object
|
||||||
|
# - $scope.project.my_permissions
|
||||||
|
|
||||||
|
template = _.template("""
|
||||||
|
<div class="status-data <% if(editable){ %>clickable<% }%>">
|
||||||
|
<span class="level" style="background-color:<%= status.color %>"></span>
|
||||||
|
<span class="status-status"><%= status.name %></span>
|
||||||
|
<% if(editable){ %><span class="icon icon-arrow-bottom"></span><% }%>
|
||||||
|
<span class="level-name">status</span>
|
||||||
|
|
||||||
|
<ul class="popover pop-status">
|
||||||
|
<% _.each(statuses, function(st) { %>
|
||||||
|
<li><a href="" class="status" title="<%- st.name %>"
|
||||||
|
data-status-id="<%- st.id %>"><%- st.name %></a></li>
|
||||||
|
<% }); %>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
""") #TODO: i18n
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
isEditable = ->
|
||||||
|
return $scope.project.my_permissions.indexOf("modify_task") != -1
|
||||||
|
|
||||||
|
render = (task) =>
|
||||||
|
status = $scope.statusById[task.status]
|
||||||
|
|
||||||
|
html = template({
|
||||||
|
status: status
|
||||||
|
statuses: $scope.statusList
|
||||||
|
editable: isEditable()
|
||||||
|
})
|
||||||
|
$el.html(html)
|
||||||
|
|
||||||
|
$el.on "click", ".status-data", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return if not isEditable()
|
||||||
|
|
||||||
|
$el.find(".pop-status").popover().open()
|
||||||
|
|
||||||
|
$el.on "click", ".status", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return if not isEditable()
|
||||||
|
|
||||||
target = angular.element(event.currentTarget)
|
target = angular.element(event.currentTarget)
|
||||||
$loading.start(target)
|
|
||||||
$tgrepo.save($scope.task).then(onSuccess, onError)
|
|
||||||
|
|
||||||
return {link:link}
|
$.fn.popover().closeAll()
|
||||||
|
|
||||||
module.directive("tgTaskDetail", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm", "$tgNavUrls",
|
task = $model.$modelValue.clone()
|
||||||
"$tgLoading", TaskDirective])
|
task.status = target.data("status-id")
|
||||||
|
$model.$setViewValue(task)
|
||||||
|
|
||||||
|
$scope.$apply()
|
||||||
|
|
||||||
|
onSuccess = ->
|
||||||
|
$confirm.notify("success")
|
||||||
|
$rootScope.$broadcast("history:reload")
|
||||||
|
$loading.finish($el.find(".level-name"))
|
||||||
|
|
||||||
|
onError = ->
|
||||||
|
$confirm.notify("error")
|
||||||
|
task.revert()
|
||||||
|
$model.$setViewValue(task)
|
||||||
|
$loading.finish($el.find(".level-name"))
|
||||||
|
|
||||||
|
$loading.start($el.find(".level-name"))
|
||||||
|
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||||
|
|
||||||
|
$scope.$watch $attrs.ngModel, (task) ->
|
||||||
|
render(task) if task
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: link
|
||||||
|
restrict: "EA"
|
||||||
|
require: "ngModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgTaskStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading",
|
||||||
|
TaskStatusButtonDirective])
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
|
||||||
## Task status directive
|
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
TaskStatusDirective = () ->
|
|
||||||
#TODO: i18n
|
|
||||||
template = _.template("""
|
template = _.template("""
|
||||||
<h1>
|
<fieldset title="Feeling a bit overwhelmed by a task? Make sure others know about it by clicking on Iocaine when editing a task. It's possible to become immune to this (fictional) deadly poison by consuming small amounts over time just as it's possible to get better at what you do by occasionally taking on extra challenges!">
|
||||||
<span>
|
<label for="is-iocaine"
|
||||||
<% if (status.is_closed) { %>
|
class="button button-gray is-iocaine <% if(isEditable){ %>editable<% }; %> <% if(isIocaine){ %>active<% }; %>">
|
||||||
Closed
|
Iocaine
|
||||||
<% } else { %>
|
</label>
|
||||||
Open
|
<input type="checkbox" id="is-iocaine" name="is-iocaine"/>
|
||||||
<% } %>
|
</fieldset>
|
||||||
<span class="us-detail-status" style="color:<%= status.color %>"><%= status.name %></span>
|
|
||||||
</h1>
|
|
||||||
<div class="us-created-by">
|
|
||||||
<div class="user-avatar">
|
|
||||||
<img src="<%= owner.photo %>" alt="<%- owner.full_name_display %>" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="created-by">
|
|
||||||
<span class="created-title">Created by <%- owner.full_name_display %></span>
|
|
||||||
<span class="created-date"><%- date %></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="issue-data">
|
|
||||||
<div class="status-data <% if (editable) { %>clickable<% } %>">
|
|
||||||
<span class="level" style="background-color:<%= status.color %>"></span>
|
|
||||||
<span class="status-status"><%= status.name %></span>
|
|
||||||
<% if (editable) { %>
|
|
||||||
<span class="icon icon-arrow-bottom"></span>
|
|
||||||
<% } %>
|
|
||||||
<span class="level-name">status</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
""")
|
|
||||||
selectionStatusTemplate = _.template("""
|
|
||||||
<ul class="popover pop-status">
|
|
||||||
<% _.each(statuses, function(status) { %>
|
|
||||||
<li><a href="" class="status" title="<%- status.name %>"
|
|
||||||
data-status-id="<%- status.id %>"><%- status.name %></a></li>
|
|
||||||
<% }); %>
|
|
||||||
</ul>
|
|
||||||
""")
|
""")
|
||||||
|
|
||||||
link = ($scope, $el, $attrs, $model) ->
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
editable = $attrs.editable?
|
isEditable = ->
|
||||||
|
return $scope.project.my_permissions.indexOf("modify_task") != -1
|
||||||
|
|
||||||
renderTaskstatus = (task) ->
|
render = (task) ->
|
||||||
owner = $scope.usersById?[task.owner]
|
if not isEditable() and not task.is_iocaine
|
||||||
date = moment(task.created_date).format("DD MMM YYYY HH:mm")
|
$el.html("")
|
||||||
status = $scope.statusById[task.status]
|
return
|
||||||
html = template({
|
|
||||||
owner: owner
|
ctx = {
|
||||||
date: date
|
isIocaine: task.is_iocaine
|
||||||
editable: editable
|
isEditable: isEditable()
|
||||||
status: status
|
}
|
||||||
})
|
html = template(ctx)
|
||||||
$el.html(html)
|
$el.html(html)
|
||||||
$el.find(".status-data").append(selectionStatusTemplate({statuses:$scope.statusList}))
|
|
||||||
|
$el.on "click", ".is-iocaine", (event) ->
|
||||||
|
return if not isEditable()
|
||||||
|
|
||||||
|
task = $model.$modelValue.clone()
|
||||||
|
task.is_iocaine = not task.is_iocaine
|
||||||
|
$model.$setViewValue(task)
|
||||||
|
$loading.start($el.find('label'))
|
||||||
|
|
||||||
|
promise = $tgrepo.save($model.$modelValue)
|
||||||
|
promise.then ->
|
||||||
|
$confirm.notify("success")
|
||||||
|
$rootscope.$broadcast("history:reload")
|
||||||
|
|
||||||
|
promise.then null, ->
|
||||||
|
task.revert()
|
||||||
|
$model.$setViewValue(task)
|
||||||
|
$confirm.notify("error")
|
||||||
|
|
||||||
|
promise.finally ->
|
||||||
|
$loading.finish($el.find('label'))
|
||||||
|
|
||||||
$scope.$watch $attrs.ngModel, (task) ->
|
$scope.$watch $attrs.ngModel, (task) ->
|
||||||
if task?
|
render(task) if task
|
||||||
renderTaskstatus(task)
|
|
||||||
|
|
||||||
if editable
|
$scope.$on "$destroy", ->
|
||||||
$el.on "click", ".status-data", (event) ->
|
$el.off()
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
$el.find(".pop-status").popover().open()
|
|
||||||
|
|
||||||
$el.on "click", ".status", (event) ->
|
return {
|
||||||
event.preventDefault()
|
link: link
|
||||||
event.stopPropagation()
|
restrict: "EA"
|
||||||
target = angular.element(event.currentTarget)
|
require: "ngModel"
|
||||||
$model.$modelValue.status = target.data("status-id")
|
}
|
||||||
renderTaskstatus($model.$modelValue)
|
|
||||||
$el.find(".popover").popover().close()
|
|
||||||
|
|
||||||
return {link:link, require:"ngModel"}
|
module.directive("tgTaskIsIocaineButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", TaskIsIocaineButtonDirective])
|
||||||
|
|
||||||
module.directive("tgTaskStatus", TaskStatusDirective)
|
|
||||||
|
|
|
@ -59,12 +59,17 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
# On Success
|
# On Success
|
||||||
promise.then =>
|
promise.then =>
|
||||||
@appTitle.set(@scope.us.subject + " - " + @scope.project.name)
|
@appTitle.set(@scope.us.subject + " - " + @scope.project.name)
|
||||||
|
@.initializeOnDeleteGoToUrl()
|
||||||
tgLoader.pageLoaded()
|
tgLoader.pageLoaded()
|
||||||
|
|
||||||
# On Error
|
# On Error
|
||||||
promise.then null, @.onInitialDataError.bind(@)
|
promise.then null, @.onInitialDataError.bind(@)
|
||||||
|
|
||||||
initializeEventHandlers: ->
|
initializeEventHandlers: ->
|
||||||
|
@scope.$on "related-tasks:update", =>
|
||||||
|
@.loadUs()
|
||||||
|
@scope.tasks = _.clone(@scope.tasks, false)
|
||||||
|
|
||||||
@scope.$on "attachment:create", =>
|
@scope.$on "attachment:create", =>
|
||||||
@analytics.trackEvent("attachment", "create", "create attachment on userstory", 1)
|
@analytics.trackEvent("attachment", "create", "create attachment on userstory", 1)
|
||||||
@rootscope.$broadcast("history:reload")
|
@rootscope.$broadcast("history:reload")
|
||||||
|
@ -75,6 +80,18 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
@scope.$on "attachment:delete", =>
|
@scope.$on "attachment:delete", =>
|
||||||
@rootscope.$broadcast("history:reload")
|
@rootscope.$broadcast("history:reload")
|
||||||
|
|
||||||
|
initializeOnDeleteGoToUrl: ->
|
||||||
|
ctx = {project: @scope.project.slug}
|
||||||
|
@scope.onDeleteGoToUrl = @navUrls.resolve("project", ctx)
|
||||||
|
if @scope.project.is_backlog_activated
|
||||||
|
if @scope.us.milestone
|
||||||
|
ctx.sprint = @scope.sprint.slug
|
||||||
|
@scope.onDeleteGoToUrl = @navUrls.resolve("project-taskboard", ctx)
|
||||||
|
else
|
||||||
|
@scope.onDeleteGoToUrl = @navUrls.resolve("project-backlog", ctx)
|
||||||
|
else if @scope.project.is_kanban_activated
|
||||||
|
@scope.onDeleteGoToUrl = @navUrls.resolve("project-kanban", ctx)
|
||||||
|
|
||||||
loadProject: ->
|
loadProject: ->
|
||||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||||
@scope.project = project
|
@scope.project = project
|
||||||
|
@ -106,12 +123,14 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
}
|
}
|
||||||
@scope.nextUrl = @navUrls.resolve("project-userstories-detail", ctx)
|
@scope.nextUrl = @navUrls.resolve("project-userstories-detail", ctx)
|
||||||
|
|
||||||
if us.milestone
|
|
||||||
@rs.sprints.get(us.project, us.milestone).then (sprint) =>
|
|
||||||
@scope.sprint = sprint
|
|
||||||
|
|
||||||
return us
|
return us
|
||||||
|
|
||||||
|
loadSprint: ->
|
||||||
|
if @scope.us.milestone
|
||||||
|
return @rs.sprints.get(@scope.us.project, @scope.us.milestone).then (sprint) =>
|
||||||
|
@scope.sprint = sprint
|
||||||
|
return sprint
|
||||||
|
|
||||||
loadTasks: ->
|
loadTasks: ->
|
||||||
return @rs.tasks.list(@scope.projectId, null, @scope.usId).then (tasks) =>
|
return @rs.tasks.list(@scope.projectId, null, @scope.usId).then (tasks) =>
|
||||||
@scope.tasks = tasks
|
@scope.tasks = tasks
|
||||||
|
@ -130,263 +149,128 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
|
|
||||||
return promise.then(=> @.loadProject())
|
return promise.then(=> @.loadProject())
|
||||||
.then(=> @.loadUsersAndRoles())
|
.then(=> @.loadUsersAndRoles())
|
||||||
.then(=> @q.all([@.loadUs(),
|
.then(=> @q.all([@.loadUs().then(=> @.loadSprint()),
|
||||||
@.loadTasks()]))
|
@.loadTasks()]))
|
||||||
|
|
||||||
block: ->
|
|
||||||
@rootscope.$broadcast("block", @scope.us)
|
|
||||||
|
|
||||||
unblock: ->
|
|
||||||
@rootscope.$broadcast("unblock", @scope.us)
|
|
||||||
|
|
||||||
delete: ->
|
|
||||||
#TODO: i18n
|
|
||||||
title = "Delete User Story"
|
|
||||||
message = @scope.us.subject
|
|
||||||
|
|
||||||
@confirm.askOnDelete(title, message).then (finish) =>
|
|
||||||
promise = @.repo.remove(@scope.us)
|
|
||||||
promise.then =>
|
|
||||||
finish()
|
|
||||||
|
|
||||||
if @scope.us.milestone
|
|
||||||
@location.path(@navUrls.resolve("project-taskboard", {project: @scope.project.slug, sprint: @scope.sprint.slug}))
|
|
||||||
else if @scope.project.is_backlog_activated
|
|
||||||
@location.path(@navUrls.resolve("project-backlog", {project: @scope.project.slug}))
|
|
||||||
else
|
|
||||||
@location.path(@navUrls.resolve("project-kanban", {project: @scope.project.slug}))
|
|
||||||
promise.then null, =>
|
|
||||||
finish(false)
|
|
||||||
$confirm.notify("error")
|
|
||||||
|
|
||||||
module.controller("UserStoryDetailController", UserStoryDetailController)
|
module.controller("UserStoryDetailController", UserStoryDetailController)
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## User story Main Directive
|
## User story status display directive
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
UsDirective = ($tgrepo, $log, $location, $confirm, $navUrls, $loading) ->
|
UsStatusDisplayDirective = ->
|
||||||
linkSidebar = ($scope, $el, $attrs, $ctrl) ->
|
# Display if a US is open or closed and its kanban status.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# tg-us-status-display(ng-model="us")
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# - US object (ng-model)
|
||||||
|
# - scope.statusById object
|
||||||
|
|
||||||
|
template = _.template("""
|
||||||
|
<span>
|
||||||
|
<% if (is_closed) { %>
|
||||||
|
Closed
|
||||||
|
<% } else { %>
|
||||||
|
Open
|
||||||
|
<% } %>
|
||||||
|
</span>
|
||||||
|
<span class="us-detail-status" style="color:<%= status.color %>">
|
||||||
|
<%= status.name %>
|
||||||
|
</span>
|
||||||
|
""") # TODO: i18n
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
$ctrl = $el.controller()
|
render = (us) ->
|
||||||
linkSidebar($scope, $el, $attrs, $ctrl)
|
|
||||||
|
|
||||||
if $el.is("form")
|
|
||||||
form = $el.checksley()
|
|
||||||
|
|
||||||
$el.on "click", ".save-us", (event) ->
|
|
||||||
if not form.validate()
|
|
||||||
return
|
|
||||||
|
|
||||||
onSuccess = ->
|
|
||||||
$loading.finish(target)
|
|
||||||
$confirm.notify("success")
|
|
||||||
ctx = {
|
|
||||||
project: $scope.project.slug
|
|
||||||
ref: $scope.us.ref
|
|
||||||
}
|
|
||||||
$location.path($navUrls.resolve("project-userstories-detail", ctx))
|
|
||||||
|
|
||||||
onError = ->
|
|
||||||
$loading.finish(target)
|
|
||||||
$confirm.notify("error")
|
|
||||||
|
|
||||||
target = angular.element(event.currentTarget)
|
|
||||||
$loading.start(target)
|
|
||||||
$tgrepo.save($scope.us).then(onSuccess, onError)
|
|
||||||
|
|
||||||
return {link:link}
|
|
||||||
|
|
||||||
module.directive("tgUsDetail", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm",
|
|
||||||
"$tgNavUrls", "$tgLoading", UsDirective])
|
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
## User story status directive
|
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
UsStatusDetailDirective = () ->
|
|
||||||
#TODO: i18n
|
|
||||||
template = _.template("""
|
|
||||||
<h1>
|
|
||||||
<span>
|
|
||||||
<% if (is_closed) { %>
|
|
||||||
Closed
|
|
||||||
<% } else { %>
|
|
||||||
Open
|
|
||||||
<% } %>
|
|
||||||
<span class="us-detail-status" style="color:<%= status.color %>"><%= status.name %></span>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="us-detail-progress-bar">
|
|
||||||
<div class="current-progress" style="width:<%- usProgress %>%"/>
|
|
||||||
<span clasS="tasks-completed">
|
|
||||||
<%- totalClosedTasks %>/<%- totalTasks %> tasks completed
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="us-created-by">
|
|
||||||
<div class="user-avatar">
|
|
||||||
<img src="<%= owner.photo %>" alt="<%- owner.full_name_display %>" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="created-by">
|
|
||||||
<span class="created-title">Created by <%- owner.full_name_display %></span>
|
|
||||||
<span class="created-date"><%- date %></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="points-per-role">
|
|
||||||
<li class="total">
|
|
||||||
<span class="points"><%- totalPoints %></span>
|
|
||||||
<span class="role">total</span>
|
|
||||||
</li>
|
|
||||||
<% _.each(rolePoints, function(rolePoint) { %>
|
|
||||||
<li class="total <% if (editable) { %>clickable<% } %>" data-role-id="<%- rolePoint.id %>">
|
|
||||||
<span class="points"><%- rolePoint.points %></span>
|
|
||||||
<span class="role"><%- rolePoint.name %></span></li>
|
|
||||||
<% }); %>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="issue-data">
|
|
||||||
<div class="status-data <% if (editable) { %>clickable<% } %>">
|
|
||||||
<span class="level" style="background-color:<%= status.color %>"></span>
|
|
||||||
<span class="status-status"><%= status.name %></span>
|
|
||||||
<% if (editable) { %>
|
|
||||||
<span class="icon icon-arrow-bottom"></span>
|
|
||||||
<% } %>
|
|
||||||
<span class="level-name">status</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
""")
|
|
||||||
selectionStatusTemplate = _.template("""
|
|
||||||
<ul class="popover pop-status">
|
|
||||||
<% _.each(statuses, function(status) { %>
|
|
||||||
<li><a href="" class="status" title="<%- status.name %>"
|
|
||||||
data-status-id="<%- status.id %>"><%- status.name %></a></li>
|
|
||||||
<% }); %>
|
|
||||||
</ul>
|
|
||||||
""")
|
|
||||||
selectionPointsTemplate = _.template("""
|
|
||||||
<ul class="popover pop-points-open">
|
|
||||||
<% _.each(points, function(point) { %>
|
|
||||||
<li><a href="" class="point" title="<%- point.name %>"
|
|
||||||
data-point-id="<%- point.id %>"><%- point.name %></a>
|
|
||||||
</li>
|
|
||||||
<% }); %>
|
|
||||||
</ul>
|
|
||||||
""")
|
|
||||||
|
|
||||||
link = ($scope, $el, $attrs, $model) ->
|
|
||||||
editable = $attrs.editable?
|
|
||||||
updatingSelectedRoleId = null
|
|
||||||
$ctrl = $el.controller()
|
|
||||||
|
|
||||||
showSelectPoints = (target) ->
|
|
||||||
us = $model.$modelValue
|
|
||||||
$el.find(".pop-points-open").remove()
|
|
||||||
$el.find(target).append(selectionPointsTemplate({ "points": $scope.project.points }))
|
|
||||||
target.removeClass('active')
|
|
||||||
$el.find(".pop-points-open a[data-point-id='#{us.points[updatingSelectedRoleId]}']").addClass("active")
|
|
||||||
# If not showing role selection let's move to the left
|
|
||||||
$el.find(".pop-points-open").popover().open()
|
|
||||||
|
|
||||||
calculateTotalPoints = (us)->
|
|
||||||
values = _.map(us.points, (v, k) -> $scope.pointsById[v].value)
|
|
||||||
values = _.filter(values, (num) -> num?)
|
|
||||||
if values.length == 0
|
|
||||||
return "?"
|
|
||||||
|
|
||||||
return _.reduce(values, (acc, num) -> acc + num)
|
|
||||||
|
|
||||||
renderUsstatus = (us) ->
|
|
||||||
owner = $scope.usersById?[us.owner]
|
|
||||||
date = moment(us.created_date).format("DD MMM YYYY HH:mm")
|
|
||||||
status = $scope.statusById[us.status]
|
|
||||||
rolePoints = _.clone(_.filter($scope.project.roles, "computable"), true)
|
|
||||||
_.map rolePoints, (v, k) ->
|
|
||||||
name = $scope.pointsById[us.points[v.id]].name
|
|
||||||
name = "?" if not name?
|
|
||||||
v.points = name
|
|
||||||
|
|
||||||
totalTasks = $scope.tasks.length
|
|
||||||
totalClosedTasks = _.filter($scope.tasks, (task) => $scope.taskStatusById[task.status].is_closed).length
|
|
||||||
usProgress = 0
|
|
||||||
usProgress = 100 * totalClosedTasks / totalTasks if totalTasks > 0
|
|
||||||
html = template({
|
html = template({
|
||||||
owner: owner
|
|
||||||
date: date
|
|
||||||
editable: editable
|
|
||||||
is_closed: us.is_closed
|
is_closed: us.is_closed
|
||||||
status: status
|
status: $scope.statusById[us.status]
|
||||||
totalPoints: us.total_points
|
|
||||||
rolePoints: rolePoints
|
|
||||||
totalTasks: totalTasks
|
|
||||||
totalClosedTasks: totalClosedTasks
|
|
||||||
usProgress: usProgress
|
|
||||||
})
|
})
|
||||||
$el.html(html)
|
$el.html(html)
|
||||||
$el.find(".status-data").append(selectionStatusTemplate({statuses:$scope.statusList}))
|
|
||||||
|
|
||||||
bindOnce $scope, "tasks", (tasks) ->
|
$scope.$watch $attrs.ngModel, (us) ->
|
||||||
$scope.$watch $attrs.ngModel, (us) ->
|
render(us) if us?
|
||||||
if us?
|
|
||||||
renderUsstatus(us)
|
|
||||||
|
|
||||||
$scope.$on "related-tasks:update", ->
|
$scope.$on "$destroy", ->
|
||||||
us = $scope.$eval $attrs.ngModel
|
$el.off()
|
||||||
if us?
|
|
||||||
# Reload the us because the status could have changed
|
|
||||||
$ctrl.loadUs()
|
|
||||||
renderUsstatus(us)
|
|
||||||
|
|
||||||
if editable
|
return {
|
||||||
$el.on "click", ".status-data", (event) ->
|
link: link
|
||||||
event.preventDefault()
|
restrict: "EA"
|
||||||
event.stopPropagation()
|
require: "ngModel"
|
||||||
$el.find(".pop-status").popover().open()
|
}
|
||||||
|
|
||||||
$el.on "click", ".status", (event) ->
|
module.directive("tgUsStatusDisplay", UsStatusDisplayDirective)
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
target = angular.element(event.currentTarget)
|
|
||||||
$model.$modelValue.status = target.data("status-id")
|
|
||||||
renderUsstatus($model.$modelValue)
|
|
||||||
$.fn.popover().closeAll()
|
|
||||||
|
|
||||||
$el.on "click", ".total.clickable", (event) ->
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
target = angular.element(event.currentTarget)
|
|
||||||
updatingSelectedRoleId = target.data("role-id")
|
|
||||||
target.siblings().removeClass('active')
|
|
||||||
target.addClass('active')
|
|
||||||
showSelectPoints(target)
|
|
||||||
|
|
||||||
$el.on "click", ".point", (event) ->
|
#############################################################################
|
||||||
event.preventDefault()
|
## User story related tasts progress splay Directive
|
||||||
event.stopPropagation()
|
#############################################################################
|
||||||
|
|
||||||
target = angular.element(event.currentTarget)
|
UsTasksProgressDisplayDirective = ->
|
||||||
$.fn.popover().closeAll()
|
# Display a progress bar with the stats of completed tasks.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# tg-us-tasks-progress-display(ng-model="tasks")
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# - Task object list (ng-model)
|
||||||
|
# - scope.taskStatusById object
|
||||||
|
|
||||||
$scope.$apply () ->
|
template = _.template("""
|
||||||
us = $model.$modelValue
|
<div class="current-progress" style="width:<%- progress %>%" />
|
||||||
usPoints = _.clone(us.points, true)
|
<span clasS="tasks-completed">
|
||||||
usPoints[updatingSelectedRoleId] = target.data("point-id")
|
<%- totalClosedTasks %>/<%- totalTasks %> tasks completed
|
||||||
us.points = usPoints
|
</span>
|
||||||
us.total_points = calculateTotalPoints(us)
|
""") # TODO: i18n
|
||||||
renderUsstatus(us)
|
|
||||||
|
|
||||||
return {link:link, require:"ngModel"}
|
link = ($scope, $el, $attrs) ->
|
||||||
|
render = (tasks) ->
|
||||||
|
totalTasks = tasks.length
|
||||||
|
totalClosedTasks = _.filter(tasks, (task) => $scope.taskStatusById[task.status].is_closed).length
|
||||||
|
|
||||||
|
progress = if totalTasks > 0 then 100 * totalClosedTasks / totalTasks else 0
|
||||||
|
|
||||||
|
html = template({
|
||||||
|
totalTasks: totalTasks
|
||||||
|
totalClosedTasks: totalClosedTasks
|
||||||
|
progress: progress
|
||||||
|
})
|
||||||
|
$el.html(html)
|
||||||
|
|
||||||
|
$scope.$watch $attrs.ngModel, (tasks) ->
|
||||||
|
render(tasks) if tasks?
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: link
|
||||||
|
restrict: "EA"
|
||||||
|
require: "ngModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgUsTasksProgressDisplay", UsTasksProgressDisplayDirective)
|
||||||
|
|
||||||
module.directive("tgUsStatusDetail", UsStatusDetailDirective)
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## User story estimation directive
|
## User story estimation directive
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
UsEstimationDirective = ($log) ->
|
UsEstimationDirective = ($rootScope, $repo, $confirm) ->
|
||||||
|
# Display the points of a US and you can edit it.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# tg-us-estimation-progress-bar(ng-model="us")
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# - Us object (ng-model)
|
||||||
|
# - scope.project object
|
||||||
|
# Optionals:
|
||||||
|
# - save-after-modify (boolean): save object after modify
|
||||||
|
|
||||||
mainTemplate = _.template("""
|
mainTemplate = _.template("""
|
||||||
<ul class="points-per-role">
|
<ul class="points-per-role">
|
||||||
<li class="total">
|
<li class="total">
|
||||||
|
@ -394,7 +278,7 @@ UsEstimationDirective = ($log) ->
|
||||||
<span class="role">total</span>
|
<span class="role">total</span>
|
||||||
</li>
|
</li>
|
||||||
<% _.each(roles, function(role) { %>
|
<% _.each(roles, function(role) { %>
|
||||||
<li class="total clickable" data-role-id="<%- role.id %>">
|
<li class="total <% if(editable){ %>clickable<% } %>" data-role-id="<%- role.id %>">
|
||||||
<span class="points"><%- role.points %></span>
|
<span class="points"><%- role.points %></span>
|
||||||
<span class="role"><%- role.name %></span></li>
|
<span class="role"><%- role.name %></span></li>
|
||||||
<% }); %>
|
<% }); %>
|
||||||
|
@ -417,7 +301,14 @@ UsEstimationDirective = ($log) ->
|
||||||
</ul>
|
</ul>
|
||||||
""")
|
""")
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
saveAfterModify = $attrs.saveAfterModify or false
|
||||||
|
|
||||||
|
isEditable = ->
|
||||||
|
if $model.$modelValue.id
|
||||||
|
return $scope.project.my_permissions.indexOf("modify_us") != -1
|
||||||
|
return $scope.project.my_permissions.indexOf("add_us") != -1
|
||||||
|
|
||||||
render = (us) ->
|
render = (us) ->
|
||||||
totalPoints = us.total_points or 0
|
totalPoints = us.total_points or 0
|
||||||
computableRoles = _.filter($scope.project.roles, "computable")
|
computableRoles = _.filter($scope.project.roles, "computable")
|
||||||
|
@ -430,7 +321,12 @@ UsEstimationDirective = ($log) ->
|
||||||
role.points = if pointObj? and pointObj.name? then pointObj.name else "?"
|
role.points = if pointObj? and pointObj.name? then pointObj.name else "?"
|
||||||
return role
|
return role
|
||||||
|
|
||||||
html = mainTemplate({totalPoints: totalPoints, roles: roles})
|
ctx = {
|
||||||
|
totalPoints: totalPoints
|
||||||
|
roles: roles
|
||||||
|
editable: isEditable()
|
||||||
|
}
|
||||||
|
html = mainTemplate(ctx)
|
||||||
$el.html(html)
|
$el.html(html)
|
||||||
|
|
||||||
renderPoints = (target, us, roleId) ->
|
renderPoints = (target, us, roleId) ->
|
||||||
|
@ -463,19 +359,15 @@ UsEstimationDirective = ($log) ->
|
||||||
return "0"
|
return "0"
|
||||||
return _.reduce(values, (acc, num) -> acc + num)
|
return _.reduce(values, (acc, num) -> acc + num)
|
||||||
|
|
||||||
$scope.$watch $attrs.ngModel, (us) ->
|
|
||||||
render(us) if us
|
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
|
||||||
$el.off()
|
|
||||||
|
|
||||||
$el.on "click", ".total.clickable", (event) ->
|
$el.on "click", ".total.clickable", (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
return if not isEditable()
|
||||||
|
|
||||||
target = angular.element(event.currentTarget)
|
target = angular.element(event.currentTarget)
|
||||||
roleId = target.data("role-id")
|
roleId = target.data("role-id")
|
||||||
|
|
||||||
us = $scope.$eval($attrs.ngModel)
|
us = $model.$modelValue
|
||||||
renderPoints(target, us, roleId)
|
renderPoints(target, us, roleId)
|
||||||
|
|
||||||
target.siblings().removeClass('active')
|
target.siblings().removeClass('active')
|
||||||
|
@ -484,8 +376,7 @@ UsEstimationDirective = ($log) ->
|
||||||
$el.on "click", ".point", (event) ->
|
$el.on "click", ".point", (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
return if not isEditable()
|
||||||
us = $scope.$eval($attrs.ngModel)
|
|
||||||
|
|
||||||
target = angular.element(event.currentTarget)
|
target = angular.element(event.currentTarget)
|
||||||
roleId = target.data("role-id")
|
roleId = target.data("role-id")
|
||||||
|
@ -493,17 +384,262 @@ UsEstimationDirective = ($log) ->
|
||||||
|
|
||||||
$el.find(".popover").popover().close()
|
$el.find(".popover").popover().close()
|
||||||
|
|
||||||
points = _.clone(us.points, true)
|
# NOTE: This block of code is strange and, sometimes, repetitive
|
||||||
|
# but is the only solution I find to update the object
|
||||||
|
# corectly
|
||||||
|
us = angular.copy($model.$modelValue)
|
||||||
|
points = _.clone($model.$modelValue.points, true)
|
||||||
points[roleId] = pointId
|
points[roleId] = pointId
|
||||||
|
us.setAttr('points', points) if us.setAttr?
|
||||||
|
us.points = points
|
||||||
|
us.total_points = calculateTotalPoints(us)
|
||||||
|
$model.$setViewValue(us)
|
||||||
|
|
||||||
$scope.$apply ->
|
if saveAfterModify
|
||||||
us.points = points
|
# Edit in the detail page
|
||||||
us.total_points = calculateTotalPoints(us)
|
onSuccess = ->
|
||||||
render(us)
|
$confirm.notify("success")
|
||||||
|
$rootScope.$broadcast("history:reload")
|
||||||
|
onError = ->
|
||||||
|
us.revert()
|
||||||
|
$model.$setViewValue(us)
|
||||||
|
$confirm.notify("error")
|
||||||
|
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||||
|
else
|
||||||
|
# Create or eedit in the lightbox
|
||||||
|
render($model.$modelValue)
|
||||||
|
|
||||||
|
$scope.$watch $attrs.ngModel, (us) ->
|
||||||
|
render(us) if us
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
link: link
|
link: link
|
||||||
restrict: "EA"
|
restrict: "EA"
|
||||||
|
require: "ngModel"
|
||||||
}
|
}
|
||||||
|
|
||||||
module.directive("tgUsEstimation", UsEstimationDirective)
|
module.directive("tgUsEstimation", ["$rootScope", "$tgRepo", "$tgConfirm", UsEstimationDirective])
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## User story status button directive
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
||||||
|
# Display the status of a US and you can edit it.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# tg-us-status-button(ng-model="us")
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# - Us object (ng-model)
|
||||||
|
# - scope.statusById object
|
||||||
|
# - $scope.project.my_permissions
|
||||||
|
|
||||||
|
template = _.template("""
|
||||||
|
<div class="status-data <% if(editable){ %>clickable<% }%>">
|
||||||
|
<span class="level" style="background-color:<%= status.color %>"></span>
|
||||||
|
<span class="status-status"><%= status.name %></span>
|
||||||
|
<% if(editable){ %><span class="icon icon-arrow-bottom"></span><% }%>
|
||||||
|
<span class="level-name">status</span>
|
||||||
|
|
||||||
|
<ul class="popover pop-status">
|
||||||
|
<% _.each(statuses, function(st) { %>
|
||||||
|
<li><a href="" class="status" title="<%- st.name %>"
|
||||||
|
data-status-id="<%- st.id %>"><%- st.name %></a></li>
|
||||||
|
<% }); %>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
""") #TODO: i18n
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
isEditable = ->
|
||||||
|
return $scope.project.my_permissions.indexOf("modify_us") != -1
|
||||||
|
|
||||||
|
render = (us) =>
|
||||||
|
status = $scope.statusById[us.status]
|
||||||
|
|
||||||
|
html = template({
|
||||||
|
status: status
|
||||||
|
statuses: $scope.statusList
|
||||||
|
editable: isEditable()
|
||||||
|
})
|
||||||
|
$el.html(html)
|
||||||
|
|
||||||
|
$el.on "click", ".status-data", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return if not isEditable()
|
||||||
|
|
||||||
|
$el.find(".pop-status").popover().open()
|
||||||
|
|
||||||
|
$el.on "click", ".status", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return if not isEditable()
|
||||||
|
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
|
||||||
|
$.fn.popover().closeAll()
|
||||||
|
|
||||||
|
us = $model.$modelValue.clone()
|
||||||
|
us.status = target.data("status-id")
|
||||||
|
$model.$setViewValue(us)
|
||||||
|
|
||||||
|
$scope.$apply()
|
||||||
|
|
||||||
|
onSuccess = ->
|
||||||
|
$confirm.notify("success")
|
||||||
|
$rootScope.$broadcast("history:reload")
|
||||||
|
$loading.finish($el.find(".level-name"))
|
||||||
|
|
||||||
|
onError = ->
|
||||||
|
$confirm.notify("error")
|
||||||
|
us.revert()
|
||||||
|
$model.$setViewValue(us)
|
||||||
|
$loading.finish($el.find(".level-name"))
|
||||||
|
|
||||||
|
$loading.start($el.find(".level-name"))
|
||||||
|
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||||
|
|
||||||
|
$scope.$watch $attrs.ngModel, (us) ->
|
||||||
|
render(us) if us
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: link
|
||||||
|
restrict: "EA"
|
||||||
|
require: "ngModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgUsStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading",
|
||||||
|
UsStatusButtonDirective])
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## User story team requirements button directive
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
|
||||||
|
template = _.template("""
|
||||||
|
<label for="team-requirement"
|
||||||
|
class="button button-gray team-requirement <% if(canEdit){ %>editable<% }; %> <% if(isRequired){ %>active<% }; %>">
|
||||||
|
Team requirement
|
||||||
|
</label>
|
||||||
|
<input type="checkbox" id="team-requirement" name="team-requirement"/>
|
||||||
|
""") #TODO: i18n
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
canEdit = ->
|
||||||
|
return $scope.project.my_permissions.indexOf("modify_us") != -1
|
||||||
|
|
||||||
|
render = (us) ->
|
||||||
|
if not canEdit() and not us.team_requirement
|
||||||
|
$el.html("")
|
||||||
|
return
|
||||||
|
|
||||||
|
ctx = {
|
||||||
|
canEdit: canEdit()
|
||||||
|
isRequired: us.team_requirement
|
||||||
|
}
|
||||||
|
html = template(ctx)
|
||||||
|
$el.html(html)
|
||||||
|
|
||||||
|
$el.on "click", ".team-requirement", (event) ->
|
||||||
|
return if not canEdit()
|
||||||
|
|
||||||
|
us = $model.$modelValue.clone()
|
||||||
|
us.team_requirement = not us.team_requirement
|
||||||
|
$model.$setViewValue(us)
|
||||||
|
|
||||||
|
$loading.start($el.find("label"))
|
||||||
|
promise = $tgrepo.save($model.$modelValue)
|
||||||
|
promise.then =>
|
||||||
|
$loading.finish($el.find("label"))
|
||||||
|
$rootscope.$broadcast("history:reload")
|
||||||
|
promise.then null, ->
|
||||||
|
$loading.finish($el.find("label"))
|
||||||
|
$confirm.notify("error")
|
||||||
|
us.revert()
|
||||||
|
$model.$setViewValue(us)
|
||||||
|
|
||||||
|
$scope.$watch $attrs.ngModel, (us) ->
|
||||||
|
render(us) if us
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: link
|
||||||
|
restrict: "EA"
|
||||||
|
require: "ngModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgUsTeamRequirementButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", UsTeamRequirementButtonDirective])
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## User story client requirements button directive
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
|
||||||
|
template = _.template("""
|
||||||
|
<label for="client-requirement"
|
||||||
|
class="button button-gray client-requirement <% if(canEdit){ %>editable<% }; %> <% if(isRequired){ %>active<% }; %>">
|
||||||
|
Client requirement
|
||||||
|
</label>
|
||||||
|
<input type="checkbox" id="client-requirement" name="client-requirement"/>
|
||||||
|
""") #TODO: i18n
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
canEdit = ->
|
||||||
|
return $scope.project.my_permissions.indexOf("modify_us") != -1
|
||||||
|
|
||||||
|
render = (us) ->
|
||||||
|
if not canEdit() and not us.client_requirement
|
||||||
|
$el.html("")
|
||||||
|
return
|
||||||
|
|
||||||
|
ctx = {
|
||||||
|
canEdit: canEdit()
|
||||||
|
isRequired: us.client_requirement
|
||||||
|
}
|
||||||
|
html = template(ctx)
|
||||||
|
$el.html(html)
|
||||||
|
|
||||||
|
$el.on "click", ".client-requirement", (event) ->
|
||||||
|
return if not canEdit()
|
||||||
|
|
||||||
|
us = $model.$modelValue.clone()
|
||||||
|
us.client_requirement = not us.client_requirement
|
||||||
|
$model.$setViewValue(us)
|
||||||
|
|
||||||
|
$loading.start($el.find("label"))
|
||||||
|
promise = $tgrepo.save($model.$modelValue)
|
||||||
|
promise.then =>
|
||||||
|
$loading.finish($el.find("label"))
|
||||||
|
$rootscope.$broadcast("history:reload")
|
||||||
|
promise.then null, ->
|
||||||
|
$loading.finish($el.find("label"))
|
||||||
|
$confirm.notify("error")
|
||||||
|
us.revert()
|
||||||
|
$model.$setViewValue(us)
|
||||||
|
|
||||||
|
$scope.$watch $attrs.ngModel, (us) ->
|
||||||
|
render(us) if us
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: link
|
||||||
|
restrict: "EA"
|
||||||
|
require: "ngModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgUsClientRequirementButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading",
|
||||||
|
UsClientRequirementButtonDirective])
|
||||||
|
|
|
@ -38,6 +38,7 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
"$scope",
|
"$scope",
|
||||||
"$rootScope",
|
"$rootScope",
|
||||||
"$tgRepo",
|
"$tgRepo",
|
||||||
|
"$tgModel",
|
||||||
"$tgConfirm",
|
"$tgConfirm",
|
||||||
"$tgResources",
|
"$tgResources",
|
||||||
"$routeParams",
|
"$routeParams",
|
||||||
|
@ -51,7 +52,7 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
"tgLoader"
|
"tgLoader"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
|
constructor: (@scope, @rootscope, @repo, @model, @confirm, @rs, @params, @q, @location,
|
||||||
@filter, @log, @appTitle, @navUrls, @analytics, tgLoader) ->
|
@filter, @log, @appTitle, @navUrls, @analytics, tgLoader) ->
|
||||||
@scope.projectSlug = @params.pslug
|
@scope.projectSlug = @params.pslug
|
||||||
@scope.wikiSlug = @params.slug
|
@scope.wikiSlug = @params.slug
|
||||||
|
@ -80,7 +81,15 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
@scope.wiki = wiki
|
@scope.wiki = wiki
|
||||||
return wiki
|
return wiki
|
||||||
|
|
||||||
@scope.wiki = {content: ""}
|
if @scope.project.my_permissions.indexOf("add_wiki_page") == -1
|
||||||
|
return null
|
||||||
|
|
||||||
|
data = {
|
||||||
|
project: @scope.projectId
|
||||||
|
slug: @scope.wikiSlug
|
||||||
|
content: ""
|
||||||
|
}
|
||||||
|
@scope.wiki = @model.make_model("wiki", data)
|
||||||
return @scope.wiki
|
return @scope.wiki
|
||||||
|
|
||||||
loadWikiLinks: ->
|
loadWikiLinks: ->
|
||||||
|
@ -109,28 +118,13 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
@scope.wikiId = data.wikipage
|
@scope.wikiId = data.wikipage
|
||||||
|
|
||||||
return prom.then null, (xhr) =>
|
return prom.then null, (xhr) =>
|
||||||
ctx = {project: @params.pslug, slug: @params.slug}
|
@scope.wikiId = null
|
||||||
@location.path(@navUrls.resolve("project-wiki-page-edit", ctx))
|
|
||||||
|
|
||||||
return promise.then(=> @.loadProject())
|
return promise.then(=> @.loadProject())
|
||||||
.then(=> @.loadUsersAndRoles())
|
.then(=> @.loadUsersAndRoles())
|
||||||
.then(=> @q.all([@.loadWikiLinks(),
|
.then(=> @q.all([@.loadWikiLinks(),
|
||||||
@.loadWiki()]))
|
@.loadWiki()]))
|
||||||
|
|
||||||
edit: ->
|
|
||||||
ctx = {
|
|
||||||
project: @scope.projectSlug
|
|
||||||
slug: @scope.wikiSlug
|
|
||||||
}
|
|
||||||
@location.path(@navUrls.resolve("project-wiki-page-edit", ctx))
|
|
||||||
|
|
||||||
cancel: ->
|
|
||||||
ctx = {
|
|
||||||
project: @scope.projectSlug
|
|
||||||
slug: @scope.wikiSlug
|
|
||||||
}
|
|
||||||
@location.path(@navUrls.resolve("project-wiki-page", ctx))
|
|
||||||
|
|
||||||
delete: ->
|
delete: ->
|
||||||
# TODO: i18n
|
# TODO: i18n
|
||||||
title = "Delete Wiki Page"
|
title = "Delete Wiki Page"
|
||||||
|
@ -151,95 +145,181 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
|
|
||||||
module.controller("WikiDetailController", WikiDetailController)
|
module.controller("WikiDetailController", WikiDetailController)
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
## Wiki Edit Controller
|
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
class WikiEditController extends WikiDetailController
|
|
||||||
save: debounce 2000, ->
|
|
||||||
onSuccess = =>
|
|
||||||
ctx = {
|
|
||||||
project: @scope.projectSlug
|
|
||||||
slug: @scope.wiki.slug
|
|
||||||
}
|
|
||||||
@location.path(@navUrls.resolve("project-wiki-page", ctx))
|
|
||||||
@confirm.notify("success")
|
|
||||||
|
|
||||||
onError = =>
|
|
||||||
@confirm.notify("error")
|
|
||||||
|
|
||||||
if @scope.wiki.id
|
|
||||||
@repo.save(@scope.wiki).then onSuccess, onError
|
|
||||||
else
|
|
||||||
@analytics.trackEvent("wikipage", "create", "create wiki page", 1)
|
|
||||||
@scope.wiki.project = @scope.projectId
|
|
||||||
@scope.wiki.slug = @scope.wikiSlug
|
|
||||||
@repo.create("wiki", @scope.wiki).then onSuccess, onError
|
|
||||||
|
|
||||||
module.controller("WikiEditController", WikiEditController)
|
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Wiki Main Directive
|
## Wiki Summary Directive
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
WikiDirective = ($tgrepo, $log, $location, $confirm) ->
|
WikiSummaryDirective = ($log) ->
|
||||||
link = ($scope, $el, $attrs) ->
|
|
||||||
$ctrl = $el.controller()
|
|
||||||
|
|
||||||
return {link:link}
|
|
||||||
|
|
||||||
module.directive("tgWikiDetail", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm", WikiDirective])
|
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
## Wiki Edit Main Directive
|
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
WikiEditDirective = ($tgrepo, $log, $location, $confirm) ->
|
|
||||||
link = ($scope, $el, $attrs) ->
|
|
||||||
$ctrl = $el.controller()
|
|
||||||
|
|
||||||
return {link:link}
|
|
||||||
|
|
||||||
module.directive("tgWikiEdit", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm", WikiEditDirective])
|
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
## Wiki User Info Directive
|
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
WikiUserInfoDirective = ($log) ->
|
|
||||||
template = _.template("""
|
template = _.template("""
|
||||||
<figure class="avatar">
|
<ul>
|
||||||
<img src="<%= imgurl %>" alt="<%- name %>">
|
<li>
|
||||||
</figure>
|
<span class="number"><%- totalEditions %></span>
|
||||||
<span class="description">last modification</span>
|
<span class="description">times <br />edited</span>
|
||||||
<span class="username"><%- name %></span>
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="number"><%- lastModifiedDate %></span>
|
||||||
|
<span class="description"> last <br />edit</span>
|
||||||
|
</li>
|
||||||
|
<li class="username-edition">
|
||||||
|
<figure class="avatar">
|
||||||
|
<img src="<%= user.imgUrl %>" alt="<%- user.name %>">
|
||||||
|
</figure>
|
||||||
|
<span class="description">last modification</span>
|
||||||
|
<span class="username"><%- user.name %></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
""")
|
""")
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
if not $attrs.ngModel?
|
|
||||||
return $log.error "WikiUserDirective: no ng-model attr is defined"
|
|
||||||
|
|
||||||
render = (wiki) ->
|
render = (wiki) ->
|
||||||
if not $scope.usersById?
|
if not $scope.usersById?
|
||||||
$log.error "WikiUserDirective requires userById set in scope."
|
$log.error "WikiSummaryDirective requires userById set in scope."
|
||||||
else
|
else
|
||||||
user = $scope.usersById[wiki.last_modifier]
|
user = $scope.usersById[wiki.last_modifier]
|
||||||
if user is undefined
|
|
||||||
ctx = {name: "unknown", imgurl: "/images/unnamed.png"}
|
|
||||||
else
|
|
||||||
ctx = {name: user.full_name_display, imgurl: user.photo}
|
|
||||||
|
|
||||||
|
if user is undefined
|
||||||
|
user = {name: "unknown", imgUrl: "/images/unnamed.png"}
|
||||||
|
else
|
||||||
|
user = {name: user.full_name_display, imgUrl: user.photo}
|
||||||
|
|
||||||
|
ctx = {
|
||||||
|
totalEditions: wiki.editions
|
||||||
|
lastModifiedDate: moment(wiki.modified_date).format("DD MMM YYYY HH:mm")
|
||||||
|
user: user
|
||||||
|
}
|
||||||
html = template(ctx)
|
html = template(ctx)
|
||||||
$el.html(html)
|
$el.html(html)
|
||||||
|
|
||||||
bindOnce($scope, $attrs.ngModel, render)
|
$scope.$watch $attrs.ngModel, (wikiPage) ->
|
||||||
|
return if not wikiPage
|
||||||
|
render(wikiPage)
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
link: link
|
link: link
|
||||||
restrict: "AE"
|
restrict: "EA"
|
||||||
|
require: "ngModel"
|
||||||
}
|
}
|
||||||
|
|
||||||
module.directive("tgWikiUserInfo", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm", WikiUserInfoDirective])
|
module.directive("tgWikiSummary", ["$log", WikiSummaryDirective])
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## Editable Wiki Content Directive
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $location, $navUrls,
|
||||||
|
$analytics) ->
|
||||||
|
template = """
|
||||||
|
<div class="view-wiki-content">
|
||||||
|
<section class="wysiwyg"
|
||||||
|
tg-bind-html="wiki.html"></section>
|
||||||
|
<span class="edit icon icon-edit" title="Edit"></span>
|
||||||
|
</div>
|
||||||
|
<div class="edit-wiki-content" style="display: none;">
|
||||||
|
<textarea placeholder="Write your wiki page here"
|
||||||
|
ng-model="wiki.content"
|
||||||
|
tg-markitup="tg-markitup"></textarea>
|
||||||
|
<span class="action-container">
|
||||||
|
<a class="save icon icon-floppy" href="" title="Save" />
|
||||||
|
<a class="cancel icon icon-delete" href="" title="Cancel" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
""" # TODO: i18n
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
isEditable = ->
|
||||||
|
return $scope.project.my_permissions.indexOf("modify_wiki_page") != -1
|
||||||
|
|
||||||
|
switchToEditMode = ->
|
||||||
|
$el.find('.edit-wiki-content').show()
|
||||||
|
$el.find('.view-wiki-content').hide()
|
||||||
|
$el.find('textarea').focus()
|
||||||
|
|
||||||
|
switchToReadMode = ->
|
||||||
|
$el.find('.edit-wiki-content').hide()
|
||||||
|
$el.find('.view-wiki-content').show()
|
||||||
|
|
||||||
|
disableEdition = ->
|
||||||
|
$el.find(".view-wiki-content .edit").remove()
|
||||||
|
$el.find(".edit-wiki-content").remove()
|
||||||
|
|
||||||
|
cancelEdition = ->
|
||||||
|
if $scope.wiki.id
|
||||||
|
$scope.wiki.revert()
|
||||||
|
switchToReadMode()
|
||||||
|
else
|
||||||
|
ctx = {project: $scope.projectSlug}
|
||||||
|
$location.path($navUrls.resolve("project-wiki", ctx))
|
||||||
|
|
||||||
|
getSelectedText = ->
|
||||||
|
if $window.getSelection
|
||||||
|
return $window.getSelection().toString()
|
||||||
|
else if $document.selection
|
||||||
|
return $document.selection.createRange().text
|
||||||
|
return null
|
||||||
|
|
||||||
|
$el.on "mouseup", ".view-wiki-content", (event) ->
|
||||||
|
# We want to dettect the a inside the div so we use the target and
|
||||||
|
# not the currentTarget
|
||||||
|
target = angular.element(event.target)
|
||||||
|
return if not isEditable()
|
||||||
|
return if target.is('a')
|
||||||
|
return if getSelectedText()
|
||||||
|
switchToEditMode()
|
||||||
|
|
||||||
|
$el.on "click", ".save", debounce 2000, ->
|
||||||
|
onSuccess = (wikiPage) ->
|
||||||
|
if not $scope.wiki.id?
|
||||||
|
$analytics.trackEvent("wikipage", "create", "create wiki page", 1)
|
||||||
|
|
||||||
|
$scope.wiki = wikiPage
|
||||||
|
$model.setModelValue = $scope.wiki
|
||||||
|
$confirm.notify("success")
|
||||||
|
switchToReadMode()
|
||||||
|
|
||||||
|
onError = ->
|
||||||
|
$confirm.notify("error")
|
||||||
|
|
||||||
|
$loading.start($el.find('.save-container'))
|
||||||
|
if $scope.wiki.id?
|
||||||
|
promise = $repo.save($scope.wiki).then(onSuccess, onError)
|
||||||
|
else
|
||||||
|
promise = $repo.create("wiki", $scope.wiki).then(onSuccess, onError)
|
||||||
|
promise.finally ->
|
||||||
|
$loading.finish($el.find('.save-container'))
|
||||||
|
|
||||||
|
$el.on "click", ".cancel", ->
|
||||||
|
cancelEdition()
|
||||||
|
|
||||||
|
$el.on "keyup", "textarea", ->
|
||||||
|
if event.keyCode == 27
|
||||||
|
cancelEdition()
|
||||||
|
|
||||||
|
$scope.$watch $attrs.ngModel, (wikiPage) ->
|
||||||
|
return if not wikiPage
|
||||||
|
$scope.wiki = wikiPage
|
||||||
|
|
||||||
|
if isEditable()
|
||||||
|
$el.addClass('editable')
|
||||||
|
if not wikiPage.id?
|
||||||
|
switchToEditMode()
|
||||||
|
else
|
||||||
|
disableEdition()
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: link
|
||||||
|
restrict: "EA"
|
||||||
|
require: "ngModel"
|
||||||
|
template: template
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgEditableWikiContent", ["$window", "$document", "$tgRepo", "$tgConfirm", "$tgLoading",
|
||||||
|
"$tgLocation", "$tgNavUrls", "$tgAnalytics",
|
||||||
|
EditableWikiContentDirective])
|
||||||
|
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 323 B |
Before Width: | Height: | Size: 859 B After Width: | Height: | Size: 370 B |
Before Width: | Height: | Size: 223 B After Width: | Height: | Size: 272 B |
Before Width: | Height: | Size: 343 B After Width: | Height: | Size: 365 B |
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 255 B |
Before Width: | Height: | Size: 357 B After Width: | Height: | Size: 321 B |
Before Width: | Height: | Size: 606 B After Width: | Height: | Size: 397 B |
Before Width: | Height: | Size: 537 B After Width: | Height: | Size: 338 B |
Before Width: | Height: | Size: 743 B After Width: | Height: | Size: 375 B |
Before Width: | Height: | Size: 269 B After Width: | Height: | Size: 343 B |
|
@ -1,47 +0,0 @@
|
||||||
extends dummy-layout
|
|
||||||
|
|
||||||
block head
|
|
||||||
title Taiga Your agile, free, and open source project management tool
|
|
||||||
|
|
||||||
block content
|
|
||||||
form.wrapper(tg-issue-detail, ng-controller="IssueDetailController as ctrl",
|
|
||||||
ng-init="section='issues'")
|
|
||||||
div.main.us-detail
|
|
||||||
div.us-detail-header.header-with-actions
|
|
||||||
include views/components/mainTitle
|
|
||||||
.action-buttons
|
|
||||||
a.button.button-green.save-issue(href="", title="Save") Save
|
|
||||||
a.button.button-red.cancel(tg-nav="project-issues-detail:project=project.slug, ref=issue.ref", href="", title="Cancel") Cancel
|
|
||||||
|
|
||||||
section.us-story-main-data
|
|
||||||
div.us-title(ng-class="{blocked: issue.is_blocked}")
|
|
||||||
div.us-edit-name-inner
|
|
||||||
span.us-number(tg-bo-ref="issue.ref")
|
|
||||||
input(type="text", ng-model="issue.subject", data-required="true", data-maxlength="500")
|
|
||||||
p.block-desc-container(ng-show="issue.is_blocked")
|
|
||||||
span.block-description-title Blocked
|
|
||||||
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(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)
|
|
||||||
|
|
||||||
tg-attachments(ng-model="issue", type="issue")
|
|
||||||
tg-history(ng-model="issue", type="issue", mode="edit")
|
|
||||||
|
|
||||||
sidebar.menu-secondary.sidebar
|
|
||||||
section.us-status(tg-issue-status, ng-model="issue", editable="true")
|
|
||||||
section.us-assigned-to(tg-assigned-to, ng-model="issue", editable="true")
|
|
||||||
section.watchers(tg-watchers, ng-model="issue", editable="true")
|
|
||||||
|
|
||||||
section.us-detail-settings
|
|
||||||
a.button.button-gray.clickable(title="Click to block the issue", ng-show="!issue.is_blocked", ng-click="ctrl.block()") Block
|
|
||||||
a.button.button-red(title="Click to delete the issue", tg-check-permission="delete_issue", ng-click="ctrl.delete()", href="") Delete
|
|
||||||
|
|
||||||
div.lightbox.lightbox-block.hidden(tg-lb-block, title="Blocking issue", ng-model="issue")
|
|
||||||
|
|
||||||
div.lightbox.lightbox-select-user(tg-lb-assignedto)
|
|
||||||
|
|
||||||
div.lightbox.lightbox-select-user(tg-lb-watchers)
|
|
|
@ -4,19 +4,17 @@ block head
|
||||||
title Taiga Your agile, free, and open source project management tool
|
title Taiga Your agile, free, and open source project management tool
|
||||||
|
|
||||||
block content
|
block content
|
||||||
div.wrapper(tg-issue-detail, ng-controller="IssueDetailController as ctrl",
|
div.wrapper(ng-controller="IssueDetailController as ctrl",
|
||||||
ng-init="section='issues'")
|
ng-init="section='issues'")
|
||||||
div.main.us-detail
|
div.main.us-detail
|
||||||
div.us-detail-header.header-with-actions
|
div.us-detail-header.header-with-actions
|
||||||
include views/components/mainTitle
|
include views/components/mainTitle
|
||||||
.action-buttons
|
|
||||||
a.button.button-green(tg-check-permission="modify_issue", href="", title="Edit", tg-nav="project-issues-detail-edit:project=project.slug,ref=issue.ref") Edit
|
|
||||||
|
|
||||||
section.us-story-main-data
|
section.us-story-main-data
|
||||||
div.us-title(ng-class="{blocked: issue.is_blocked}")
|
div.us-title(ng-class="{blocked: issue.is_blocked}")
|
||||||
h2.us-title-text
|
h2.us-title-text
|
||||||
span.us-number(tg-bo-ref="issue.ref")
|
span.us-number(tg-bo-ref="issue.ref")
|
||||||
span.us-name(ng-bind="issue.subject")
|
span.us-name(tg-editable-subject, ng-model="issue", required-perm="modify_issue")
|
||||||
|
|
||||||
p.us-related-task(ng-if="issue.generated_user_stories") This issue has been promoted to US:
|
p.us-related-task(ng-if="issue.generated_user_stories") This issue has been promoted to US:
|
||||||
a(ng-repeat="us in issue.generated_user_stories",
|
a(ng-repeat="us in issue.generated_user_stories",
|
||||||
|
@ -30,20 +28,40 @@ block content
|
||||||
span.block-description(tg-bind-html="issue.blocked_note || 'This issue is blocked'")
|
span.block-description(tg-bind-html="issue.blocked_note || 'This issue is blocked'")
|
||||||
|
|
||||||
div.issue-nav
|
div.issue-nav
|
||||||
a.icon.icon-arrow-left(ng-show="previousUrl",href="{{ previousUrl }}", title="previous issue")
|
a.icon.icon-arrow-left(ng-show="previousUrl", tg-bo-href="previousUrl",
|
||||||
a.icon.icon-arrow-right(ng-show="nextUrl", href="{{ nextUrl }}", title="next issue")
|
title="previous issue")
|
||||||
|
a.icon.icon-arrow-right(ng-show="nextUrl", tg-bo-href="nextUrl",
|
||||||
|
title="next issue")
|
||||||
|
|
||||||
div(tg-tag-line, ng-model="issue.tags", ng-show="issue.tags")
|
div.tags-block(tg-tag-line, ng-model="issue", required-perm="modify_issue")
|
||||||
|
|
||||||
section.us-content.wysiwyg(tg-bind-html="issue.description_html")
|
section.duty-content.wysiwyg(tg-editable-description, ng-model="issue", required-perm="modify_issue")
|
||||||
|
|
||||||
tg-attachments(ng-model="issue", type="issue")
|
tg-attachments(ng-model="issue", type="issue")
|
||||||
tg-history(ng-model="issue", type="issue")
|
tg-history(ng-model="issue", type="issue")
|
||||||
|
|
||||||
sidebar.menu-secondary.sidebar
|
sidebar.menu-secondary.sidebar
|
||||||
section.us-status(tg-issue-status, ng-model="issue")
|
section.us-status
|
||||||
section.us-assigned-to(tg-assigned-to, ng-model="issue")
|
h1(tg-issue-status-display, ng-model="issue")
|
||||||
section.watchers(tg-watchers, ng-model="issue")
|
tg-created-by-display.us-created-by(ng-model="issue")
|
||||||
|
div.duty-data-container
|
||||||
|
div.duty-data(tg-issue-type-button, ng-model="issue")
|
||||||
|
div.duty-data(tg-issue-severity-button, ng-model="issue")
|
||||||
|
div.duty-data(tg-issue-priority-button, ng-model="issue")
|
||||||
|
div.duty-data(tg-issue-status-button, ng-model="issue")
|
||||||
|
|
||||||
|
section.duty-assigned-to(tg-assigned-to, ng-model="issue", required-perm="modify_issue")
|
||||||
|
|
||||||
|
section.watchers(tg-watchers, ng-model="issue", required-perm="modify_issue")
|
||||||
|
|
||||||
section.us-detail-settings
|
section.us-detail-settings
|
||||||
tg-promote-issue-to-us-button(ng-model="issue")
|
tg-promote-issue-to-us-button(tg-check-permission="add_us", ng-model="issue")
|
||||||
|
tg-block-button(tg-check-permission="modify_issue", ng-model="issue")
|
||||||
|
tg-delete-button(tg-check-permission="delete_issue",
|
||||||
|
on-delete-title="'Delete issue'",
|
||||||
|
on-delete-go-to-url="onDeleteGoToUrl",
|
||||||
|
ng-model="issue")
|
||||||
|
|
||||||
|
div.lightbox.lightbox-block.hidden(tg-lb-block, title="Blocking issue", ng-model="issue")
|
||||||
|
div.lightbox.lightbox-select-user(tg-lb-assignedto)
|
||||||
|
div.lightbox.lightbox-select-user(tg-lb-watchers)
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
extends dummy-layout
|
|
||||||
|
|
||||||
block head
|
|
||||||
title Taiga Your agile, free, and open source project management tool
|
|
||||||
|
|
||||||
block content
|
|
||||||
form.wrapper(tg-task-detail, ng-controller="TaskDetailController as ctrl",
|
|
||||||
ng-init="section='backlog'")
|
|
||||||
div.main.us-detail
|
|
||||||
div.us-detail-header.header-with-actions
|
|
||||||
include views/components/mainTitle
|
|
||||||
.action-buttons
|
|
||||||
a.button.button-green.save-task(href="", title="Save") Save
|
|
||||||
a.button.button-red.cancel(tg-nav="project-tasks-detail:project=project.slug,ref=task.ref", href="", title="Cancel") Cancel
|
|
||||||
|
|
||||||
section.us-story-main-data
|
|
||||||
div.us-title(ng-class="{blocked: task.is_blocked}")
|
|
||||||
div.us-edit-name-inner
|
|
||||||
span.us-number(tg-bo-ref="task.ref")
|
|
||||||
input(type="text", ng-model="task.subject", data-required="true", data-maxlength="500")
|
|
||||||
p.block-desc-container(ng-show="task.is_blocked")
|
|
||||||
span.block-description-title Blocked
|
|
||||||
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(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)
|
|
||||||
|
|
||||||
tg-attachments(ng-model="task", type="task")
|
|
||||||
tg-history(ng-model="task", type="task", mode="edit")
|
|
||||||
|
|
||||||
sidebar.menu-secondary.sidebar
|
|
||||||
section.us-status(tg-task-status, ng-model="task", editable="true")
|
|
||||||
section.us-assigned-to(tg-assigned-to, ng-model="task", editable="true")
|
|
||||||
section.watchers(tg-watchers, ng-model="task", editable="true")
|
|
||||||
|
|
||||||
section.us-detail-settings
|
|
||||||
fieldset(title="Feeling a bit overwhelmed by a task? Make sure others know about it by clicking on Iocaine when editing a task. It's possible to become immune to this (fictional) deadly poison by consuming small amounts over time just as it's possible to get better at what you do by occasionally taking on extra challenges!")
|
|
||||||
label.clickable.button.button-gray(for="is-iocaine", ng-class="{'active': task.is_iocaine}") Iocaine
|
|
||||||
input(ng-model="task.is_iocaine", type="checkbox", id="is-iocaine", name="is-iocaine")
|
|
||||||
|
|
||||||
a.button.button-gray.clickable(ng-show="!task.is_blocked", ng-click="ctrl.block()") Block
|
|
||||||
a.button.button-red(tg-check-permission="delete_task", ng-click="ctrl.delete()", href="") Delete
|
|
||||||
|
|
||||||
div.lightbox.lightbox-block.hidden(tg-lb-block, title="Blocking task", ng-model="task")
|
|
||||||
div.lightbox.lightbox-select-user.hidden(tg-lb-assignedto)
|
|
||||||
div.lightbox.lightbox-select-user.hidden(tg-lb-watchers)
|
|
|
@ -4,7 +4,7 @@ block head
|
||||||
title Taiga Your agile, free, and open source project management tool
|
title Taiga Your agile, free, and open source project management tool
|
||||||
|
|
||||||
block content
|
block content
|
||||||
div.wrapper(tg-task-detail, ng-controller="TaskDetailController as ctrl",
|
div.wrapper(ng-controller="TaskDetailController as ctrl",
|
||||||
ng-init="section='backlog'")
|
ng-init="section='backlog'")
|
||||||
div.main.us-detail
|
div.main.us-detail
|
||||||
div.us-detail-header.header-with-actions
|
div.us-detail-header.header-with-actions
|
||||||
|
@ -15,16 +15,12 @@ block content
|
||||||
href="", title="Go to taskboard",
|
href="", title="Go to taskboard",
|
||||||
tg-nav="project-taskboard:project=project.slug,sprint=sprint.slug",
|
tg-nav="project-taskboard:project=project.slug,sprint=sprint.slug",
|
||||||
ng-if="sprint && project.is_backlog_activated") Taskboard
|
ng-if="sprint && project.is_backlog_activated") Taskboard
|
||||||
a.button.button-green(
|
|
||||||
tg-check-permission="modify_task", href="",
|
|
||||||
title="Edit",
|
|
||||||
tg-nav="project-tasks-detail-edit:project=project.slug,ref=task.ref") Edit
|
|
||||||
|
|
||||||
section.us-story-main-data
|
section.us-story-main-data
|
||||||
div.us-title(ng-class="{blocked: task.is_blocked}")
|
div.us-title(ng-class="{blocked: task.is_blocked}")
|
||||||
h2.us-title-text
|
h2.us-title-text
|
||||||
span.us-number(tg-bo-ref="task.ref")
|
span.us-number(tg-bo-ref="task.ref")
|
||||||
span.us-name(ng-bind="task.subject")
|
span.us-name(tg-editable-subject, ng-model="task", required-perm="modify_task")
|
||||||
h3.us-related-task This task belongs to
|
h3.us-related-task This task belongs to
|
||||||
a(tg-check-permission="view_us", href="", title="Go to user story",
|
a(tg-check-permission="view_us", href="", title="Go to user story",
|
||||||
tg-nav="project-userstories-detail:project=project.slug, ref=us.ref",
|
tg-nav="project-userstories-detail:project=project.slug, ref=us.ref",
|
||||||
|
@ -35,20 +31,37 @@ block content
|
||||||
span.block-description-title Blocked
|
span.block-description-title Blocked
|
||||||
span.block-description(tg-bind-html="task.blocked_note || 'This task is blocked'")
|
span.block-description(tg-bind-html="task.blocked_note || 'This task is blocked'")
|
||||||
div.issue-nav
|
div.issue-nav
|
||||||
a.icon.icon-arrow-left(ng-show="previousUrl",href="{{ previousUrl }}", title="previous task")
|
a.icon.icon-arrow-left(ng-show="previousUrl", tg-bo-href="previousUrl",
|
||||||
a.icon.icon-arrow-right(ng-show="nextUrl", href="{{ nextUrl }}", title="next task")
|
title="previous task")
|
||||||
|
a.icon.icon-arrow-right(ng-show="nextUrl", tg-bo-href="nextUrl",
|
||||||
|
title="next task")
|
||||||
|
|
||||||
div(tg-tag-line, ng-model="task.tags", ng-show="task.tags")
|
div.tags-block(tg-tag-line, ng-model="task", required-perm="modify_task")
|
||||||
|
|
||||||
section.us-content.wysiwyg(tg-bind-html="task.description_html")
|
section.duty-content.wysiwyg(tg-editable-description, ng-model="task", required-perm="modify_task")
|
||||||
|
|
||||||
tg-attachments(ng-model="task", type="task")
|
tg-attachments(ng-model="task", type="task")
|
||||||
tg-history(ng-model="task", type="task")
|
tg-history(ng-model="task", type="task")
|
||||||
|
|
||||||
sidebar.menu-secondary.sidebar
|
sidebar.menu-secondary.sidebar
|
||||||
section.us-status(tg-task-status, ng-model="task")
|
section.us-status
|
||||||
section.us-assigned-to(tg-assigned-to, ng-model="task")
|
h1(tg-task-status-display, ng-model="task")
|
||||||
section.watchers(tg-watchers, ng-model="task")
|
div.us-created-by(tg-created-by-display, ng-model="task")
|
||||||
|
div.duty-data-container
|
||||||
|
div.duty-data(tg-task-status-button, ng-model="task")
|
||||||
|
|
||||||
|
section.duty-assigned-to(tg-assigned-to, ng-model="task", required-perm="modify_task")
|
||||||
|
|
||||||
|
section.watchers(tg-watchers, ng-model="task", required-perm="modify_task")
|
||||||
|
|
||||||
section.us-detail-settings
|
section.us-detail-settings
|
||||||
span.button.button-gray(href="", ng-class="{'active': task.is_iocaine }", title="Feeling a bit overwhelmed by a task? Make sure others know about it by clicking on Iocaine when editing a task. It's possible to become immune to this (fictional) deadly poison by consuming small amounts over time just as it's possible to get better at what you do by occasionally taking on extra challenges!") Iocaine
|
tg-task-is-iocaine-button(ng-model="task")
|
||||||
|
tg-block-button(tg-check-permission="modify_task", ng-model="task")
|
||||||
|
tg-delete-button(tg-check-permission="delete_task",
|
||||||
|
on-delete-title="'Delete Task'",
|
||||||
|
on-delete-go-to-url="onDeleteGoToUrl",
|
||||||
|
ng-model="task")
|
||||||
|
|
||||||
|
div.lightbox.lightbox-block.hidden(tg-lb-block, title="Blocking task", ng-model="task")
|
||||||
|
div.lightbox.lightbox-select-user(tg-lb-assignedto)
|
||||||
|
div.lightbox.lightbox-select-user(tg-lb-watchers)
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
extends dummy-layout
|
|
||||||
|
|
||||||
block head
|
|
||||||
title Taiga Your agile, free, and open source project management tool
|
|
||||||
|
|
||||||
block content
|
|
||||||
form.wrapper(tg-us-detail, ng-controller="UserStoryDetailController as ctrl",
|
|
||||||
ng-init="section='backlog'")
|
|
||||||
div.main.us-detail
|
|
||||||
div.us-detail-header.header-with-actions
|
|
||||||
include views/components/mainTitle
|
|
||||||
.action-buttons
|
|
||||||
a.button.button-green.save-us(href="", title="Save") Save
|
|
||||||
a.button.button-red.cancel(tg-nav="project-userstories-detail:project=project.slug,ref=us.ref", href="", title="Cancel") Cancel
|
|
||||||
|
|
||||||
section.us-story-main-data
|
|
||||||
div.us-title(ng-class="{blocked: us.is_blocked}")
|
|
||||||
div.us-edit-name-inner
|
|
||||||
span.us-number(tg-bo-ref="us.ref")
|
|
||||||
input(type="text", ng-model="us.subject", data-required="true", data-maxlength="500")
|
|
||||||
p.block-desc-container(ng-show="us.is_blocked")
|
|
||||||
span.block-description-title Blocked
|
|
||||||
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(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)
|
|
||||||
|
|
||||||
tg-attachments(ng-model="us", type="us")
|
|
||||||
tg-history(ng-model="us", type="us", mode="edit")
|
|
||||||
|
|
||||||
sidebar.menu-secondary.sidebar
|
|
||||||
section.us-status(tg-us-status-detail, ng-model="us", editable="true")
|
|
||||||
section.us-assigned-to(tg-assigned-to, ng-model="us", editable="true")
|
|
||||||
section.watchers(tg-watchers, ng-model="us", editable="true")
|
|
||||||
|
|
||||||
section.us-detail-settings
|
|
||||||
fieldset
|
|
||||||
label.clickable.button.button-gray(for="client-requirement", ng-class="{'active': us.client_requirement}") Client requirement
|
|
||||||
input(ng-model="us.client_requirement", type="checkbox", id="client-requirement", name="client-requirement")
|
|
||||||
fieldset
|
|
||||||
label.clickable.button.button-gray(for="team-requirement", ng-class="{'active': us.team_requirement}") Team requirement
|
|
||||||
input(ng-model="us.team_requirement", type="checkbox", id="team-requirement", name="team-requirement")
|
|
||||||
|
|
||||||
a.button.button-gray.clickable(ng-show="!us.is_blocked", ng-click="ctrl.block()") Block
|
|
||||||
a.button.button-red(tg-check-permission="delete_us", ng-click="ctrl.delete()", href="") Delete
|
|
||||||
|
|
||||||
div.lightbox.lightbox-block.hidden(tg-lb-block, title="Blocking issue", ng-model="us")
|
|
||||||
div.lightbox.lightbox-select-user.hidden(tg-lb-assignedto)
|
|
||||||
div.lightbox.lightbox-select-user.hidden(tg-lb-watchers)
|
|
|
@ -4,7 +4,7 @@ block head
|
||||||
title Taiga Your agile, free, and open source project management tool
|
title Taiga Your agile, free, and open source project management tool
|
||||||
|
|
||||||
block content
|
block content
|
||||||
div.wrapper(tg-us-detail, ng-controller="UserStoryDetailController as ctrl",
|
div.wrapper(ng-controller="UserStoryDetailController as ctrl",
|
||||||
ng-init="section='backlog'")
|
ng-init="section='backlog'")
|
||||||
div.main.us-detail
|
div.main.us-detail
|
||||||
div.us-detail-header.header-with-actions
|
div.us-detail-header.header-with-actions
|
||||||
|
@ -15,16 +15,12 @@ block content
|
||||||
href="", title="Go to taskboard",
|
href="", title="Go to taskboard",
|
||||||
tg-nav="project-taskboard:project=project.slug,sprint=sprint.slug",
|
tg-nav="project-taskboard:project=project.slug,sprint=sprint.slug",
|
||||||
ng-if="sprint && project.is_backlog_activated") Taskboard
|
ng-if="sprint && project.is_backlog_activated") Taskboard
|
||||||
a.button.button-green(
|
|
||||||
tg-check-permission="modify_us", href="",
|
|
||||||
title="Edit",
|
|
||||||
tg-nav="project-userstories-detail-edit:project=project.slug,ref=us.ref") Edit
|
|
||||||
|
|
||||||
section.us-story-main-data
|
section.us-story-main-data
|
||||||
div.us-title(ng-class="{blocked: us.is_blocked}")
|
div.us-title(ng-class="{blocked: us.is_blocked}")
|
||||||
h2.us-title-text
|
h2.us-title-text
|
||||||
span.us-number(tg-bo-ref="us.ref")
|
span.us-number(tg-bo-ref="us.ref")
|
||||||
span.us-name(ng-bind="us.subject")
|
span.us-name(tg-editable-subject, ng-model="us", required-perm="modify_us")
|
||||||
|
|
||||||
p.us-related-task(ng-if="us.origin_issue") This US has been promoted from Issue
|
p.us-related-task(ng-if="us.origin_issue") This US has been promoted from Issue
|
||||||
a(tg-check-permission="view_us", href="", title="Go to issue",
|
a(tg-check-permission="view_us", href="", title="Go to issue",
|
||||||
|
@ -36,13 +32,14 @@ block content
|
||||||
span.block-description-title Blocked
|
span.block-description-title Blocked
|
||||||
span.block-description(tg-bind-html="us.blocked_note || 'This user story is blocked'")
|
span.block-description(tg-bind-html="us.blocked_note || 'This user story is blocked'")
|
||||||
div.issue-nav
|
div.issue-nav
|
||||||
a.icon.icon-arrow-left(ng-show="previousUrl",href="{{ previousUrl }}",
|
a.icon.icon-arrow-left(ng-show="previousUrl", tg-bo-href="previousUrl",
|
||||||
title="previous user story")
|
title="previous user story")
|
||||||
a.icon.icon-arrow-right(ng-show="nextUrl", href="{{ nextUrl }}", title="next user story")
|
a.icon.icon-arrow-right(ng-show="nextUrl", tg-bo-href="nextUrl",
|
||||||
|
title="next user story")
|
||||||
|
|
||||||
div(tg-tag-line, ng-model="us.tags", ng-show="us.tags")
|
div.tags-block(tg-tag-line, ng-model="us", required-perm="modify_us")
|
||||||
|
|
||||||
section.us-content.wysiwyg(tg-bind-html="us.description_html")
|
section.duty-content.wysiwyg(tg-editable-description, ng-model="us", required-perm="modify_us")
|
||||||
|
|
||||||
include views/modules/related-tasks
|
include views/modules/related-tasks
|
||||||
|
|
||||||
|
@ -50,15 +47,27 @@ block content
|
||||||
tg-history(ng-model="us", type="us")
|
tg-history(ng-model="us", type="us")
|
||||||
|
|
||||||
sidebar.menu-secondary.sidebar
|
sidebar.menu-secondary.sidebar
|
||||||
section.us-status(tg-us-status-detail, ng-model="us")
|
section.us-status
|
||||||
section.us-assigned-to(tg-assigned-to, ng-model="us")
|
h1(tg-us-status-display, ng-model="us")
|
||||||
section.us-created-by(tg-created-by, ng-model="us")
|
div.us-detail-progress-bar(tg-us-tasks-progress-display, ng-model="tasks")
|
||||||
section.watchers(tg-watchers, ng-model="us")
|
tg-created-by-display.us-created-by(ng-model="us")
|
||||||
|
tg-us-estimation(ng-model="us", save-after-modify="true")
|
||||||
|
div.duty-data-container
|
||||||
|
div.duty-data(tg-us-status-button, ng-model="us")
|
||||||
|
|
||||||
|
section.duty-assigned-to(tg-assigned-to, ng-model="us", required-perm="modify_us")
|
||||||
|
|
||||||
|
section.watchers(tg-watchers, ng-model="us", required-perm="modify_us")
|
||||||
|
|
||||||
section.us-detail-settings
|
section.us-detail-settings
|
||||||
span.button.button-gray(href="", title="Client requirement",
|
tg-us-team-requirement-button(ng-model="us")
|
||||||
ng-class="{'active': us.client_requirement}") Client requirement
|
tg-us-client-requirement-button(ng-model="us")
|
||||||
span.button.button-gray(href="", title="Team requirement",
|
tg-block-button(tg-check-permission="modify_us", ng-model="us")
|
||||||
ng-class="{'active': us.team_requirement}") Team requirement
|
tg-delete-button(tg-check-permission="delete_us",
|
||||||
|
on-delete-title="'Delete User Story'",
|
||||||
|
on-delete-go-to-url="onDeleteGoToUrl",
|
||||||
|
ng-model="us")
|
||||||
|
|
||||||
|
div.lightbox.lightbox-block.hidden(tg-lb-block, title="Blocking us", ng-model="us")
|
||||||
div.lightbox.lightbox-select-user.hidden(tg-lb-assignedto)
|
div.lightbox.lightbox-select-user.hidden(tg-lb-assignedto)
|
||||||
|
div.lightbox.lightbox-select-user.hidden(tg-lb-watchers)
|
||||||
|
|
|
@ -14,7 +14,7 @@ form
|
||||||
select.severity(ng-model="issue.severity", ng-options="s.id as s.name for s in severityList")
|
select.severity(ng-model="issue.severity", ng-options="s.id as s.name for s in severityList")
|
||||||
|
|
||||||
fieldset
|
fieldset
|
||||||
div(tg-tag-line, editable="true", ng-model="issue.tags")
|
div.tags-block(tg-lb-tag-line, ng-model="issue.tags")
|
||||||
|
|
||||||
fieldset
|
fieldset
|
||||||
textarea.description(placeholder="Description", ng-model="issue.description")
|
textarea.description(placeholder="Description", ng-model="issue.description")
|
||||||
|
|
|
@ -16,7 +16,7 @@ form
|
||||||
option(value="") Unassigned
|
option(value="") Unassigned
|
||||||
|
|
||||||
fieldset
|
fieldset
|
||||||
div(tg-tag-line, editable="true", ng-model="task.tags")
|
div.tags-block(tg-lb-tag-line, ng-model="task.tags")
|
||||||
|
|
||||||
fieldset
|
fieldset
|
||||||
textarea.description(placeholder="Type a short description", ng-model="task.description")
|
textarea.description(placeholder="Type a short description", ng-model="task.description")
|
||||||
|
|
|
@ -8,14 +8,13 @@ form
|
||||||
|
|
||||||
fieldset.estimation
|
fieldset.estimation
|
||||||
tg-us-estimation(ng-model="us")
|
tg-us-estimation(ng-model="us")
|
||||||
//- Render by tg-lb-create-edit-userstory
|
|
||||||
|
|
||||||
fieldset
|
fieldset
|
||||||
select(name="status", ng-model="us.status", ng-options="s.id as s.name for s in usStatusList",
|
select(name="status", ng-model="us.status", ng-options="s.id as s.name for s in usStatusList",
|
||||||
tr="placeholder:common.status")
|
tr="placeholder:common.status")
|
||||||
|
|
||||||
fieldset
|
fieldset
|
||||||
div(tg-tag-line, editable="true", ng-model="us.tags")
|
div.tags-block(tg-lb-tag-line, ng-model="us.tags")
|
||||||
|
|
||||||
fieldset
|
fieldset
|
||||||
textarea.description(name="description", ng-model="us.description",
|
textarea.description(name="description", ng-model="us.description",
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
div.summary.wiki-summary
|
|
||||||
ul
|
|
||||||
li
|
|
||||||
span.number(tg-bo-bind="wiki.editions")
|
|
||||||
span.description times <br />edited
|
|
||||||
li(ng-if="wiki.modified_date")
|
|
||||||
span.number(tg-bo-bind="wiki.modified_date|momentFormat:'DD MMM YYYY HH:mm'")
|
|
||||||
span.description last <br />edit
|
|
||||||
li.username-edition(tg-wiki-user-info, ng-model='wiki')
|
|
|
@ -1,24 +0,0 @@
|
||||||
extends dummy-layout
|
|
||||||
|
|
||||||
block head
|
|
||||||
title Taiga Your agile, free, and open source project management tool
|
|
||||||
|
|
||||||
block content
|
|
||||||
div.wrapper(tg-wiki-edit, ng-controller="WikiEditController as ctrl",
|
|
||||||
ng-init="section='wiki'")
|
|
||||||
sidebar.menu-secondary.extrabar(tg-check-permission="view_wiki_links")
|
|
||||||
section.wiki-nav(tg-wiki-nav, ng-model="wikiLinks")
|
|
||||||
section.main.wiki
|
|
||||||
div.header-with-actions
|
|
||||||
h1
|
|
||||||
span(tg-bo-bind="project.name", class="project-name-short")
|
|
||||||
span.green Wiki
|
|
||||||
span.wiki-title(tg-bo-bind='wikiSlug|unslugify')
|
|
||||||
.action-buttons
|
|
||||||
a.button.button-green.save-wiki(href="", title="Save", ng-click="ctrl.save()") Save
|
|
||||||
a.button.button-red.cancel-wiki(href="", title="CAncel", ng-click="ctrl.cancel()") Cancel
|
|
||||||
|
|
||||||
section.wysiwyg
|
|
||||||
textarea(placeholder="Write a your wiki page", ng-model="wiki.content", tg-markitup)
|
|
||||||
|
|
||||||
tg-attachments(ng-model="wiki", type="wiki_page", ng-if="wiki.id")
|
|
|
@ -4,25 +4,22 @@ block head
|
||||||
title Taiga Your agile, free, and open source project management tool
|
title Taiga Your agile, free, and open source project management tool
|
||||||
|
|
||||||
block content
|
block content
|
||||||
div.wrapper(tg-wiki-detail, ng-controller="WikiDetailController as ctrl",
|
div.wrapper(ng-controller="WikiDetailController as ctrl",
|
||||||
ng-init="section='wiki'")
|
ng-init="section='wiki'")
|
||||||
sidebar.menu-secondary.extrabar(tg-check-permission="view_wiki_links")
|
sidebar.menu-secondary.extrabar(tg-check-permission="view_wiki_links")
|
||||||
section.wiki-nav(tg-wiki-nav, ng-model="wikiLinks")
|
section.wiki-nav(tg-wiki-nav, ng-model="wikiLinks")
|
||||||
section.main.wiki
|
section.main.wiki
|
||||||
.header-with-actions
|
.header
|
||||||
h1
|
h1
|
||||||
span(tg-bo-bind="project.name", class="project-name-short")
|
span(tg-bo-bind="project.name")
|
||||||
span.green Wiki
|
span.green Wiki
|
||||||
span.wiki-title(tg-bo-bind='wiki.slug|unslugify')
|
span.wiki-title(tg-bo-bind='wikiSlug|unslugify')
|
||||||
.action-buttons
|
|
||||||
a.button.button-red.delete-wiki(tg-check-permission="delete_wiki_page",
|
|
||||||
href="", title="Delete", ng-click="ctrl.delete()") Delete
|
|
||||||
|
|
||||||
a.button.button-green.edit-wiki(tg-check-permission="modify_wiki_page",
|
div.summary.wiki-summary(tg-wiki-summary, ng-model="wiki", ng-if="wiki.id")
|
||||||
href="", title="Edit", ng-click="ctrl.edit()") Edit
|
section.wiki-content(tg-editable-wiki-content, ng-model="wiki")
|
||||||
|
|
||||||
include views/modules/wiki-summary
|
tg-attachments(ng-model="wiki", type="wiki_page", ng-if="wiki.id")
|
||||||
|
|
||||||
section.wiki-content.wysiwyg(tg-bind-html="wiki.html")
|
a.remove(href="", ng-click="ctrl.delete()", ng-if="wiki.id", title="Remove this wiki page")
|
||||||
|
span.icon.icon-delete
|
||||||
tg-attachments(ng-model="wiki", type="wiki_page")
|
span Remove this wiki page
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
.markItUpHeader {
|
||||||
|
ul {
|
||||||
|
background: $whitish;
|
||||||
|
padding: .3rem;
|
||||||
|
li {
|
||||||
|
display: inline-block;
|
||||||
|
float: none;
|
||||||
|
a {
|
||||||
|
opacity: .8;
|
||||||
|
&:hover {
|
||||||
|
@include transition(opacity .2s linear);
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.preview-icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 2.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.markItUpContainer {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
.actions {
|
||||||
|
background: $whitish;
|
||||||
|
margin-top: .5rem;
|
||||||
|
min-height: 2rem;
|
||||||
|
padding: .3rem;
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,10 +30,8 @@
|
||||||
.tags-block {
|
.tags-block {
|
||||||
.tags-container {
|
.tags-container {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
|
||||||
}
|
}
|
||||||
input {
|
input {
|
||||||
display: inline-block;
|
|
||||||
padding: .4rem;
|
padding: .4rem;
|
||||||
width: 14rem;
|
width: 14rem;
|
||||||
}
|
}
|
||||||
|
@ -42,7 +40,18 @@
|
||||||
margin: 0 .5rem .5rem 0;
|
margin: 0 .5rem .5rem 0;
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
}
|
}
|
||||||
.save {
|
.add-tag {
|
||||||
display: none;
|
color: $gray-light;
|
||||||
|
&:hover {
|
||||||
|
color: $fresh-taiga;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.icon-plus {
|
||||||
|
@extend %large;
|
||||||
|
}
|
||||||
|
.add-tag-text {
|
||||||
|
@extend %small;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
.watchers {
|
.watchers {
|
||||||
margin-top: 2rem;
|
margin-top: 1rem;
|
||||||
.watchers-header {
|
.watchers-header {
|
||||||
border-bottom: 2px solid $gray-light;
|
border-bottom: 2px solid $gray-light;
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
|
|
|
@ -39,6 +39,10 @@ sup {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-spinner {
|
||||||
|
@include animation (spin 1s linear infinite);
|
||||||
|
}
|
||||||
|
|
||||||
.clickable {
|
.clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,12 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
max-width: 94%;
|
max-width: 94%;
|
||||||
|
&:hover {
|
||||||
|
.icon-edit {
|
||||||
|
@include transition(opacity .3s linear);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.us-number {
|
.us-number {
|
||||||
@extend %xlarge;
|
@extend %xlarge;
|
||||||
|
@ -72,6 +78,17 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
line-height: 2.2rem;
|
line-height: 2.2rem;
|
||||||
padding-right: 1rem;
|
padding-right: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.icon-edit,
|
||||||
|
.icon-floppy,
|
||||||
|
.icon-spinner {
|
||||||
|
@extend %large;
|
||||||
|
color: $gray-light;
|
||||||
|
margin-left: .5rem;
|
||||||
|
}
|
||||||
|
.icon-edit {
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
.us-related-task {
|
.us-related-task {
|
||||||
@extend %small;
|
@extend %small;
|
||||||
|
@ -133,12 +150,74 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.us-content {
|
.duty-content {
|
||||||
|
position: relative;
|
||||||
|
&:hover {
|
||||||
|
.view-description {
|
||||||
|
.edit {
|
||||||
|
@include transition(all .2s linear);
|
||||||
|
opacity: 1;
|
||||||
|
top: -1.5rem;
|
||||||
|
}
|
||||||
|
.editable {
|
||||||
|
background: $whitish;
|
||||||
|
cursor: pointer;
|
||||||
|
.no-description {
|
||||||
|
color: $grayer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.wysiwyg {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
.no-description {
|
||||||
|
color: $gray-light;
|
||||||
|
}
|
||||||
textarea {
|
textarea {
|
||||||
background: $white;
|
background: $white;
|
||||||
height: 10rem;
|
height: 10rem;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
.save-container {
|
||||||
|
position: absolute;
|
||||||
|
right: 1rem;
|
||||||
|
top: .2rem;
|
||||||
|
.save {
|
||||||
|
color: $blackish;
|
||||||
|
opacity: .6;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
@include transition(opacity .2s linear);
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.edit {
|
||||||
|
color: $grayer;
|
||||||
|
}
|
||||||
|
.view-description {
|
||||||
|
.edit {
|
||||||
|
@include transition(all .2s linear);
|
||||||
|
background: $whitish;
|
||||||
|
left: 0;
|
||||||
|
opacity: 0;
|
||||||
|
padding: .2rem .5rem;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.edit-description {
|
||||||
|
.save {
|
||||||
|
top: .4rem;
|
||||||
|
}
|
||||||
|
.edit {
|
||||||
|
@include transition(all .2s linear);
|
||||||
|
position: absolute;
|
||||||
|
right: 2.5rem;
|
||||||
|
top: .4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-list {
|
.comment-list {
|
||||||
|
@ -230,23 +309,25 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.issue-data {
|
.duty-data-container {
|
||||||
@extend %small;
|
@extend %small;
|
||||||
div {
|
margin-bottom: 1rem;
|
||||||
@include clearfix();
|
.duty-data {
|
||||||
@include transition(background .2s ease-in);
|
|
||||||
background: darken($whitish, 5%);
|
|
||||||
margin-bottom: .5rem;
|
margin-bottom: .5rem;
|
||||||
padding: .5rem;
|
|
||||||
padding-right: 1rem;
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
div {
|
||||||
.clickable {
|
|
||||||
&:hover {
|
|
||||||
@include transition(background .2s ease-in);
|
@include transition(background .2s ease-in);
|
||||||
background: darken($whitish, 10%);
|
background: darken($whitish, 5%);
|
||||||
|
padding: .5rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
.clickable {
|
||||||
|
&:hover {
|
||||||
|
@include transition(background .2s ease-in);
|
||||||
|
background: darken($whitish, 10%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.level {
|
.level {
|
||||||
|
@ -257,6 +338,10 @@
|
||||||
.level-name {
|
.level-name {
|
||||||
color: darken($whitish, 20%);
|
color: darken($whitish, 20%);
|
||||||
float: right;
|
float: right;
|
||||||
|
&.loading span {
|
||||||
|
@include animation (loading .5s linear);
|
||||||
|
@include animation (spin 1s linear infinite);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,9 +356,25 @@
|
||||||
}
|
}
|
||||||
.button-gray {
|
.button-gray {
|
||||||
background: $gray-light;
|
background: $gray-light;
|
||||||
&:hover,
|
&:hover {
|
||||||
|
background: $gray-light;
|
||||||
|
}
|
||||||
|
&.editable {
|
||||||
|
&:hover {
|
||||||
|
background: $grayer;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
&.active {
|
&.active {
|
||||||
background: $grayer;
|
background: $green-taiga;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item-block {
|
||||||
|
&.editable {
|
||||||
|
&:hover {
|
||||||
|
background: $red;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.button-red {
|
.button-red {
|
||||||
|
@ -283,7 +384,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
label {
|
label {
|
||||||
cursor: pointer;
|
&.editable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
+input {
|
+input {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,70 @@
|
||||||
|
.wiki {
|
||||||
|
.remove {
|
||||||
|
@extend %small;
|
||||||
|
color: $gray-light;
|
||||||
|
&:hover {
|
||||||
|
span {
|
||||||
|
@include transition(color .2s linear);
|
||||||
|
color: $grayer;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
@include transition(color .2s linear);
|
||||||
|
color: $red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
color: $gray-light;
|
||||||
|
margin-right: .3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.wiki-content {
|
.wiki-content {
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
|
position: relative;
|
||||||
|
.view-wiki-content {
|
||||||
|
&:hover {
|
||||||
|
.wysiwyg {
|
||||||
|
background: $whitish;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.edit {
|
||||||
|
@include transition(all .2s linear);
|
||||||
|
opacity: 1;
|
||||||
|
top: -1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.edit {
|
||||||
|
@include transition(all .2s linear);
|
||||||
|
background: $whitish;
|
||||||
|
left: 0;
|
||||||
|
opacity: 0;
|
||||||
|
padding: .2rem .5rem;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.edit-wiki-content {
|
||||||
|
.icon {
|
||||||
|
&:hover {
|
||||||
|
@include transition(all .2s linear);
|
||||||
|
color: $grayer;
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.preview-icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 3.5rem;
|
||||||
|
}
|
||||||
|
.action-container {
|
||||||
|
position: absolute;
|
||||||
|
right: 1rem;
|
||||||
|
top: .3rem;
|
||||||
|
}
|
||||||
|
.edit {
|
||||||
|
position: absolute;
|
||||||
|
right: 3.5rem;
|
||||||
|
top: .4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,26 @@
|
||||||
.us-assigned-to {
|
.duty-assigned-to {
|
||||||
@include table-flex();
|
@include table-flex();
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
&:hover {
|
||||||
|
.assigned-to {
|
||||||
|
.icon-delete {
|
||||||
|
@include transition (opacity .3s linear);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.loading {
|
||||||
|
width: 100%;
|
||||||
|
span {
|
||||||
|
font-size: 30px;
|
||||||
|
padding: 20px 0;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
@include animation (loading .5s linear);
|
||||||
|
@include animation (spin 1s linear infinite);
|
||||||
|
}
|
||||||
|
}
|
||||||
.user-avatar {
|
.user-avatar {
|
||||||
@include table-flex-child(1, 0);
|
@include table-flex-child(1, 0);
|
||||||
img {
|
img {
|
||||||
|
@ -22,13 +41,21 @@
|
||||||
@extend %large;
|
@extend %large;
|
||||||
color: $green-taiga;
|
color: $green-taiga;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
line-height: 1.5rem;
|
||||||
&.editable {
|
&.editable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.icon {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.assigned-name {
|
||||||
|
@include ellipsis(80%);
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
.icon-delete {
|
.icon-delete {
|
||||||
color: $gray-light;
|
color: $gray-light;
|
||||||
opacity: 1;
|
opacity: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
|
@ -98,7 +98,7 @@
|
||||||
}
|
}
|
||||||
.icon-edit,
|
.icon-edit,
|
||||||
.icon-floppy {
|
.icon-floppy {
|
||||||
right: 4rem;
|
right: 3.5rem;
|
||||||
}
|
}
|
||||||
.icon-delete {
|
.icon-delete {
|
||||||
right: 2rem;
|
right: 2rem;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
.history {
|
.history {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
}
|
||||||
.changes-title {
|
.changes-title {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -66,7 +65,22 @@
|
||||||
}
|
}
|
||||||
.add-comment {
|
.add-comment {
|
||||||
@include clearfix;
|
@include clearfix;
|
||||||
|
&.active {
|
||||||
|
.button-green {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
@include transition(height .3s ease-in);
|
||||||
|
height: 6rem;
|
||||||
|
}
|
||||||
|
.preview-icon {
|
||||||
|
opacity: 1;
|
||||||
|
position: absolute;
|
||||||
|
right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
textarea {
|
textarea {
|
||||||
|
background: $white;
|
||||||
float: left;
|
float: left;
|
||||||
height: 41px;
|
height: 41px;
|
||||||
margin-bottom: .5rem;
|
margin-bottom: .5rem;
|
||||||
|
@ -79,14 +93,13 @@
|
||||||
.button-green {
|
.button-green {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
&.active {
|
.edit,
|
||||||
.button-green {
|
.preview-icon {
|
||||||
display: block;
|
position: absolute;
|
||||||
}
|
right: 1rem;
|
||||||
textarea {
|
}
|
||||||
@include transition(height .3s ease-in);
|
.preview-icon {
|
||||||
height: 6rem;
|
opacity: 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
a.show-more-comments {
|
a.show-more-comments {
|
||||||
|
|
|
@ -61,6 +61,17 @@
|
||||||
.status {
|
.status {
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
&:hover {
|
||||||
|
.icon {
|
||||||
|
@include transition (opacity .2s ease-in);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.not-clickable {
|
||||||
|
&:hover {
|
||||||
|
color: $grayer;
|
||||||
|
}
|
||||||
|
}
|
||||||
.popover {
|
.popover {
|
||||||
a {
|
a {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
@ -73,6 +84,7 @@
|
||||||
.icon {
|
.icon {
|
||||||
color: $gray-light;
|
color: $gray-light;
|
||||||
margin-left: .2rem;
|
margin-left: .2rem;
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.pop-status {
|
.pop-status {
|
||||||
|
@ -160,8 +172,10 @@
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
.task-assignedto {
|
.task-assignedto {
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
&.editable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
.icon {
|
.icon {
|
||||||
@include transition(opacity .3s linear);
|
@include transition(opacity .3s linear);
|
||||||
|
|
|
@ -53,6 +53,8 @@ exports.files = function () {
|
||||||
'components/spinner',
|
'components/spinner',
|
||||||
'components/help-notion-button',
|
'components/help-notion-button',
|
||||||
'components/beta',
|
'components/beta',
|
||||||
|
'components/markitup',
|
||||||
|
|
||||||
|
|
||||||
//#################################################
|
//#################################################
|
||||||
// Modules
|
// Modules
|
||||||
|
|