<% if(perms.modify_task) { %>
@@ -166,7 +166,7 @@ RelatedTaskRowDirective = ($repo, $compile, $confirm, $rootscope, $loading) ->
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) ->
template = _.template("""
diff --git a/app/coffee/modules/resources/projects.coffee b/app/coffee/modules/resources/projects.coffee
index 19437881..f3e1ab51 100644
--- a/app/coffee/modules/resources/projects.coffee
+++ b/app/coffee/modules/resources/projects.coffee
@@ -45,9 +45,6 @@ resourceProvider = ($repo) ->
service.stats = (projectId) ->
return $repo.queryOneRaw("projects", "#{projectId}/stats")
- service.tags = (projectId) ->
- return $repo.queryOneRaw("projects", "#{projectId}/tags")
-
service.tagsColors = (id) ->
return $repo.queryOne("projects", "#{id}/tags_colors")
diff --git a/app/coffee/modules/taskboard/lightboxes.coffee b/app/coffee/modules/taskboard/lightboxes.coffee
index 9a28e5da..6a1c09f0 100644
--- a/app/coffee/modules/taskboard/lightboxes.coffee
+++ b/app/coffee/modules/taskboard/lightboxes.coffee
@@ -25,7 +25,7 @@ debounce = @.taiga.debounce
CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, lightboxService) ->
link = ($scope, $el, attrs) ->
- isNew = true
+ $scope.isNew = true
$scope.$on "taskform:new", (ctx, sprintId, usId) ->
$scope.task = {
@@ -37,7 +37,7 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, lightboxService) ->
assigned_to: null
tags: []
}
- isNew = true
+ $scope.isNew = true
# Update texts for creation
$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.task = task
- isNew = false
+ $scope.isNew = false
# Update texts for edition
$el.find(".button-green span").html("Save") #TODO: i18n
@@ -60,7 +60,7 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, lightboxService) ->
if not form.validate()
return
- if isNew
+ if $scope.isNew
promise = $repo.create("tasks", $scope.task)
broadcastEvent = "taskform:new:success"
else
diff --git a/app/coffee/modules/tasks/detail.coffee b/app/coffee/modules/tasks/detail.coffee
index de3b715d..5598ccc5 100644
--- a/app/coffee/modules/tasks/detail.coffee
+++ b/app/coffee/modules/tasks/detail.coffee
@@ -57,6 +57,7 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
promise.then () =>
@appTitle.set(@scope.task.subject + " - " + @scope.project.name)
+ @.initializeOnDeleteGoToUrl()
tgLoader.pageLoaded()
promise.then null, @.onInitialDataError.bind(@)
@@ -70,6 +71,21 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
@scope.$on "attachment:delete", =>
@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: ->
return @rs.projects.get(@scope.projectId).then (project) =>
@scope.project = project
@@ -97,14 +113,19 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
ref: @scope.task.neighbors.next.ref
}
@scope.nextUrl = @navUrls.resolve("project-tasks-detail", ctx)
+ return task
- if task.milestone
- @rs.sprints.get(task.project, task.milestone).then (sprint) =>
- @scope.sprint = sprint
+ loadSprint: ->
+ if @scope.task.milestone
+ return @rs.sprints.get(@scope.task.project, @scope.task.milestone).then (sprint) =>
+ @scope.sprint = sprint
+ return sprint
- if task.user_story
- @rs.userstories.get(task.project, task.user_story).then (us) =>
- @scope.us = us
+ loadUserStory: ->
+ if @scope.task.user_story
+ return @rs.userstories.get(@scope.task.project, @scope.task.user_story).then (us) =>
+ @scope.us = us
+ return us
loadInitialData: ->
params = {
@@ -119,157 +140,216 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
return promise.then(=> @.loadProject())
.then(=> @.loadUsersAndRoles())
- .then(=> @.loadTask())
-
- 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")
+ .then(=> @.loadTask().then(=> @q.all([@.loadUserStory(),
+ @.loadSprint()])))
module.controller("TaskDetailController", TaskDetailController)
#############################################################################
-## Task Main Directive
+## Task status display directive
#############################################################################
-TaskDirective = ($tgrepo, $log, $location, $confirm, $navUrls, $loading) ->
- linkSidebar = ($scope, $el, $attrs, $ctrl) ->
+TaskStatusDisplayDirective = ->
+ # 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("""
+
+ <% if (status.is_closed) { %>
+ Closed
+ <% } else { %>
+ Open
+ <% } %>
+
+
+ <%= status.name %>
+
+ """) # TODO: i18n
link = ($scope, $el, $attrs) ->
- $ctrl = $el.controller()
- linkSidebar($scope, $el, $attrs, $ctrl)
+ render = (task) ->
+ html = template({
+ status: $scope.statusById[task.status]
+ })
+ $el.html(html)
- if $el.is("form")
- form = $el.checksley()
+ $scope.$watch $attrs.ngModel, (task) ->
+ render(task) if task?
- $el.on "click", ".save-task", (event) ->
- if not form.validate()
- return
+ $scope.$on "$destroy", ->
+ $el.off()
- onSuccess = ->
- $loading.finish(target)
- $confirm.notify("success")
- ctx = {
- project: $scope.project.slug
- ref: $scope.task.ref
- }
- $location.path($navUrls.resolve("project-tasks-detail", ctx))
+ return {
+ link: link
+ restrict: "EA"
+ require: "ngModel"
+ }
- onError = ->
- $loading.finish(target)
- $confirm.notify("error")
+module.directive("tgTaskStatusDisplay", TaskStatusDisplayDirective)
+
+
+#############################################################################
+## 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("""
+
+
+
<%= status.name %>
+ <% if(editable){ %>
<% }%>
+
status
+
+
+ <% _.each(statuses, function(st) { %>
+ - <%- st.name %>
+ <% }); %>
+
+
+ """) #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)
- $loading.start(target)
- $tgrepo.save($scope.task).then(onSuccess, onError)
- return {link:link}
+ $.fn.popover().closeAll()
-module.directive("tgTaskDetail", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm", "$tgNavUrls",
- "$tgLoading", TaskDirective])
+ task = $model.$modelValue.clone()
+ 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])
-#############################################################################
-## Task status directive
-#############################################################################
-
-TaskStatusDirective = () ->
- #TODO: i18n
+TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
template = _.template("""
-
-
- <% if (status.is_closed) { %>
- Closed
- <% } else { %>
- Open
- <% } %>
- <%= status.name %>
-
-
-
-

-
-
-
- Created by <%- owner.full_name_display %>
- <%- date %>
-
-
-
-
-
- <%= status.name %>
- <% if (editable) { %>
-
- <% } %>
- status
-
-
- """)
- selectionStatusTemplate = _.template("""
-
+
""")
link = ($scope, $el, $attrs, $model) ->
- editable = $attrs.editable?
+ isEditable = ->
+ return $scope.project.my_permissions.indexOf("modify_task") != -1
- renderTaskstatus = (task) ->
- owner = $scope.usersById?[task.owner]
- date = moment(task.created_date).format("DD MMM YYYY HH:mm")
- status = $scope.statusById[task.status]
- html = template({
- owner: owner
- date: date
- editable: editable
- status: status
- })
+ render = (task) ->
+ if not isEditable() and not task.is_iocaine
+ $el.html("")
+ return
+
+ ctx = {
+ isIocaine: task.is_iocaine
+ isEditable: isEditable()
+ }
+ html = template(ctx)
$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) ->
- if task?
- renderTaskstatus(task)
+ render(task) if task
- if editable
- $el.on "click", ".status-data", (event) ->
- event.preventDefault()
- event.stopPropagation()
- $el.find(".pop-status").popover().open()
+ $scope.$on "$destroy", ->
+ $el.off()
- $el.on "click", ".status", (event) ->
- event.preventDefault()
- event.stopPropagation()
- target = angular.element(event.currentTarget)
- $model.$modelValue.status = target.data("status-id")
- renderTaskstatus($model.$modelValue)
- $el.find(".popover").popover().close()
+ return {
+ link: link
+ restrict: "EA"
+ require: "ngModel"
+ }
- return {link:link, require:"ngModel"}
-
-module.directive("tgTaskStatus", TaskStatusDirective)
+module.directive("tgTaskIsIocaineButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", TaskIsIocaineButtonDirective])
diff --git a/app/coffee/modules/userstories/detail.coffee b/app/coffee/modules/userstories/detail.coffee
index f3845362..050679b4 100644
--- a/app/coffee/modules/userstories/detail.coffee
+++ b/app/coffee/modules/userstories/detail.coffee
@@ -59,12 +59,17 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
# On Success
promise.then =>
@appTitle.set(@scope.us.subject + " - " + @scope.project.name)
+ @.initializeOnDeleteGoToUrl()
tgLoader.pageLoaded()
# On Error
promise.then null, @.onInitialDataError.bind(@)
initializeEventHandlers: ->
+ @scope.$on "related-tasks:update", =>
+ @.loadUs()
+ @scope.tasks = _.clone(@scope.tasks, false)
+
@scope.$on "attachment:create", =>
@analytics.trackEvent("attachment", "create", "create attachment on userstory", 1)
@rootscope.$broadcast("history:reload")
@@ -75,6 +80,18 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
@scope.$on "attachment:delete", =>
@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: ->
return @rs.projects.get(@scope.projectId).then (project) =>
@scope.project = project
@@ -106,12 +123,14 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
}
@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
+ loadSprint: ->
+ if @scope.us.milestone
+ return @rs.sprints.get(@scope.us.project, @scope.us.milestone).then (sprint) =>
+ @scope.sprint = sprint
+ return sprint
+
loadTasks: ->
return @rs.tasks.list(@scope.projectId, null, @scope.usId).then (tasks) =>
@scope.tasks = tasks
@@ -130,263 +149,128 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
return promise.then(=> @.loadProject())
.then(=> @.loadUsersAndRoles())
- .then(=> @q.all([@.loadUs(),
+ .then(=> @q.all([@.loadUs().then(=> @.loadSprint()),
@.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)
+
#############################################################################
-## User story Main Directive
+## User story status display directive
#############################################################################
-UsDirective = ($tgrepo, $log, $location, $confirm, $navUrls, $loading) ->
- linkSidebar = ($scope, $el, $attrs, $ctrl) ->
+UsStatusDisplayDirective = ->
+ # 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("""
+
+ <% if (is_closed) { %>
+ Closed
+ <% } else { %>
+ Open
+ <% } %>
+
+
+ <%= status.name %>
+
+ """) # TODO: i18n
link = ($scope, $el, $attrs) ->
- $ctrl = $el.controller()
- 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("""
-
-
- <% if (is_closed) { %>
- Closed
- <% } else { %>
- Open
- <% } %>
- <%= status.name %>
-
-
-
-
-
- <%- totalClosedTasks %>/<%- totalTasks %> tasks completed
-
-
-
-
-
-

-
-
-
- Created by <%- owner.full_name_display %>
- <%- date %>
-
-
-
-
- -
- <%- totalPoints %>
- total
-
- <% _.each(rolePoints, function(rolePoint) { %>
- -
- <%- rolePoint.points %>
- <%- rolePoint.name %>
- <% }); %>
-
-
-
-
-
- <%= status.name %>
- <% if (editable) { %>
-
- <% } %>
- status
-
-
- """)
- selectionStatusTemplate = _.template("""
-
- """)
- selectionPointsTemplate = _.template("""
-
- """)
-
- 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
+ render = (us) ->
html = template({
- owner: owner
- date: date
- editable: editable
is_closed: us.is_closed
- status: status
- totalPoints: us.total_points
- rolePoints: rolePoints
- totalTasks: totalTasks
- totalClosedTasks: totalClosedTasks
- usProgress: usProgress
+ status: $scope.statusById[us.status]
})
$el.html(html)
- $el.find(".status-data").append(selectionStatusTemplate({statuses:$scope.statusList}))
- bindOnce $scope, "tasks", (tasks) ->
- $scope.$watch $attrs.ngModel, (us) ->
- if us?
- renderUsstatus(us)
+ $scope.$watch $attrs.ngModel, (us) ->
+ render(us) if us?
- $scope.$on "related-tasks:update", ->
- us = $scope.$eval $attrs.ngModel
- if us?
- # Reload the us because the status could have changed
- $ctrl.loadUs()
- renderUsstatus(us)
+ $scope.$on "$destroy", ->
+ $el.off()
- if editable
- $el.on "click", ".status-data", (event) ->
- event.preventDefault()
- event.stopPropagation()
- $el.find(".pop-status").popover().open()
+ return {
+ link: link
+ restrict: "EA"
+ require: "ngModel"
+ }
- $el.on "click", ".status", (event) ->
- event.preventDefault()
- event.stopPropagation()
- target = angular.element(event.currentTarget)
- $model.$modelValue.status = target.data("status-id")
- renderUsstatus($model.$modelValue)
- $.fn.popover().closeAll()
+module.directive("tgUsStatusDisplay", UsStatusDisplayDirective)
- $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()
- event.stopPropagation()
+#############################################################################
+## User story related tasts progress splay Directive
+#############################################################################
- target = angular.element(event.currentTarget)
- $.fn.popover().closeAll()
+UsTasksProgressDisplayDirective = ->
+ # 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 () ->
- us = $model.$modelValue
- usPoints = _.clone(us.points, true)
- usPoints[updatingSelectedRoleId] = target.data("point-id")
- us.points = usPoints
- us.total_points = calculateTotalPoints(us)
- renderUsstatus(us)
+ template = _.template("""
+
+
+ <%- totalClosedTasks %>/<%- totalTasks %> tasks completed
+
+ """) # TODO: i18n
- 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
#############################################################################
-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("""
-
@@ -394,7 +278,7 @@ UsEstimationDirective = ($log) ->
total
<% _.each(roles, function(role) { %>
- -
+
-
<%- role.points %>
<%- role.name %>
<% }); %>
@@ -417,7 +301,14 @@ UsEstimationDirective = ($log) ->
""")
- 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) ->
totalPoints = us.total_points or 0
computableRoles = _.filter($scope.project.roles, "computable")
@@ -430,7 +321,12 @@ UsEstimationDirective = ($log) ->
role.points = if pointObj? and pointObj.name? then pointObj.name else "?"
return role
- html = mainTemplate({totalPoints: totalPoints, roles: roles})
+ ctx = {
+ totalPoints: totalPoints
+ roles: roles
+ editable: isEditable()
+ }
+ html = mainTemplate(ctx)
$el.html(html)
renderPoints = (target, us, roleId) ->
@@ -463,19 +359,15 @@ UsEstimationDirective = ($log) ->
return "0"
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) ->
event.preventDefault()
event.stopPropagation()
+ return if not isEditable()
+
target = angular.element(event.currentTarget)
roleId = target.data("role-id")
- us = $scope.$eval($attrs.ngModel)
+ us = $model.$modelValue
renderPoints(target, us, roleId)
target.siblings().removeClass('active')
@@ -484,8 +376,7 @@ UsEstimationDirective = ($log) ->
$el.on "click", ".point", (event) ->
event.preventDefault()
event.stopPropagation()
-
- us = $scope.$eval($attrs.ngModel)
+ return if not isEditable()
target = angular.element(event.currentTarget)
roleId = target.data("role-id")
@@ -493,17 +384,262 @@ UsEstimationDirective = ($log) ->
$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
+ us.setAttr('points', points) if us.setAttr?
+ us.points = points
+ us.total_points = calculateTotalPoints(us)
+ $model.$setViewValue(us)
- $scope.$apply ->
- us.points = points
- us.total_points = calculateTotalPoints(us)
- render(us)
+ if saveAfterModify
+ # Edit in the detail page
+ onSuccess = ->
+ $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 {
link: link
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("""
+
+
+
<%= status.name %>
+ <% if(editable){ %>
<% }%>
+
status
+
+
+ <% _.each(statuses, function(st) { %>
+ - <%- st.name %>
+ <% }); %>
+
+
+ """) #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("""
+
+
+ """) #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("""
+
+
+ """) #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])
diff --git a/app/coffee/modules/wiki/main.coffee b/app/coffee/modules/wiki/main.coffee
index de58d10e..354822ef 100644
--- a/app/coffee/modules/wiki/main.coffee
+++ b/app/coffee/modules/wiki/main.coffee
@@ -38,6 +38,7 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
"$scope",
"$rootScope",
"$tgRepo",
+ "$tgModel",
"$tgConfirm",
"$tgResources",
"$routeParams",
@@ -51,7 +52,7 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
"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) ->
@scope.projectSlug = @params.pslug
@scope.wikiSlug = @params.slug
@@ -80,7 +81,15 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
@scope.wiki = 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
loadWikiLinks: ->
@@ -109,28 +118,13 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
@scope.wikiId = data.wikipage
return prom.then null, (xhr) =>
- ctx = {project: @params.pslug, slug: @params.slug}
- @location.path(@navUrls.resolve("project-wiki-page-edit", ctx))
+ @scope.wikiId = null
return promise.then(=> @.loadProject())
.then(=> @.loadUsersAndRoles())
.then(=> @q.all([@.loadWikiLinks(),
@.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: ->
# TODO: i18n
title = "Delete Wiki Page"
@@ -151,95 +145,181 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
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) ->
- 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) ->
+WikiSummaryDirective = ($log) ->
template = _.template("""
-
-
-
-
last modification
-
<%- name %>
+
+ -
+ <%- totalEditions %>
+ times
edited
+
+ -
+ <%- lastModifiedDate %>
+ last
edit
+
+ -
+
+
+
+ last modification
+ <%- user.name %>
+
+
""")
- link = ($scope, $el, $attrs) ->
- if not $attrs.ngModel?
- return $log.error "WikiUserDirective: no ng-model attr is defined"
-
+ link = ($scope, $el, $attrs, $model) ->
render = (wiki) ->
if not $scope.usersById?
- $log.error "WikiUserDirective requires userById set in scope."
+ $log.error "WikiSummaryDirective requires userById set in scope."
else
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)
$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 {
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 = """
+
+
+
+
+
+ """ # 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])
diff --git a/app/images/markitup/bold.png b/app/images/markitup/bold.png
old mode 100755
new mode 100644
index 889ae80e..714fd12b
Binary files a/app/images/markitup/bold.png and b/app/images/markitup/bold.png differ
diff --git a/app/images/markitup/code.png b/app/images/markitup/code.png
index 63fe6cef..a4156e2b 100644
Binary files a/app/images/markitup/code.png and b/app/images/markitup/code.png differ
diff --git a/app/images/markitup/italic.png b/app/images/markitup/italic.png
old mode 100755
new mode 100644
index 8482ac8c..3793aa56
Binary files a/app/images/markitup/italic.png and b/app/images/markitup/italic.png differ
diff --git a/app/images/markitup/link.png b/app/images/markitup/link.png
index 25eacb7c..9a610fb5 100755
Binary files a/app/images/markitup/link.png and b/app/images/markitup/link.png differ
diff --git a/app/images/markitup/list-bullet.png b/app/images/markitup/list-bullet.png
old mode 100755
new mode 100644
index 4a8672bd..1b45081e
Binary files a/app/images/markitup/list-bullet.png and b/app/images/markitup/list-bullet.png differ
diff --git a/app/images/markitup/list-numeric.png b/app/images/markitup/list-numeric.png
index 33b0b8df..e01ac608 100755
Binary files a/app/images/markitup/list-numeric.png and b/app/images/markitup/list-numeric.png differ
diff --git a/app/images/markitup/picture.png b/app/images/markitup/picture.png
old mode 100755
new mode 100644
index 4a158fef..5e638e1d
Binary files a/app/images/markitup/picture.png and b/app/images/markitup/picture.png differ
diff --git a/app/images/markitup/preview.png b/app/images/markitup/preview.png
index a9925a06..51ae6c3e 100755
Binary files a/app/images/markitup/preview.png and b/app/images/markitup/preview.png differ
diff --git a/app/images/markitup/quotes.png b/app/images/markitup/quotes.png
index e54ebeba..55620b63 100644
Binary files a/app/images/markitup/quotes.png and b/app/images/markitup/quotes.png differ
diff --git a/app/images/markitup/stroke.png b/app/images/markitup/stroke.png
old mode 100755
new mode 100644
index 612058a7..1b3971f1
Binary files a/app/images/markitup/stroke.png and b/app/images/markitup/stroke.png differ
diff --git a/app/partials/issues-detail-edit.jade b/app/partials/issues-detail-edit.jade
deleted file mode 100644
index 4cc4db4c..00000000
--- a/app/partials/issues-detail-edit.jade
+++ /dev/null
@@ -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)
diff --git a/app/partials/issues-detail.jade b/app/partials/issues-detail.jade
index 15075c3a..c4cdfb72 100644
--- a/app/partials/issues-detail.jade
+++ b/app/partials/issues-detail.jade
@@ -4,19 +4,17 @@ block head
title Taiga Your agile, free, and open source project management tool
block content
- div.wrapper(tg-issue-detail, ng-controller="IssueDetailController as ctrl",
+ div.wrapper(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(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
div.us-title(ng-class="{blocked: issue.is_blocked}")
h2.us-title-text
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:
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'")
div.issue-nav
- a.icon.icon-arrow-left(ng-show="previousUrl",href="{{ previousUrl }}", title="previous issue")
- a.icon.icon-arrow-right(ng-show="nextUrl", href="{{ nextUrl }}", title="next issue")
+ a.icon.icon-arrow-left(ng-show="previousUrl", tg-bo-href="previousUrl",
+ 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-history(ng-model="issue", type="issue")
sidebar.menu-secondary.sidebar
- section.us-status(tg-issue-status, ng-model="issue")
- section.us-assigned-to(tg-assigned-to, ng-model="issue")
- section.watchers(tg-watchers, ng-model="issue")
+ section.us-status
+ h1(tg-issue-status-display, 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
- 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)
diff --git a/app/partials/task-detail-edit.jade b/app/partials/task-detail-edit.jade
deleted file mode 100644
index a056b697..00000000
--- a/app/partials/task-detail-edit.jade
+++ /dev/null
@@ -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)
diff --git a/app/partials/task-detail.jade b/app/partials/task-detail.jade
index 1f9727d3..052648a1 100644
--- a/app/partials/task-detail.jade
+++ b/app/partials/task-detail.jade
@@ -4,7 +4,7 @@ block head
title Taiga Your agile, free, and open source project management tool
block content
- div.wrapper(tg-task-detail, ng-controller="TaskDetailController as ctrl",
+ div.wrapper(ng-controller="TaskDetailController as ctrl",
ng-init="section='backlog'")
div.main.us-detail
div.us-detail-header.header-with-actions
@@ -15,16 +15,12 @@ block content
href="", title="Go to taskboard",
tg-nav="project-taskboard:project=project.slug,sprint=sprint.slug",
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
div.us-title(ng-class="{blocked: task.is_blocked}")
h2.us-title-text
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
a(tg-check-permission="view_us", href="", title="Go to user story",
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(tg-bind-html="task.blocked_note || 'This task is blocked'")
div.issue-nav
- a.icon.icon-arrow-left(ng-show="previousUrl",href="{{ previousUrl }}", title="previous task")
- a.icon.icon-arrow-right(ng-show="nextUrl", href="{{ nextUrl }}", title="next task")
+ a.icon.icon-arrow-left(ng-show="previousUrl", tg-bo-href="previousUrl",
+ 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-history(ng-model="task", type="task")
sidebar.menu-secondary.sidebar
- section.us-status(tg-task-status, ng-model="task")
- section.us-assigned-to(tg-assigned-to, ng-model="task")
- section.watchers(tg-watchers, ng-model="task")
+ section.us-status
+ h1(tg-task-status-display, 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
- 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)
diff --git a/app/partials/us-detail-edit.jade b/app/partials/us-detail-edit.jade
deleted file mode 100644
index 43fe65c2..00000000
--- a/app/partials/us-detail-edit.jade
+++ /dev/null
@@ -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)
diff --git a/app/partials/us-detail.jade b/app/partials/us-detail.jade
index 2553c7f4..93fe31ce 100644
--- a/app/partials/us-detail.jade
+++ b/app/partials/us-detail.jade
@@ -4,7 +4,7 @@ block head
title Taiga Your agile, free, and open source project management tool
block content
- div.wrapper(tg-us-detail, ng-controller="UserStoryDetailController as ctrl",
+ div.wrapper(ng-controller="UserStoryDetailController as ctrl",
ng-init="section='backlog'")
div.main.us-detail
div.us-detail-header.header-with-actions
@@ -15,16 +15,12 @@ block content
href="", title="Go to taskboard",
tg-nav="project-taskboard:project=project.slug,sprint=sprint.slug",
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
div.us-title(ng-class="{blocked: us.is_blocked}")
h2.us-title-text
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
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(tg-bind-html="us.blocked_note || 'This user story is blocked'")
div.issue-nav
- a.icon.icon-arrow-left(ng-show="previousUrl",href="{{ previousUrl }}",
- title="previous user story")
- a.icon.icon-arrow-right(ng-show="nextUrl", href="{{ nextUrl }}", title="next user story")
+ a.icon.icon-arrow-left(ng-show="previousUrl", tg-bo-href="previousUrl",
+ title="previous 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
@@ -50,15 +47,27 @@ block content
tg-history(ng-model="us", type="us")
sidebar.menu-secondary.sidebar
- section.us-status(tg-us-status-detail, ng-model="us")
- section.us-assigned-to(tg-assigned-to, ng-model="us")
- section.us-created-by(tg-created-by, ng-model="us")
- section.watchers(tg-watchers, ng-model="us")
+ section.us-status
+ h1(tg-us-status-display, ng-model="us")
+ div.us-detail-progress-bar(tg-us-tasks-progress-display, ng-model="tasks")
+ 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
- span.button.button-gray(href="", title="Client requirement",
- ng-class="{'active': us.client_requirement}") Client requirement
- span.button.button-gray(href="", title="Team requirement",
- ng-class="{'active': us.team_requirement}") Team requirement
+ tg-us-team-requirement-button(ng-model="us")
+ tg-us-client-requirement-button(ng-model="us")
+ tg-block-button(tg-check-permission="modify_us", ng-model="us")
+ 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-watchers)
diff --git a/app/partials/views/modules/lightbox-create-issue.jade b/app/partials/views/modules/lightbox-create-issue.jade
index 1a7142de..1dbe545d 100644
--- a/app/partials/views/modules/lightbox-create-issue.jade
+++ b/app/partials/views/modules/lightbox-create-issue.jade
@@ -14,7 +14,7 @@ form
select.severity(ng-model="issue.severity", ng-options="s.id as s.name for s in severityList")
fieldset
- div(tg-tag-line, editable="true", ng-model="issue.tags")
+ div.tags-block(tg-lb-tag-line, ng-model="issue.tags")
fieldset
textarea.description(placeholder="Description", ng-model="issue.description")
diff --git a/app/partials/views/modules/lightbox-task-create-edit.jade b/app/partials/views/modules/lightbox-task-create-edit.jade
index 5ff14193..96772bd8 100644
--- a/app/partials/views/modules/lightbox-task-create-edit.jade
+++ b/app/partials/views/modules/lightbox-task-create-edit.jade
@@ -16,7 +16,7 @@ form
option(value="") Unassigned
fieldset
- div(tg-tag-line, editable="true", ng-model="task.tags")
+ div.tags-block(tg-lb-tag-line, ng-model="task.tags")
fieldset
textarea.description(placeholder="Type a short description", ng-model="task.description")
diff --git a/app/partials/views/modules/lightbox-us-create-edit.jade b/app/partials/views/modules/lightbox-us-create-edit.jade
index a916418c..15808251 100644
--- a/app/partials/views/modules/lightbox-us-create-edit.jade
+++ b/app/partials/views/modules/lightbox-us-create-edit.jade
@@ -8,14 +8,13 @@ form
fieldset.estimation
tg-us-estimation(ng-model="us")
- //- Render by tg-lb-create-edit-userstory
fieldset
select(name="status", ng-model="us.status", ng-options="s.id as s.name for s in usStatusList",
tr="placeholder:common.status")
fieldset
- div(tg-tag-line, editable="true", ng-model="us.tags")
+ div.tags-block(tg-lb-tag-line, ng-model="us.tags")
fieldset
textarea.description(name="description", ng-model="us.description",
diff --git a/app/partials/views/modules/wiki-summary.jade b/app/partials/views/modules/wiki-summary.jade
deleted file mode 100644
index bcd4bd80..00000000
--- a/app/partials/views/modules/wiki-summary.jade
+++ /dev/null
@@ -1,9 +0,0 @@
-div.summary.wiki-summary
- ul
- li
- span.number(tg-bo-bind="wiki.editions")
- span.description times
edited
- li(ng-if="wiki.modified_date")
- span.number(tg-bo-bind="wiki.modified_date|momentFormat:'DD MMM YYYY HH:mm'")
- span.description last
edit
- li.username-edition(tg-wiki-user-info, ng-model='wiki')
diff --git a/app/partials/wiki-edit.jade b/app/partials/wiki-edit.jade
deleted file mode 100644
index 2d1b04f6..00000000
--- a/app/partials/wiki-edit.jade
+++ /dev/null
@@ -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")
diff --git a/app/partials/wiki.jade b/app/partials/wiki.jade
index 27d95235..fb0a7578 100644
--- a/app/partials/wiki.jade
+++ b/app/partials/wiki.jade
@@ -4,25 +4,22 @@ block head
title Taiga Your agile, free, and open source project management tool
block content
- div.wrapper(tg-wiki-detail, ng-controller="WikiDetailController as ctrl",
+ div.wrapper(ng-controller="WikiDetailController 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
- .header-with-actions
+ .header
h1
- span(tg-bo-bind="project.name", class="project-name-short")
+ span(tg-bo-bind="project.name")
span.green Wiki
- span.wiki-title(tg-bo-bind='wiki.slug|unslugify')
- .action-buttons
- a.button.button-red.delete-wiki(tg-check-permission="delete_wiki_page",
- href="", title="Delete", ng-click="ctrl.delete()") Delete
+ span.wiki-title(tg-bo-bind='wikiSlug|unslugify')
- a.button.button-green.edit-wiki(tg-check-permission="modify_wiki_page",
- href="", title="Edit", ng-click="ctrl.edit()") Edit
+ div.summary.wiki-summary(tg-wiki-summary, ng-model="wiki", ng-if="wiki.id")
+ 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")
-
- tg-attachments(ng-model="wiki", type="wiki_page")
+ a.remove(href="", ng-click="ctrl.delete()", ng-if="wiki.id", title="Remove this wiki page")
+ span.icon.icon-delete
+ span Remove this wiki page
diff --git a/app/styles/components/markitup.scss b/app/styles/components/markitup.scss
new file mode 100644
index 00000000..eb70f415
--- /dev/null
+++ b/app/styles/components/markitup.scss
@@ -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;
+ }
+}
diff --git a/app/styles/components/tag.scss b/app/styles/components/tag.scss
index 3797a2e7..41ea7899 100644
--- a/app/styles/components/tag.scss
+++ b/app/styles/components/tag.scss
@@ -30,10 +30,8 @@
.tags-block {
.tags-container {
display: inline-block;
- vertical-align: middle;
}
input {
- display: inline-block;
padding: .4rem;
width: 14rem;
}
@@ -42,7 +40,18 @@
margin: 0 .5rem .5rem 0;
padding: .5rem;
}
- .save {
- display: none;
+ .add-tag {
+ color: $gray-light;
+ &:hover {
+ color: $fresh-taiga;
+ }
+ }
+ .icon-plus {
+ @extend %large;
+ }
+ .add-tag-text {
+ @extend %small;
}
}
+
+
diff --git a/app/styles/components/watchers.scss b/app/styles/components/watchers.scss
index 63afee95..303fa7f9 100644
--- a/app/styles/components/watchers.scss
+++ b/app/styles/components/watchers.scss
@@ -1,5 +1,5 @@
.watchers {
- margin-top: 2rem;
+ margin-top: 1rem;
.watchers-header {
border-bottom: 2px solid $gray-light;
padding: .5rem;
diff --git a/app/styles/layout/elements.scss b/app/styles/layout/elements.scss
index 44f9c1da..c545653b 100644
--- a/app/styles/layout/elements.scss
+++ b/app/styles/layout/elements.scss
@@ -39,6 +39,10 @@ sup {
vertical-align: middle;
}
+.icon-spinner {
+ @include animation (spin 1s linear infinite);
+}
+
.clickable {
cursor: pointer;
}
diff --git a/app/styles/layout/us-detail.scss b/app/styles/layout/us-detail.scss
index a9c813d6..dd2c489e 100644
--- a/app/styles/layout/us-detail.scss
+++ b/app/styles/layout/us-detail.scss
@@ -57,6 +57,12 @@
display: flex;
margin-bottom: 0;
max-width: 94%;
+ &:hover {
+ .icon-edit {
+ @include transition(opacity .3s linear);
+ opacity: 1;
+ }
+ }
}
.us-number {
@extend %xlarge;
@@ -72,6 +78,17 @@
display: inline-block;
line-height: 2.2rem;
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 {
@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 {
background: $white;
height: 10rem;
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 {
@@ -230,23 +309,25 @@
}
}
-.issue-data {
+.duty-data-container {
@extend %small;
- div {
- @include clearfix();
- @include transition(background .2s ease-in);
- background: darken($whitish, 5%);
+ margin-bottom: 1rem;
+ .duty-data {
margin-bottom: .5rem;
- padding: .5rem;
- padding-right: 1rem;
&:last-child {
margin: 0;
}
- }
- .clickable {
- &:hover {
+ div {
@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 {
@@ -257,6 +338,10 @@
.level-name {
color: darken($whitish, 20%);
float: right;
+ &.loading span {
+ @include animation (loading .5s linear);
+ @include animation (spin 1s linear infinite);
+ }
}
}
@@ -271,9 +356,25 @@
}
.button-gray {
background: $gray-light;
- &:hover,
+ &:hover {
+ background: $gray-light;
+ }
+ &.editable {
+ &:hover {
+ background: $grayer;
+ cursor: pointer;
+ }
+ }
&.active {
- background: $grayer;
+ background: $green-taiga;
+ }
+ }
+ .item-block {
+ &.editable {
+ &:hover {
+ background: $red;
+ cursor: pointer;
+ }
}
}
.button-red {
@@ -283,7 +384,9 @@
}
}
label {
- cursor: pointer;
+ &.editable {
+ cursor: pointer;
+ }
+input {
display: none;
}
diff --git a/app/styles/layout/wiki.scss b/app/styles/layout/wiki.scss
index d8cfd8fb..4c0eae1e 100644
--- a/app/styles/layout/wiki.scss
+++ b/app/styles/layout/wiki.scss
@@ -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 {
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;
+ }
+ }
}
diff --git a/app/styles/modules/common/assigned-to.scss b/app/styles/modules/common/assigned-to.scss
index 4b7be68b..9fc78665 100644
--- a/app/styles/modules/common/assigned-to.scss
+++ b/app/styles/modules/common/assigned-to.scss
@@ -1,7 +1,26 @@
-.us-assigned-to {
+.duty-assigned-to {
@include table-flex();
margin-top: 1rem;
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 {
@include table-flex-child(1, 0);
img {
@@ -22,13 +41,21 @@
@extend %large;
color: $green-taiga;
cursor: default;
+ line-height: 1.5rem;
&.editable {
cursor: pointer;
}
+ .icon {
+ vertical-align: top;
+ }
+ }
+ .assigned-name {
+ @include ellipsis(80%);
+ display: inline-block;
}
.icon-delete {
color: $gray-light;
- opacity: 1;
+ opacity: 0;
position: absolute;
right: 0;
top: 0;
diff --git a/app/styles/modules/common/attachments.scss b/app/styles/modules/common/attachments.scss
index b49b67e0..92050f74 100644
--- a/app/styles/modules/common/attachments.scss
+++ b/app/styles/modules/common/attachments.scss
@@ -98,7 +98,7 @@
}
.icon-edit,
.icon-floppy {
- right: 4rem;
+ right: 3.5rem;
}
.icon-delete {
right: 2rem;
diff --git a/app/styles/modules/common/history.scss b/app/styles/modules/common/history.scss
index 0e5e68d2..920d4582 100644
--- a/app/styles/modules/common/history.scss
+++ b/app/styles/modules/common/history.scss
@@ -1,6 +1,5 @@
.history {
margin-bottom: 1rem;
- padding: 0 1rem;
}
.changes-title {
display: block;
@@ -66,7 +65,22 @@
}
.add-comment {
@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 {
+ background: $white;
float: left;
height: 41px;
margin-bottom: .5rem;
@@ -79,14 +93,13 @@
.button-green {
display: none;
}
- &.active {
- .button-green {
- display: block;
- }
- textarea {
- @include transition(height .3s ease-in);
- height: 6rem;
- }
+ .edit,
+ .preview-icon {
+ position: absolute;
+ right: 1rem;
+ }
+ .preview-icon {
+ opacity: 0;
}
}
a.show-more-comments {
diff --git a/app/styles/modules/common/related-tasks.scss b/app/styles/modules/common/related-tasks.scss
index 37936812..bf4dfb8b 100644
--- a/app/styles/modules/common/related-tasks.scss
+++ b/app/styles/modules/common/related-tasks.scss
@@ -61,6 +61,17 @@
.status {
position: relative;
text-align: left;
+ &:hover {
+ .icon {
+ @include transition (opacity .2s ease-in);
+ opacity: 1;
+ }
+ }
+ .not-clickable {
+ &:hover {
+ color: $grayer;
+ }
+ }
.popover {
a {
text-align: left;
@@ -73,6 +84,7 @@
.icon {
color: $gray-light;
margin-left: .2rem;
+ opacity: 0;
}
}
.pop-status {
@@ -160,8 +172,10 @@
text-align: left;
}
.task-assignedto {
- cursor: pointer;
position: relative;
+ &.editable {
+ cursor: pointer;
+ }
&:hover {
.icon {
@include transition(opacity .3s linear);
diff --git a/main-sass.js b/main-sass.js
index 5f77ff33..6c1b7bdb 100644
--- a/main-sass.js
+++ b/main-sass.js
@@ -53,6 +53,8 @@ exports.files = function () {
'components/spinner',
'components/help-notion-button',
'components/beta',
+ 'components/markitup',
+
//#################################################
// Modules