diff --git a/app/coffee/modules/issues/detail.coffee b/app/coffee/modules/issues/detail.coffee
index ec70b2c5..9dc08296 100644
--- a/app/coffee/modules/issues/detail.coffee
+++ b/app/coffee/modules/issues/detail.coffee
@@ -26,6 +26,7 @@ toString = @.taiga.toString
joinStr = @.taiga.joinStr
groupBy = @.taiga.groupBy
bindOnce = @.taiga.bindOnce
+bindMethods = @.taiga.bindMethods
module = angular.module("taigaIssues")
@@ -52,6 +53,8 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
@log, @appMetaService, @analytics, @navUrls, @translate) ->
+ bindMethods(@)
+
@scope.issueRef = @params.issueref
@scope.sectionName = @translate.instant("ISSUES.SECTION_NAME")
@.initializeEventHandlers()
@@ -144,6 +147,49 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
@.fillUsersAndRoles(project.members, project.roles)
@.loadIssue()
+ ###
+ # Note: This methods (onUpvote() and onDownvote()) are related to tg-vote-button.
+ # See app/modules/components/vote-button for more info
+ ###
+ onUpvote: ->
+ onSuccess = =>
+ @.loadIssue()
+ @rootscope.$broadcast("object:updated")
+ onError = =>
+ @confirm.notify("error")
+
+ return @rs.issues.upvote(@scope.issueId).then(onSuccess, onError)
+
+ onDownvote: ->
+ onSuccess = =>
+ @.loadIssue()
+ @rootscope.$broadcast("object:updated")
+ onError = =>
+ @confirm.notify("error")
+
+ return @rs.issues.downvote(@scope.issueId).then(onSuccess, onError)
+
+ ###
+ # Note: This methods (onWatch() and onUnwatch()) are related to tg-watch-button.
+ # See app/modules/components/watch-button for more info
+ ###
+ onWatch: ->
+ onSuccess = =>
+ @.loadIssue()
+ @rootscope.$broadcast("object:updated")
+ onError = =>
+ @confirm.notify("error")
+
+ return @rs.issues.watch(@scope.issueId).then(onSuccess, onError)
+
+ onUnwatch: ->
+ onSuccess = =>
+ @.loadIssue()
+ @rootscope.$broadcast("object:updated")
+ onError = =>
+ @confirm.notify("error")
+
+ return @rs.issues.unwatch(@scope.issueId).then(onSuccess, onError)
module.controller("IssueDetailController", IssueDetailController)
diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee
index f17841e1..73c75922 100644
--- a/app/coffee/modules/resources.coffee
+++ b/app/coffee/modules/resources.coffee
@@ -37,8 +37,11 @@ urls = {
"users-change-password": "/users/change_password"
"users-change-email": "/users/change_email"
"users-cancel-account": "/users/cancel"
- "contacts": "/users/%s/contacts"
- "stats": "/users/%s/stats"
+ "user-stats": "/users/%s/stats"
+ "user-liked": "/users/%s/liked"
+ "user-voted": "/users/%s/voted"
+ "user-watched": "/users/%s/watched"
+ "user-contacts": "/users/%s/contacts"
# User - Notification
"permissions": "/permissions"
@@ -63,6 +66,10 @@ urls = {
"project-templates": "/project-templates"
"project-modules": "/projects/%s/modules"
"bulk-update-projects-order": "/projects/bulk_update_order"
+ "project-like": "/projects/%s/like"
+ "project-unlike": "/projects/%s/unlike"
+ "project-watch": "/projects/%s/watch"
+ "project-unwatch": "/projects/%s/unwatch"
# Project Values - Choises
"userstory-statuses": "/userstory-statuses"
@@ -83,16 +90,28 @@ urls = {
"bulk-update-us-sprint-order": "/userstories/bulk_update_sprint_order"
"bulk-update-us-kanban-order": "/userstories/bulk_update_kanban_order"
"userstories-filters": "/userstories/filters_data"
+ "userstory-upvote": "/userstories/%s/upvote"
+ "userstory-downvote": "/userstories/%s/downvote"
+ "userstory-watch": "/userstories/%s/watch"
+ "userstory-unwatch": "/userstories/%s/unwatch"
# Tasks
"tasks": "/tasks"
"bulk-create-tasks": "/tasks/bulk_create"
"bulk-update-task-taskboard-order": "/tasks/bulk_update_taskboard_order"
+ "task-upvote": "/tasks/%s/upvote"
+ "task-downvote": "/tasks/%s/downvote"
+ "task-watch": "/tasks/%s/watch"
+ "task-unwatch": "/tasks/%s/unwatch"
# Issues
"issues": "/issues"
"bulk-create-issues": "/issues/bulk_create"
"issues-filters": "/issues/filters_data"
+ "issue-upvote": "/issues/%s/upvote"
+ "issue-downvote": "/issues/%s/downvote"
+ "issue-watch": "/issues/%s/watch"
+ "issue-unwatch": "/issues/%s/unwatch"
# Wiki pages
"wiki": "/wiki"
diff --git a/app/coffee/modules/resources/issues.coffee b/app/coffee/modules/resources/issues.coffee
index 22cb0f05..7ca1ef15 100644
--- a/app/coffee/modules/resources/issues.coffee
+++ b/app/coffee/modules/resources/issues.coffee
@@ -55,6 +55,22 @@ resourceProvider = ($repo, $http, $urls, $storage, $q) ->
params = {project_id: projectId, bulk_issues: data}
return $http.post(url, params)
+ service.upvote = (issueId) ->
+ url = $urls.resolve("issue-upvote", issueId)
+ return $http.post(url)
+
+ service.downvote = (issueId) ->
+ url = $urls.resolve("issue-downvote", issueId)
+ return $http.post(url)
+
+ service.watch = (issueId) ->
+ url = $urls.resolve("issue-watch", issueId)
+ return $http.post(url)
+
+ service.unwatch = (issueId) ->
+ url = $urls.resolve("issue-unwatch", issueId)
+ return $http.post(url)
+
service.stats = (projectId) ->
return $repo.queryOneRaw("projects", "#{projectId}/issues_stats")
diff --git a/app/coffee/modules/resources/tasks.coffee b/app/coffee/modules/resources/tasks.coffee
index 7069012c..caab72f9 100644
--- a/app/coffee/modules/resources/tasks.coffee
+++ b/app/coffee/modules/resources/tasks.coffee
@@ -57,6 +57,22 @@ resourceProvider = ($repo, $http, $urls, $storage) ->
return $http.post(url, params).then (result) ->
return result.data
+ service.upvote = (taskId) ->
+ url = $urls.resolve("task-upvote", taskId)
+ return $http.post(url)
+
+ service.downvote = (taskId) ->
+ url = $urls.resolve("task-downvote", taskId)
+ return $http.post(url)
+
+ service.watch = (taskId) ->
+ url = $urls.resolve("task-watch", taskId)
+ return $http.post(url)
+
+ service.unwatch = (taskId) ->
+ url = $urls.resolve("task-unwatch", taskId)
+ return $http.post(url)
+
service.bulkUpdateTaskTaskboardOrder = (projectId, data) ->
url = $urls.resolve("bulk-update-task-taskboard-order")
params = {project_id: projectId, bulk_tasks: data}
diff --git a/app/coffee/modules/resources/userstories.coffee b/app/coffee/modules/resources/userstories.coffee
index 0d0fbd3d..ce7c139c 100644
--- a/app/coffee/modules/resources/userstories.coffee
+++ b/app/coffee/modules/resources/userstories.coffee
@@ -67,6 +67,22 @@ resourceProvider = ($repo, $http, $urls, $storage) ->
return $http.post(url, data)
+ service.upvote = (userStoryId) ->
+ url = $urls.resolve("userstory-upvote", userStoryId)
+ return $http.post(url)
+
+ service.downvote = (userStoryId) ->
+ url = $urls.resolve("userstory-downvote", userStoryId)
+ return $http.post(url)
+
+ service.watch = (userStoryId) ->
+ url = $urls.resolve("userstory-watch", userStoryId)
+ return $http.post(url)
+
+ service.unwatch = (userStoryId) ->
+ url = $urls.resolve("userstory-unwatch", userStoryId)
+ return $http.post(url)
+
service.bulkUpdateBacklogOrder = (projectId, data) ->
url = $urls.resolve("bulk-update-us-backlog-order")
params = {project_id: projectId, bulk_stories: data}
diff --git a/app/coffee/modules/tasks/detail.coffee b/app/coffee/modules/tasks/detail.coffee
index 35b31a59..653e9f6b 100644
--- a/app/coffee/modules/tasks/detail.coffee
+++ b/app/coffee/modules/tasks/detail.coffee
@@ -23,6 +23,7 @@ taiga = @.taiga
mixOf = @.taiga.mixOf
groupBy = @.taiga.groupBy
+bindMethods = @.taiga.bindMethods
module = angular.module("taigaTasks")
@@ -50,6 +51,8 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
@log, @appMetaService, @navUrls, @analytics, @translate) ->
+ bindMethods(@)
+
@scope.taskRef = @params.taskref
@scope.sectionName = @translate.instant("TASK.SECTION_NAME")
@.initializeEventHandlers()
@@ -145,6 +148,50 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
@.fillUsersAndRoles(project.members, project.roles)
@.loadTask().then(=> @q.all([@.loadSprint(), @.loadUserStory()]))
+ ###
+ # Note: This methods (onUpvote() and onDownvote()) are related to tg-vote-button.
+ # See app/modules/components/vote-button for more info
+ ###
+ onUpvote: ->
+ onSuccess = =>
+ @.loadTask()
+ @rootscope.$broadcast("object:updated")
+ onError = =>
+ @confirm.notify("error")
+
+ return @rs.tasks.upvote(@scope.taskId).then(onSuccess, onError)
+
+ onDownvote: ->
+ onSuccess = =>
+ @.loadTask()
+ @rootscope.$broadcast("object:updated")
+ onError = =>
+ @confirm.notify("error")
+
+ return @rs.tasks.downvote(@scope.taskId).then(onSuccess, onError)
+
+ ###
+ # Note: This methods (onWatch() and onUnwatch()) are related to tg-watch-button.
+ # See app/modules/components/watch-button for more info
+ ###
+ onWatch: ->
+ onSuccess = =>
+ @.loadTask()
+ @rootscope.$broadcast("object:updated")
+ onError = =>
+ @confirm.notify("error")
+
+ return @rs.tasks.watch(@scope.taskId).then(onSuccess, onError)
+
+ onUnwatch: ->
+ onSuccess = =>
+ @.loadTask()
+ @rootscope.$broadcast("object:updated")
+ onError = =>
+ @confirm.notify("error")
+
+ return @rs.tasks.unwatch(@scope.taskId).then(onSuccess, onError)
+
module.controller("TaskDetailController", TaskDetailController)
@@ -195,7 +242,7 @@ module.directive("tgTaskStatusDisplay", ["$tgTemplate", "$compile", TaskStatusDi
## Task status button directive
#############################################################################
-TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $compile, $translate) ->
+TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $compile, $translate, $template) ->
# Display the status of Task and you can edit it.
#
# Example:
@@ -206,21 +253,7 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $co
# - scope.statusById object
# - $scope.project.my_permissions
- template = _.template("""
-
-
-
<%- status.name %>
- <% if(editable){ %>
<% }%>
-
-
-
- <% _.each(statuses, function(st) { %>
- - <%- st.name %>
- <% }); %>
-
-
- """)
+ template = $template.get("us/us-status-button.html", true)
link = ($scope, $el, $attrs, $model) ->
isEditable = ->
@@ -288,7 +321,7 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $co
}
module.directive("tgTaskStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue",
- "$compile", "$translate", TaskStatusButtonDirective])
+ "$compile", "$translate", "$tgTemplate", TaskStatusButtonDirective])
TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue, $compile) ->
diff --git a/app/coffee/modules/userstories/detail.coffee b/app/coffee/modules/userstories/detail.coffee
index d819a947..1ee45eca 100644
--- a/app/coffee/modules/userstories/detail.coffee
+++ b/app/coffee/modules/userstories/detail.coffee
@@ -24,6 +24,7 @@ taiga = @.taiga
mixOf = @.taiga.mixOf
groupBy = @.taiga.groupBy
bindOnce = @.taiga.bindOnce
+bindMethods = @.taiga.bindMethods
module = angular.module("taigaUserStories")
@@ -50,6 +51,8 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
@log, @appMetaService, @navUrls, @analytics, @translate) ->
+ bindMethods(@)
+
@scope.usRef = @params.usref
@scope.sectionName = @translate.instant("US.SECTION_NAME")
@.initializeEventHandlers()
@@ -182,6 +185,50 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
@.fillUsersAndRoles(project.members, project.roles)
@.loadUs().then(=> @q.all([@.loadSprint(), @.loadTasks()]))
+ ###
+ # Note: This methods (onUpvote() and onDownvote()) are related to tg-vote-button.
+ # See app/modules/components/vote-button for more info
+ ###
+ onUpvote: ->
+ onSuccess = =>
+ @.loadUs()
+ @rootscope.$broadcast("object:updated")
+ onError = =>
+ @confirm.notify("error")
+
+ return @rs.userstories.upvote(@scope.usId).then(onSuccess, onError)
+
+ onDownvote: ->
+ onSuccess = =>
+ @.loadUs()
+ @rootscope.$broadcast("object:updated")
+ onError = =>
+ @confirm.notify("error")
+
+ return @rs.userstories.downvote(@scope.usId).then(onSuccess, onError)
+
+ ###
+ # Note: This methods (onWatch() and onUnwatch()) are related to tg-watch-button.
+ # See app/modules/components/watch-button for more info
+ ###
+ onWatch: ->
+ onSuccess = =>
+ @.loadUs()
+ @rootscope.$broadcast("object:updated")
+ onError = =>
+ @confirm.notify("error")
+
+ return @rs.userstories.watch(@scope.usId).then(onSuccess, onError)
+
+ onUnwatch: ->
+ onSuccess = =>
+ @.loadUs()
+ @rootscope.$broadcast("object:updated")
+ onError = =>
+ @confirm.notify("error")
+
+ return @rs.userstories.unwatch(@scope.usId).then(onSuccess, onError)
+
module.controller("UserStoryDetailController", UserStoryDetailController)
diff --git a/app/modules/components/vote-button/vote-button.controller.coffee b/app/modules/components/vote-button/vote-button.controller.coffee
new file mode 100644
index 00000000..cc7ce7f9
--- /dev/null
+++ b/app/modules/components/vote-button/vote-button.controller.coffee
@@ -0,0 +1,36 @@
+class VoteButtonController
+ @.$inject = [
+ "tgCurrentUserService",
+ ]
+
+ constructor: (@currentUserService) ->
+ @.user = @currentUserService.getUser()
+ @.isMouseOver = false
+ @.loading = false
+
+ showTextWhenMouseIsOver: ->
+ @.isMouseOver = true
+
+ showTextWhenMouseIsLeave: ->
+ @.isMouseOver = false
+
+ toggleVote: ->
+ @.loading = true
+
+ if not @.item.is_voter
+ promise = @._upvote()
+ else
+ promise = @._downvote()
+
+ promise.finally () => @.loading = false
+
+ return promise
+
+ _upvote: ->
+ @.onUpvote().then =>
+ @.showTextWhenMouseIsLeave()
+
+ _downvote: ->
+ @.onDownvote()
+
+angular.module("taigaComponents").controller("VoteButton", VoteButtonController)
diff --git a/app/modules/components/vote-button/vote-button.controller.spec.coffee b/app/modules/components/vote-button/vote-button.controller.spec.coffee
new file mode 100644
index 00000000..1d3a41e1
--- /dev/null
+++ b/app/modules/components/vote-button/vote-button.controller.spec.coffee
@@ -0,0 +1,82 @@
+describe "VoteButton", ->
+ provide = null
+ $controller = null
+ $rootScope = null
+ mocks = {}
+
+ _mockCurrentUser = () ->
+ mocks.currentUser = {
+ getUser: sinon.stub()
+ }
+
+ provide.value "tgCurrentUserService", mocks.currentUser
+
+ _mocks = ->
+ mocks = {
+ onUpvote: sinon.stub(),
+ onDownvote: sinon.stub()
+ }
+
+ module ($provide) ->
+ provide = $provide
+ _mockCurrentUser()
+ return null
+
+ _inject = (callback) ->
+ inject (_$controller_, _$rootScope_) ->
+ $rootScope = _$rootScope_
+ $controller = _$controller_
+
+ _setup = ->
+ _mocks()
+ _inject()
+
+ beforeEach ->
+ module "taigaComponents"
+ _setup()
+
+ it "upvote", (done) ->
+ $scope = $rootScope.$new()
+
+ mocks.onUpvote = sinon.stub().promise()
+
+ ctrl = $controller("VoteButton", $scope, {
+ item: {is_voter: false}
+ onUpvote: mocks.onUpvote
+ onDownvote: mocks.onDownvote
+ })
+
+ promise = ctrl.toggleVote()
+
+ expect(ctrl.loading).to.be.true;
+
+ mocks.onUpvote.resolve()
+
+ promise.finally () ->
+ expect(mocks.onUpvote).to.be.calledOnce
+ expect(ctrl.loading).to.be.false;
+
+ done()
+
+ it "downvote", (done) ->
+ $scope = $rootScope.$new()
+
+ mocks.onDownvote = sinon.stub().promise()
+
+ ctrl = $controller("VoteButton", $scope, {
+ item: {is_voter: true}
+ onUpvote: mocks.onUpvote
+ onDownvote: mocks.onDownvote
+ })
+
+ promise = ctrl.toggleVote()
+
+ expect(ctrl.loading).to.be.true;
+
+ mocks.onDownvote.resolve()
+
+ promise.finally () ->
+ expect(mocks.onDownvote).to.be.calledOnce
+ expect(ctrl.loading).to.be.false;
+
+ done()
diff --git a/app/modules/components/vote-button/vote-button.directive.coffee b/app/modules/components/vote-button/vote-button.directive.coffee
new file mode 100644
index 00000000..19d7ebce
--- /dev/null
+++ b/app/modules/components/vote-button/vote-button.directive.coffee
@@ -0,0 +1,14 @@
+VoteButtonDirective = ->
+ return {
+ scope: {}
+ controller: "VoteButton",
+ bindToController: {
+ item: "=",
+ onUpvote: "=",
+ onDownvote: "="
+ }
+ controllerAs: "vm",
+ templateUrl: "components/vote-button/vote-button.html",
+ }
+
+angular.module("taigaComponents").directive("tgVoteButton", VoteButtonDirective)
diff --git a/app/modules/components/vote-button/vote-button.jade b/app/modules/components/vote-button/vote-button.jade
new file mode 100644
index 00000000..30b89e74
--- /dev/null
+++ b/app/modules/components/vote-button/vote-button.jade
@@ -0,0 +1,15 @@
+a(
+ href=""
+ title="{{ 'COMMON.VOTE_BUTTON.BUTTON_TITLE' | translate }}"
+ ng-if="::vm.user"
+ ng-click="vm.toggleVote()"
+ ng-class="{'active': vm.item.is_voter, 'is-hover': vm.item.is_voter && vm.isMouseOver, 'disable': !vm.user}"
+ ng-mouseover="vm.showTextWhenMouseIsOver()"
+ ng-mouseleave="vm.showTextWhenMouseIsLeave()"
+)
+ span.track-icon
+ include ../../../svg/upvote.svg
+ span.track-button-counter(
+ title="{{ 'COMMON.VOTE_BUTTON.COUNTER_TITLE'|translate:{total:vm.item.total_voters||0}:'messageformat' }}",
+ tg-loading="vm.loading"
+ ) {{ vm.item.total_voters }}
diff --git a/app/modules/components/watch-button/watch-button.controller.coffee b/app/modules/components/watch-button/watch-button.controller.coffee
new file mode 100644
index 00000000..c9f94b7a
--- /dev/null
+++ b/app/modules/components/watch-button/watch-button.controller.coffee
@@ -0,0 +1,36 @@
+class WatchButtonController
+ @.$inject = [
+ "tgCurrentUserService",
+ ]
+
+ constructor: (@currentUserService) ->
+ @.user = @currentUserService.getUser()
+ @.isMouseOver = false
+ @.loading = false
+
+ showTextWhenMouseIsOver: ->
+ @.isMouseOver = true
+
+ showTextWhenMouseIsLeave: ->
+ @.isMouseOver = false
+
+ toggleWatch: ->
+ @.loading = true
+
+ if not @.item.is_watcher
+ promise = @._watch()
+ else
+ promise = @._unwatch()
+
+ promise.finally () => @.loading = false
+
+ return promise
+
+ _watch: ->
+ @.onWatch().then =>
+ @.showTextWhenMouseIsLeave()
+
+ _unwatch: ->
+ @.onUnwatch()
+
+angular.module("taigaComponents").controller("WatchButton", WatchButtonController)
diff --git a/app/modules/components/watch-button/watch-button.controller.spec.coffee b/app/modules/components/watch-button/watch-button.controller.spec.coffee
new file mode 100644
index 00000000..6d8bf0ff
--- /dev/null
+++ b/app/modules/components/watch-button/watch-button.controller.spec.coffee
@@ -0,0 +1,83 @@
+describe "WatchButton", ->
+ provide = null
+ $controller = null
+ $rootScope = null
+ mocks = {}
+
+ _mockCurrentUser = () ->
+ mocks.currentUser = {
+ getUser: sinon.stub()
+ }
+
+ provide.value "tgCurrentUserService", mocks.currentUser
+
+ _mocks = ->
+ mocks = {
+ onWatch: sinon.stub(),
+ onUnwatch: sinon.stub()
+ }
+
+ module ($provide) ->
+ provide = $provide
+ _mockCurrentUser()
+ return null
+
+ _inject = (callback) ->
+ inject (_$controller_, _$rootScope_) ->
+ $rootScope = _$rootScope_
+ $controller = _$controller_
+
+ _setup = ->
+ _mocks()
+ _inject()
+
+ beforeEach ->
+ module "taigaComponents"
+ _setup()
+
+ it "watch", (done) ->
+ $scope = $rootScope.$new()
+
+ mocks.onWatch = sinon.stub().promise()
+
+ ctrl = $controller("WatchButton", $scope, {
+ item: {is_watcher: false}
+ onWatch: mocks.onWatch
+ onUnwatch: mocks.onUnwatch
+ })
+
+
+ promise = ctrl.toggleWatch()
+
+ expect(ctrl.loading).to.be.true;
+
+ mocks.onWatch.resolve()
+
+ promise.finally () ->
+ expect(mocks.onWatch).to.be.calledOnce
+ expect(ctrl.loading).to.be.false;
+
+ done()
+
+ it "unwatch", (done) ->
+ $scope = $rootScope.$new()
+
+ mocks.onUnwatch = sinon.stub().promise()
+
+ ctrl = $controller("WatchButton", $scope, {
+ item: {is_watcher: true}
+ onWatch: mocks.onWatch
+ onUnwatch: mocks.onUnwatch
+ })
+
+ promise = ctrl.toggleWatch()
+
+ expect(ctrl.loading).to.be.true;
+
+ mocks.onUnwatch.resolve()
+
+ promise.finally () ->
+ expect(mocks.onUnwatch).to.be.calledOnce
+ expect(ctrl.loading).to.be.false;
+
+ done()
diff --git a/app/modules/components/watch-button/watch-button.directive.coffee b/app/modules/components/watch-button/watch-button.directive.coffee
new file mode 100644
index 00000000..ade7526a
--- /dev/null
+++ b/app/modules/components/watch-button/watch-button.directive.coffee
@@ -0,0 +1,14 @@
+WatchButtonDirective = ->
+ return {
+ scope: {}
+ controller: "WatchButton",
+ bindToController: {
+ item: "=",
+ onWatch: "=",
+ onUnwatch: "="
+ }
+ controllerAs: "vm",
+ templateUrl: "components/watch-button/watch-button.html",
+ }
+
+angular.module("taigaComponents").directive("tgWatchButton", WatchButtonDirective)
diff --git a/app/modules/components/watch-button/watch-button.jade b/app/modules/components/watch-button/watch-button.jade
new file mode 100644
index 00000000..2d25bbde
--- /dev/null
+++ b/app/modules/components/watch-button/watch-button.jade
@@ -0,0 +1,44 @@
+mixin counter
+ span.track-button-counter(
+ title="{{ 'COMMON.WATCH_BUTTON.COUNTER_TITLE'|translate:{total:vm.item.watchers.length||0}:'messageformat' }}",
+ tg-loading="vm.loading"
+ )
+ | {{ vm.item.watchers.length }}
+
+
+//- Registered user button
+a.track-button.watch-button.watch-container(
+ href=""
+ title="{{ 'COMMON.WATCH_BUTTON.BUTTON_TITLE' | translate }}"
+ ng-if="::vm.user"
+ ng-click="vm.toggleWatch()"
+ ng-class="{'active': vm.item.is_watcher, 'is-hover': vm.item.is_watcher && vm.isMouseOver}"
+ ng-mouseover="vm.showTextWhenMouseIsOver()"
+ ng-mouseleave="vm.showTextWhenMouseIsLeave()"
+)
+ span.track-inner
+ span.track-icon
+ include ../../../svg/watch.svg
+ span(
+ ng-if="!vm.item.is_watcher",
+ translate="COMMON.WATCH_BUTTON.WATCH"
+ )
+ span(
+ ng-if="vm.item.is_watcher && !vm.isMouseOver",
+ translate="COMMON.WATCH_BUTTON.WATCHING"
+ )
+ span(
+ ng-if="vm.item.is_watcher && vm.isMouseOver",
+ translate="COMMON.WATCH_BUTTON.UNWATCH"
+ )
+ +counter
+
+//- Anonymous user button
+span.track-button.watch-button.watch-container(
+ ng-if="::!vm.user"
+)
+ span.track-inner
+ span.track-icon
+ include ../../../svg/watch.svg
+ span(translate="COMMON.WATCH_BUTTON.WATCHERS")
+ +counter
diff --git a/app/partials/issue/issues-detail.jade b/app/partials/issue/issues-detail.jade
index c4f1d6d5..397bb6fc 100644
--- a/app/partials/issue/issues-detail.jade
+++ b/app/partials/issue/issues-detail.jade
@@ -1,74 +1,115 @@
doctype html
-div.wrapper(ng-controller="IssueDetailController as ctrl",
- ng-init="section='issues'")
+div.wrapper(
+ ng-controller="IssueDetailController as ctrl",
+ ng-init="section='issues'"
+)
tg-project-menu
+
div.main.us-detail
div.us-detail-header.header-with-actions
include ../includes/components/mainTitle
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(tg-editable-subject, ng-model="issue", required-perm="modify_issue")
+ header
+ tg-vote-button.upvote-btn(
+ item="issue"
+ on-upvote="ctrl.onUpvote"
+ on-downvote="ctrl.onDownvote"
+ )
+ .us-title(ng-class="{blocked: issue.is_blocked}")
+ h2.us-title-text
+ span.us-number(tg-bo-ref="issue.ref")
+ span.us-name(tg-editable-subject, ng-model="issue", required-perm="modify_issue")
- p.us-related-task(ng-if="issue.generated_user_stories.length")
- | {{ 'ISSUES.PROMOTED'|translate }}
- a(ng-repeat="us in issue.generated_user_stories",
- tg-check-permission="view_us", href="",
- tg-bo-title="'#' + us.ref + ' ' + us.subject",
- tg-nav="project-userstories-detail:project=project.slug,ref=us.ref")
- span(tg-bo-ref="us.ref")
+ p.us-related-task(ng-if="issue.generated_user_stories.length")
+ | {{ 'ISSUES.PROMOTED'|translate }}
+ a(
+ href=""
+ ng-repeat="us in issue.generated_user_stories"
+ tg-check-permission="view_us"
+ tg-bo-title="'#' + us.ref + ' ' + us.subject"
+ tg-nav="project-userstories-detail:project=project.slug,ref=us.ref"
+ )
+ span(tg-bo-ref="us.ref")
- p.external-reference(ng-if="issue.external_reference")
- | {{ 'ISSUES.EXTERNAL_REFERENCE'|translate }}
- a(target="_blank", tg-bo-href="issue.external_reference[1]",
- title="{{'ISSUES.GO_TO_EXTERNAL_REFERENCE' | translate}}")
- span {{ issue.external_reference[1] }}
+ p.external-reference(ng-if="issue.external_reference")
+ | {{ 'ISSUES.EXTERNAL_REFERENCE'|translate }}
+ a(
+ target="_blank"
+ tg-bo-href="issue.external_reference[1]"
+ title="{{'ISSUES.GO_TO_EXTERNAL_REFERENCE' | translate}}"
+ )
+ span {{ issue.external_reference[1] }}
- p.block-desc-container(ng-show="issue.is_blocked")
- span.block-description-title(translate="COMMON.BLOCKED")
- span.block-description(ng-bind="issue.blocked_note || ('ISSUES.BLOCKED' | translate)")
+ p.block-desc-container(ng-show="issue.is_blocked")
+ span.block-description-title(translate="COMMON.BLOCKED")
+ span.block-description(ng-bind="issue.blocked_note || ('ISSUES.BLOCKED' | translate)")
- div.issue-nav
- a.icon.icon-arrow-left(ng-show="previousUrl", tg-bo-href="previousUrl",
- title="{{'ISSUES.TITLE_PREVIOUS_ISSUE' | translate}}")
- a.icon.icon-arrow-right(ng-show="nextUrl", tg-bo-href="nextUrl",
- title="{{'ISSUES.TITLE_NEXT_ISSUE' | translate}}")
+ div.issue-nav
+ a.icon.icon-arrow-left(
+ ng-show="previousUrl"
+ tg-bo-href="previousUrl"
+ title="{{'ISSUES.TITLE_PREVIOUS_ISSUE' | translate}}"
+ )
+ a.icon.icon-arrow-right(
+ ng-show="nextUrl"
+ tg-bo-href="nextUrl"
+ title="{{'ISSUES.TITLE_NEXT_ISSUE' | translate}}"
+ )
div.tags-block(tg-tag-line, ng-model="issue", required-perm="modify_issue")
section.duty-content(tg-editable-description, ng-model="issue", required-perm="modify_issue")
// Custom Fields
- tg-custom-attributes-values(ng-model="issue", type="issue", project="project", required-edition-perm="modify_issue")
+ tg-custom-attributes-values(
+ ng-model="issue"
+ type="issue"
+ project="project"
+ required-edition-perm="modify_issue"
+ )
tg-attachments(ng-model="issue", type="issue")
tg-history(ng-model="issue", type="issue")
- sidebar.menu-secondary.sidebar
- 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")
+ sidebar.menu-secondary.sidebar.ticket-data
+ section.status
+ .ticket-title(tg-issue-status-display, ng-model="issue")
+ tg-created-by-display.ticket-created-by(ng-model="issue")
+ div.ticket-data-container
+ div.ticket-status(tg-issue-type-button, ng-model="issue")
+ div.ticket-status(tg-issue-severity-button, ng-model="issue")
+ div.ticket-status(tg-issue-priority-button, ng-model="issue")
+ div.ticket-status(tg-issue-status-button, ng-model="issue")
- section.duty-assigned-to(tg-assigned-to, ng-model="issue", required-perm="modify_issue")
+ section.ticket-assigned-to(tg-assigned-to, ng-model="issue", required-perm="modify_issue")
- section.watchers(tg-watchers, ng-model="issue", required-perm="modify_issue")
+ section.track-buttons-container.ticket-track-buttons
- section.us-detail-settings
- 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="{{'ISSUES.ACTION_DELETE' | translate}}",
- on-delete-go-to-url="onDeleteGoToUrl",
- ng-model="issue")
+ div.watch-button
+ tg-watch-button(
+ item="issue"
+ on-watch="ctrl.onWatch"
+ on-unwatch="ctrl.onUnwatch"
+ )
- div.lightbox.lightbox-block(tg-lb-block, title="ISSUES.LIGHTBOX_TITLE_BLOKING_ISSUE", ng-model="issue")
+ div.ticket-watchers(
+ tg-watchers
+ ng-model="issue"
+ required-perm="modify_issue"
+ )
+
+ section.ticket-detail-settings
+ 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="{{'ISSUES.ACTION_DELETE' | translate}}",
+ on-delete-go-to-url="onDeleteGoToUrl",
+ ng-model="issue"
+ )
+
+ div.lightbox.lightbox-block(tg-lb-block, ng-model="issue", title="ISSUES.LIGHTBOX_TITLE_BLOKING_ISSUE")
div.lightbox.lightbox-select-user(tg-lb-assignedto)
div.lightbox.lightbox-select-user(tg-lb-watchers)
diff --git a/app/partials/task/task-detail.jade b/app/partials/task/task-detail.jade
index e24dd306..1f732574 100644
--- a/app/partials/task/task-detail.jade
+++ b/app/partials/task/task-detail.jade
@@ -1,76 +1,130 @@
doctype html
-div.wrapper(ng-controller="TaskDetailController as ctrl",
- ng-init="section='backlog-kanban'")
+div.wrapper(
+ ng-controller="TaskDetailController as ctrl"
+ ng-init="section='backlog-kanban'"
+)
tg-project-menu
+
div.main.us-detail
div.us-detail-header.header-with-actions
include ../includes/components/mainTitle
.action-buttons
a.button-gray(
- tg-check-permission="view_milestones",
- href="", title="{{'TASK.TITLE_LINK_TASKBOARD' | translate}}",
- tg-nav="project-taskboard:project=project.slug,sprint=sprint.slug",
- ng-if="sprint && project.is_backlog_activated", translate="TASK.LINK_TASKBOARD")
+ href=""
+ title="{{'TASK.TITLE_LINK_TASKBOARD' | translate}}"
+ tg-check-permission="view_milestones"
+ tg-nav="project-taskboard:project=project.slug,sprint=sprint.slug"
+ ng-if="sprint && project.is_backlog_activated"
+ translate="TASK.LINK_TASKBOARD"
+ )
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(tg-editable-subject, ng-model="task", required-perm="modify_task")
+ header
+ tg-vote-button.upvote-btn(
+ item="task",
+ on-upvote="ctrl.onUpvote",
+ on-downvote="ctrl.onDownvote"
+ )
+ div.us-title(ng-class="{blocked: task.is_blocked}")
+ h2.us-title-text
+ span.us-number(tg-bo-ref="task.ref")
+ span.us-name(
+ tg-editable-subject
+ ng-model="task"
+ required-perm="modify_task"
+ )
- h3.us-related-task(ng-if="us")
- | {{ 'TASK.OWNER_US'|translate }}
- a(tg-check-permission="view_us", href="", title="{{'TASK.TITLE_LINK_GO_OWNER' | translate}}",
- tg-nav="project-userstories-detail:project=project.slug,ref=us.ref")
- span(tg-bo-ref="us.ref")
- span(tg-bo-bind="us.subject")
+ h3.us-related-task(ng-if="us")
+ | {{ 'TASK.OWNER_US'|translate }}
+ a(
+ href=""
+ tg-check-permission="view_us"
+ tg-nav="project-userstories-detail:project=project.slug,ref=us.ref"
+ title="{{'TASK.TITLE_LINK_GO_OWNER' | translate}}"
+ )
+ span(tg-bo-ref="us.ref")
+ span(tg-bo-bind="us.subject")
- p.external-reference(ng-if="task.external_reference")
- a(target="_blank", tg-bo-href="task.external_reference[1]",
- title="{{'TASK.TITLE_LINK_GO_ORIGIN' | translate}}")
- | {{ "TASK.ORIGIN_US"| translate }}
- span {{ task.external_reference[1] }}
+ p.external-reference(ng-if="task.external_reference")
+ a(
+ tg-bo-href="task.external_reference[1]",
+ target="_blank"
+ title="{{'TASK.TITLE_LINK_GO_ORIGIN' | translate}}"
+ )
+ | {{ "TASK.ORIGIN_US"| translate }}
+ span {{ task.external_reference[1] }}
- p.block-desc-container(ng-show="task.is_blocked")
- span.block-description-title(translate="COMMON.BLOCKED")
- span.block-description(ng-bind="task.blocked_note || ('TASK.BLOCKED_DESCRIPTION' | translate)")
+ p.block-desc-container(ng-show="task.is_blocked")
+ span.block-description-title(translate="COMMON.BLOCKED")
+ span.block-description(
+ ng-bind="task.blocked_note || ('TASK.BLOCKED_DESCRIPTION' | translate)"
+ )
- div.issue-nav
- a.icon.icon-arrow-left(ng-show="previousUrl", tg-bo-href="previousUrl",
- title="{{'TASK.PREVIOUS' | translate}}")
- a.icon.icon-arrow-right(ng-show="nextUrl", tg-bo-href="nextUrl",
- title="{{'TASK.NEXT' | translate}}")
+ div.issue-nav
+ a.icon.icon-arrow-left(
+ ng-show="previousUrl"
+ tg-bo-href="previousUrl"
+ title="{{'TASK.PREVIOUS' | translate}}"
+ )
+ a.icon.icon-arrow-right(
+ ng-show="nextUrl"
+ tg-bo-href="nextUrl"
+ title="{{'TASK.NEXT' | translate}}"
+ )
div.tags-block(tg-tag-line, ng-model="task", required-perm="modify_task")
section.duty-content(tg-editable-description, ng-model="task", required-perm="modify_task")
// Custom Fields
- tg-custom-attributes-values(ng-model="task", type="task", project="project", required-edition-perm="modify_task")
+ tg-custom-attributes-values(
+ ng-model="task"
+ type="task"
+ project="project"
+ required-edition-perm="modify_task"
+ )
tg-attachments(ng-model="task", type="task")
tg-history(ng-model="task", type="task")
- sidebar.menu-secondary.sidebar
- 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")
+ sidebar.menu-secondary.sidebar.ticket-data
- section.duty-assigned-to(tg-assigned-to, ng-model="task", required-perm="modify_task")
+ section.status
- section.watchers(tg-watchers, ng-model="task", required-perm="modify_task")
+ .ticket-title(tg-task-status-display, ng-model="task")
- section.us-detail-settings
- 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="{{'TASK.TITLE_DELETE_ACTION' | translate}}",
- on-delete-go-to-url="onDeleteGoToUrl",
- ng-model="task")
+ .ticket-created-by(tg-created-by-display, ng-model="task")
- div.lightbox.lightbox-block(tg-lb-block, title="TASK.LIGHTBOX_TITLE_BLOKING_TASK", ng-model="task")
+ .ticket-data-container
+ .ticket-status(tg-task-status-button, ng-model="task")
+
+ section.ticket-assigned-to(tg-assigned-to, ng-model="task", required-perm="modify_task")
+
+ section.track-buttons-container.ticket-track-buttons
+ div.watch-button
+ tg-watch-button(
+ item="task"
+ on-watch="ctrl.onWatch"
+ on-unwatch="ctrl.onUnwatch"
+ )
+
+ div.ticket-watchers(
+ tg-watchers,
+ ng-model="task",
+ required-perm="modify_task"
+ )
+
+ section.ticket-detail-settings
+ 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="{{'TASK.TITLE_DELETE_ACTION' | translate}}"
+ on-delete-go-to-url="onDeleteGoToUrl"
+ ng-model="task"
+ )
+
+ div.lightbox.lightbox-block(tg-lb-block, ng-model="task", title="TASK.LIGHTBOX_TITLE_BLOKING_TASK")
div.lightbox.lightbox-select-user(tg-lb-assignedto)
div.lightbox.lightbox-select-user(tg-lb-watchers)
diff --git a/app/partials/us/us-detail.jade b/app/partials/us/us-detail.jade
index 95ca50f1..f3606df8 100644
--- a/app/partials/us/us-detail.jade
+++ b/app/partials/us/us-detail.jade
@@ -1,80 +1,152 @@
doctype html
-div.wrapper(ng-controller="UserStoryDetailController as ctrl",
- ng-init="section='backlog-kanban'")
+div.wrapper(
+ ng-controller="UserStoryDetailController as ctrl",
+ ng-init="section='backlog-kanban'"
+)
tg-project-menu
+
div.main.us-detail
div.us-detail-header.header-with-actions
include ../includes/components/mainTitle
.action-buttons
a.button-gray(
- tg-check-permission="view_milestones",
- href="", title="{{'US.TITLE_LINK_TASKBOARD' | translate}}",
- tg-nav="project-taskboard:project=project.slug,sprint=sprint.slug",
- ng-if="sprint && project.is_backlog_activated", translate="US.LINK_TASKBOARD")
+ href=""
+ tg-check-permission="view_milestones"
+ tg-nav="project-taskboard:project=project.slug,sprint=sprint.slug"
+ ng-if="sprint && project.is_backlog_activated"
+ title="{{'US.TITLE_LINK_TASKBOARD' | translate}}"
+ translate="US.LINK_TASKBOARD"
+ )
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(tg-editable-subject, ng-model="us", required-perm="modify_us")
+ header
+ tg-vote-button.upvote-btn(
+ item="us"
+ on-upvote="ctrl.onUpvote"
+ on-downvote="ctrl.onDownvote"
+ )
+ div.us-title(ng-class="{blocked: us.is_blocked}")
+ h2.us-title-text
+ span.us-number(tg-bo-ref="us.ref")
+ span.us-name(tg-editable-subject, ng-model="us", required-perm="modify_us")
- p.us-related-task(ng-if="us.origin_issue")
- | {{ 'US.PROMOTED'|translate }}
- a(tg-check-permission="view_us", href="", title="{{'US.TITLE_LINK_GO_TO_ISSUE' | translate}}",
- tg-nav="project-issues-detail:project=project.slug,ref=us.origin_issue.ref"
- tg-bo-title="'#' + us.origin_issue.ref + ' ' + us.origin_issue.subject")
- span(tg-bo-ref="us.origin_issue.ref")
+ p.us-related-task(ng-if="us.origin_issue")
+ | {{ 'US.PROMOTED'|translate }}
+ a(
+ href=""
+ tg-check-permission="view_us"
+ tg-nav="project-issues-detail:project=project.slug,ref=us.origin_issue.ref"
+ tg-bo-title="'#' + us.origin_issue.ref + ' ' + us.origin_issue.subject"
+ title="{{'US.TITLE_LINK_GO_TO_ISSUE' | translate}}"
+ )
+ span(tg-bo-ref="us.origin_issue.ref")
- p.external-reference(ng-if="us.external_reference")
- | {{ 'US.EXTERNAL_REFERENCE'|translate }}
- a(target="_blank", tg-bo-href="us.external_reference[1]",
- title="{{'US.GO_TO_EXTERNAL_REFERENCE' | translate}}")
- span {{ us.external_reference[1] }}
+ p.external-reference(ng-if="us.external_reference")
+ | {{ 'US.EXTERNAL_REFERENCE'|translate }}
+ a(
+ tg-bo-href="us.external_reference[1]",
+ title="{{'US.GO_TO_EXTERNAL_REFERENCE' | translate}}"
+ target="_blank"
+ )
+ span {{ us.external_reference[1] }}
- p.block-desc-container(ng-show="us.is_blocked")
- span.block-description-title(translate="COMMON.BLOCKED")
- span.block-description(ng-bind="us.blocked_note || ('US.BLOCKED' | translate)")
- div.issue-nav
- a.icon.icon-arrow-left(ng-show="previousUrl", tg-bo-href="previousUrl",
- title="{{'US.PREVIOUS' | translate}}")
- a.icon.icon-arrow-right(ng-show="nextUrl", tg-bo-href="nextUrl",
- title="{{'US.NEXT' | translate}}")
+ p.block-desc-container(ng-show="us.is_blocked")
+ span.block-description-title(translate="COMMON.BLOCKED")
+ span.block-description(ng-bind="us.blocked_note || ('US.BLOCKED' | translate)")
+ div.issue-nav
+ a.icon.icon-arrow-left(
+ ng-show="previousUrl"
+ tg-bo-href="previousUrl"
+ title="{{'US.PREVIOUS' | translate}}"
+ )
+ a.icon.icon-arrow-right(
+ ng-show="nextUrl"
+ tg-bo-href="nextUrl"
+ title="{{'US.NEXT' | translate}}"
+ )
div.tags-block(tg-tag-line, ng-model="us", required-perm="modify_us")
section.duty-content(tg-editable-description, ng-model="us", required-perm="modify_us")
// Custom Fields
- tg-custom-attributes-values(ng-model="us", type="userstory", project="project", required-edition-perm="modify_us")
+ tg-custom-attributes-values(
+ ng-model="us"
+ type="userstory"
+ project="project"
+ required-edition-perm="modify_us"
+ )
include ../includes/modules/related-tasks
- tg-attachments(ng-model="us", type="us")
- tg-history(ng-model="us", type="us")
+ tg-attachments(
+ ng-model="us"
+ type="us"
+ )
+ tg-history(
+ ng-model="us"
+ type="us"
+ )
- sidebar.menu-secondary.sidebar
- 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")
+ sidebar.menu-secondary.sidebar.ticket-data
+ section
+ div.ticket-title(
+ tg-us-status-display
+ ng-model="us"
+ )
+
+ tg-created-by-display.ticket-created-by(ng-model="us")
+
+ //div.ticket-detail-progress-bar(tg-us-tasks-progress-display, ng-model="tasks")
+
+ div.ticket-data-container
+ div.ticket-status(
+ tg-us-status-button
+ ng-model="us"
+ )
+
+ section.ticket-estimation
tg-us-estimation(ng-model="us")
- 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.ticket-assigned-to(
+ tg-assigned-to
+ ng-model="us"
+ required-perm="modify_us"
+ )
- section.watchers(tg-watchers, ng-model="us", required-perm="modify_us")
+ section.track-buttons-container.ticket-track-buttons
+ div.watch-button
+ tg-watch-button(
+ item="us"
+ on-watch="ctrl.onWatch"
+ on-unwatch="ctrl.onUnwatch"
+ )
- section.us-detail-settings
+ div.ticket-watchers(
+ tg-watchers
+ ng-model="us"
+ required-perm="modify_us"
+ )
+
+ section.ticket-detail-settings
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' | translate}}",
- on-delete-go-to-url="onDeleteGoToUrl",
- 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' | translate}}"
+ on-delete-go-to-url="onDeleteGoToUrl"
+ ng-model="us"
+ )
- div.lightbox.lightbox-block(tg-lb-block, title="{{ 'US.LIGHTBOX_TITLE_BLOKING_US' | translate }}", ng-model="us")
+ div.lightbox.lightbox-block(
+ tg-lb-block
+ title="{{ 'US.LIGHTBOX_TITLE_BLOKING_US' | translate }}"
+ ng-model="us"
+ )
div.lightbox.lightbox-select-user(tg-lb-assignedto)
div.lightbox.lightbox-select-user(tg-lb-watchers)