commit
6711ecdc7d
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -1,5 +1,15 @@
|
|||
# Changelog
|
||||
|
||||
## 3.3.0 Picea mariana (2018-04-26)
|
||||
|
||||
### Features
|
||||
|
||||
- Add "live notifications" to Taiga:
|
||||
- Add configuration in profile area.
|
||||
- Add "due date" in US, Tasks and Issues.
|
||||
- Add multiple assignement only in US.
|
||||
- Delete cards in Kanban and sprint Taskboard.
|
||||
|
||||
## 3.2.3 (2018-04-04)
|
||||
|
||||
### Misc
|
||||
|
|
|
@ -451,6 +451,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
|||
{templateUrl: "user/user-change-password.html"})
|
||||
$routeProvider.when("/user-settings/mail-notifications",
|
||||
{templateUrl: "user/mail-notifications.html"})
|
||||
$routeProvider.when("/user-settings/live-notifications",
|
||||
{templateUrl: "user/live-notifications.html"})
|
||||
$routeProvider.when("/change-email/:email_token",
|
||||
{templateUrl: "user/change-email.html"})
|
||||
$routeProvider.when("/cancel-account/:cancel_token",
|
||||
|
|
|
@ -124,6 +124,7 @@ urls = {
|
|||
"user-settings-user-change-password": "/user-settings/user-change-password"
|
||||
"user-settings-user-avatar": "/user-settings/user-avatar"
|
||||
"user-settings-mail-notifications": "/user-settings/mail-notifications"
|
||||
"user-settings-live-notifications": "/user-settings/live-notifications"
|
||||
"user-settings-contrib": "/user-settings/contrib/:plugin"
|
||||
|
||||
}
|
||||
|
|
|
@ -292,7 +292,6 @@ class QueueModelTransformation extends taiga.Service
|
|||
|
||||
save: (transformation) ->
|
||||
defered = @q.defer()
|
||||
|
||||
@qqueue.add () =>
|
||||
obj = @.getObj()
|
||||
comment = obj.comment
|
||||
|
|
|
@ -230,7 +230,6 @@ WatchersDirective = ($rootscope, $confirm, $repo, $modelTransform, $template, $c
|
|||
watchers = _.map(watchers, (watcherId) -> $scope.usersById[watcherId])
|
||||
renderWatchers(watchers)
|
||||
$rootscope.$broadcast("object:updated")
|
||||
|
||||
transform.then null, ->
|
||||
$confirm.notify("error")
|
||||
|
||||
|
@ -299,6 +298,129 @@ module.directive("tgWatchers", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgQueue
|
|||
"$translate", WatchersDirective])
|
||||
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Assigned Users directive
|
||||
#############################################################################
|
||||
|
||||
AssignedUsersDirective = ($rootscope, $confirm, $repo, $modelTransform, $template, $compile, $translate, $currentUserService) ->
|
||||
# You have to include a div with the tg-lb-assignedusers directive in the page
|
||||
# where use this directive
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
isEditable = ->
|
||||
return $scope.project?.my_permissions?.indexOf($attrs.requiredPerm) != -1
|
||||
isAssigned = ->
|
||||
return $scope.assignedUsers.length > 0
|
||||
|
||||
save = (assignedUsers, assignedToUser) ->
|
||||
transform = $modelTransform.save (item) ->
|
||||
item.assigned_users = assignedUsers
|
||||
if not item.assigned_to
|
||||
item.assigned_to = assignedToUser
|
||||
return item
|
||||
|
||||
transform.then ->
|
||||
assignedUsers = _.map(assignedUsers, (assignedUserId) -> $scope.usersById[assignedUserId])
|
||||
renderAssignedUsers(assignedUsers)
|
||||
result = $rootscope.$broadcast("object:updated")
|
||||
|
||||
transform.then null, ->
|
||||
$confirm.notify("error")
|
||||
|
||||
openAssignedUsers = ->
|
||||
item = _.clone($model.$modelValue, false)
|
||||
$rootscope.$broadcast("assigned-user:add", item)
|
||||
|
||||
assignToMe = ->
|
||||
return if not isEditable()
|
||||
currentUserId = $currentUserService.getUser().get('id')
|
||||
assignedUsers = _.clone($model.$modelValue.assigned_users, false)
|
||||
assignedUsers.push(currentUserId)
|
||||
assignedUsers = _.uniq(assignedUsers)
|
||||
save(assignedUsers, currentUserId)
|
||||
|
||||
deleteAssignedUser = (assignedUserIds) ->
|
||||
transform = $modelTransform.save (item) ->
|
||||
item.assigned_users = assignedUserIds
|
||||
|
||||
# Update as
|
||||
if item.assigned_to not in assignedUserIds and assignedUserIds.length > 0
|
||||
item.assigned_to = assignedUserIds[0]
|
||||
if assignedUserIds.length == 0
|
||||
item.assigned_to = null
|
||||
|
||||
return item
|
||||
|
||||
transform.then () ->
|
||||
item = $modelTransform.getObj()
|
||||
assignedUsers = _.map(item.assignedUsers, (assignedUserId) -> $scope.usersById[assignedUserId])
|
||||
renderAssignedUsers(assignedUsers)
|
||||
$rootscope.$broadcast("object:updated")
|
||||
|
||||
transform.then null, ->
|
||||
item.revert()
|
||||
$confirm.notify("error")
|
||||
|
||||
renderAssignedUsers = (assignedUsers) ->
|
||||
$scope.assignedUsers = assignedUsers
|
||||
$scope.isEditable = isEditable()
|
||||
$scope.isAssigned = isAssigned()
|
||||
$scope.openAssignedUsers = openAssignedUsers
|
||||
$scope.assignToMe = assignToMe
|
||||
|
||||
$el.on "click", ".remove-user", (event) ->
|
||||
event.preventDefault()
|
||||
return if not isEditable()
|
||||
target = angular.element(event.currentTarget)
|
||||
assignedUserId = target.data("assigned-user-id")
|
||||
|
||||
title = $translate.instant("COMMON.ASSIGNED_USERS.TITLE_LIGHTBOX_DELETE_ASSIGNED")
|
||||
message = $scope.usersById[assignedUserId].full_name_display
|
||||
|
||||
$confirm.askOnDelete(title, message).then (askResponse) =>
|
||||
askResponse.finish()
|
||||
|
||||
assignedUserIds = _.clone($model.$modelValue.assigned_users, false)
|
||||
assignedUserIds = _.pull(assignedUserIds, assignedUserId)
|
||||
|
||||
deleteAssignedUser(assignedUserIds)
|
||||
|
||||
$scope.$on "assigned-user:deleted", (ctx, assignedUserId) ->
|
||||
assignedUsersIds = _.clone($model.$modelValue.assigned_users, false)
|
||||
assignedUsersIds = _.pull(assignedUsersIds, assignedUserId)
|
||||
assignedUsersIds = _.uniq(assignedUsersIds)
|
||||
deleteAssignedUser(assignedUsersIds)
|
||||
|
||||
$scope.$on "assigned-user:added", (ctx, assignedUserId) ->
|
||||
assignedUsers = _.clone($model.$modelValue.assigned_users, false)
|
||||
assignedUsers.push(assignedUserId)
|
||||
assignedUsers = _.uniq(assignedUsers)
|
||||
|
||||
# Save assigned_users and assignedUserId for assign_to legacy attribute
|
||||
save(assignedUsers, assignedUserId)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (item) ->
|
||||
return if not item?
|
||||
assignedUsers = _.map(item.assigned_users, (assignedUserId) -> $scope.usersById[assignedUserId])
|
||||
assignedUsers = _.filter assignedUsers, (it) -> return !!it
|
||||
|
||||
renderAssignedUsers(assignedUsers)
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
scope: true,
|
||||
templateUrl: "common/components/assigned-users.html",
|
||||
link:link,
|
||||
require:"ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgAssignedUsers", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgQueueModelTransformation", "$tgTemplate", "$compile",
|
||||
"$translate", "tgCurrentUserService", AssignedUsersDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Assigned to directive
|
||||
#############################################################################
|
||||
|
@ -386,7 +508,6 @@ AssignedToDirective = ($rootscope, $confirm, $repo, $loading, $modelTransform, $
|
|||
|
||||
$scope.$on "assigned-to:added", (ctx, userId, item) ->
|
||||
return if item.id != $model.$modelValue.id
|
||||
|
||||
save(userId)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (instance) ->
|
||||
|
|
|
@ -86,8 +86,10 @@ class ConfirmService extends taiga.Service
|
|||
|
||||
return defered.promise
|
||||
|
||||
askOnDelete: (title, message) ->
|
||||
return @.ask(title, @translate.instant("NOTIFICATION.ASK_DELETE"), message)
|
||||
askOnDelete: (title, message, subtitle) ->
|
||||
if not subtitle?
|
||||
subtitle = @translate.instant("NOTIFICATION.ASK_DELETE")
|
||||
return @.ask(title, subtitle, message)
|
||||
|
||||
askChoice: (title, subtitle, choices, replacement, warning, lightboxSelector=".lightbox-ask-choice") ->
|
||||
defered = @q.defer()
|
||||
|
|
|
@ -54,10 +54,10 @@ class LightboxService extends taiga.Service
|
|||
@animationFrame.add ->
|
||||
$el.addClass("open")
|
||||
$el.one "transitionend", =>
|
||||
firstField = $el.find('input,textarea').first()
|
||||
firstField = $el.find('input:not(.no-focus),textarea:not(.no-focus)').first()
|
||||
|
||||
if firstField.length
|
||||
$el.find('input,textarea').first().focus()
|
||||
firstField.focus()
|
||||
else if document.activeElement
|
||||
$(document.activeElement).blur()
|
||||
|
||||
|
@ -646,7 +646,6 @@ AssignedToLightboxDirective = (lightboxService, lightboxKeyboardNavigationServic
|
|||
selectedItem = item
|
||||
assignedToId = item.assigned_to
|
||||
selectedUser = $scope.usersById[assignedToId]
|
||||
|
||||
render(selectedUser)
|
||||
lightboxService.open($el).then ->
|
||||
$el.find('input').focus()
|
||||
|
@ -693,10 +692,121 @@ AssignedToLightboxDirective = (lightboxService, lightboxKeyboardNavigationServic
|
|||
link:link
|
||||
}
|
||||
|
||||
|
||||
module.directive("tgLbAssignedto", ["lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", "$compile", "tgAvatarService", AssignedToLightboxDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Assigned Users Lightbox directive
|
||||
#############################################################################
|
||||
|
||||
AssignedUsersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationService, $template, $compile, avatarService) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
selectedUsers = []
|
||||
selectedItem = null
|
||||
usersTemplate = $template.get("common/lightbox/lightbox-assigned-users-users.html", true)
|
||||
|
||||
normalizeString = (string) ->
|
||||
normalizedString = string
|
||||
normalizedString = normalizedString.replace("Á", "A").replace("Ä", "A").replace("À", "A")
|
||||
normalizedString = normalizedString.replace("É", "E").replace("Ë", "E").replace("È", "E")
|
||||
normalizedString = normalizedString.replace("Í", "I").replace("Ï", "I").replace("Ì", "I")
|
||||
normalizedString = normalizedString.replace("Ó", "O").replace("Ö", "O").replace("Ò", "O")
|
||||
normalizedString = normalizedString.replace("Ú", "U").replace("Ü", "U").replace("Ù", "U")
|
||||
return normalizedString
|
||||
|
||||
filterUsers = (text, user) ->
|
||||
username = user.full_name_display.toUpperCase()
|
||||
username = normalizeString(username)
|
||||
text = text.toUpperCase()
|
||||
text = normalizeString(text)
|
||||
|
||||
return _.includes(username, text)
|
||||
|
||||
# Render the specific list of users.
|
||||
render = (assignedUsersIds, text) ->
|
||||
users = _.clone($scope.activeUsers, true)
|
||||
users = _.sortBy(users, (o) -> if o.id is $scope.user.id then 0 else o.id)
|
||||
users = _.filter(users, _.partial(filterUsers, text)) if text?
|
||||
|
||||
# Add selected users
|
||||
selected = []
|
||||
_.map users, (user) ->
|
||||
if user.id in assignedUsersIds
|
||||
user.avatar = avatarService.getAvatar(user)
|
||||
selected.push(user)
|
||||
|
||||
# Filter users in searchs
|
||||
|
||||
visible = []
|
||||
_.map users, (user) ->
|
||||
if user.id not in assignedUsersIds
|
||||
user.avatar = avatarService.getAvatar(user)
|
||||
visible.push(user)
|
||||
|
||||
ctx = {
|
||||
selected: selected
|
||||
users: _.slice(visible, 0, 5)
|
||||
showMore: users.length > 5
|
||||
}
|
||||
|
||||
html = usersTemplate(ctx)
|
||||
html = $compile(html)($scope)
|
||||
$el.find(".assigned-to-list").html(html)
|
||||
|
||||
closeLightbox = () ->
|
||||
lightboxKeyboardNavigationService.stop()
|
||||
lightboxService.close($el)
|
||||
|
||||
$scope.$on "assigned-user:add", (ctx, item) ->
|
||||
selectedItem = item
|
||||
selectedUsers = item.assigned_users
|
||||
render(selectedUsers)
|
||||
|
||||
lightboxService.open($el).then ->
|
||||
$el.find("input").focus()
|
||||
lightboxKeyboardNavigationService.init($el)
|
||||
|
||||
$scope.$watch "usersSearch", (searchingText) ->
|
||||
if searchingText?
|
||||
render(selectedUsers, searchingText)
|
||||
$el.find('input').focus()
|
||||
|
||||
$el.on "click", ".user-list-single", debounce 200, (event) ->
|
||||
closeLightbox()
|
||||
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
|
||||
$scope.$apply ->
|
||||
$scope.usersSearch = null
|
||||
$scope.$broadcast("assigned-user:added", target.data("user-id"), selectedItem)
|
||||
|
||||
$el.on "click", ".remove-assigned-to", (event) ->
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
event.stopPropagation()
|
||||
|
||||
$scope.$apply ->
|
||||
$scope.usersSearch = null
|
||||
$scope.$broadcast("assigned-user:deleted", target.data("user-id"), selectedItem)
|
||||
closeLightbox()
|
||||
|
||||
$el.on "click", ".close", (event) ->
|
||||
event.preventDefault()
|
||||
|
||||
closeLightbox()
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
templateUrl: "common/lightbox/lightbox-assigned-users.html"
|
||||
link:link
|
||||
}
|
||||
|
||||
module.directive("tgLbAssignedUsers", ["$tgRepo", "lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", "$compile", "tgAvatarService", AssignedUsersLightboxDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Watchers Lightbox directive
|
||||
#############################################################################
|
||||
|
@ -802,3 +912,73 @@ LightboxLeaveProjectWarningDirective = (lightboxService, $template, $compile) ->
|
|||
}
|
||||
|
||||
module.directive("tgLightboxLeaveProjectWarning", ["lightboxService", LightboxLeaveProjectWarningDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Set Due Date Lightbox Directive
|
||||
#############################################################################
|
||||
|
||||
SetDueDateDirective = (lightboxService, $loading, $translate, $confirm, $modelTransform) ->
|
||||
link = ($scope, $el, attrs) ->
|
||||
prettyDate = $translate.instant("COMMON.PICKERDATE.FORMAT")
|
||||
lightboxService.open($el)
|
||||
|
||||
if ($scope.object.due_date)
|
||||
$scope.new_due_date = moment($scope.object.due_date).format(prettyDate)
|
||||
|
||||
$el.on "click", ".suggestion", (event) ->
|
||||
target = angular.element(event.currentTarget)
|
||||
quantity = target.data('quantity')
|
||||
unit = target.data('unit')
|
||||
value = moment().add(quantity, unit).format(prettyDate)
|
||||
$el.find(".due-date").val(value)
|
||||
|
||||
save = ->
|
||||
currentLoading = $loading()
|
||||
.target($el.find(".submit-button"))
|
||||
.start()
|
||||
|
||||
transform = $modelTransform.save (object) ->
|
||||
new_due_date = $('.due-date').val()
|
||||
object.due_date = if (new_due_date) \
|
||||
then moment(new_due_date, prettyDate).format("YYYY-MM-DD") \
|
||||
else null
|
||||
return object
|
||||
|
||||
transform.then ->
|
||||
$confirm.notify("success")
|
||||
|
||||
transform.then null, ->
|
||||
$confirm.notify("error")
|
||||
|
||||
transform.finally ->
|
||||
currentLoading.finish()
|
||||
lightboxService.close($el)
|
||||
|
||||
$el.on "click", ".submit-button", (event) ->
|
||||
event.preventDefault()
|
||||
save()
|
||||
|
||||
remove = ->
|
||||
title = $translate.instant("LIGHTBOX.DELETE_DUE_DATE.TITLE")
|
||||
subtitle = $translate.instant("LIGHTBOX.DELETE_DUE_DATE.SUBTITLE")
|
||||
message = moment($scope.object.due_date).format(prettyDate)
|
||||
|
||||
$confirm.askOnDelete(title, message, subtitle).then (askResponse) ->
|
||||
askResponse.finish()
|
||||
$('.due-date').val(null)
|
||||
$scope.object.due_date_reason = null
|
||||
save()
|
||||
|
||||
$el.on "click", ".delete-due-date", (event) ->
|
||||
event.preventDefault()
|
||||
remove()
|
||||
|
||||
return {
|
||||
templateUrl: 'common/lightbox/lightbox-due-date.html',
|
||||
link: link,
|
||||
scope: true
|
||||
}
|
||||
|
||||
module.directive("tgLbSetDueDate", ["lightboxService", "$tgLoading", "$translate", "$tgConfirm"
|
||||
"$tgQueueModelTransformation", SetDueDateDirective])
|
||||
|
|
|
@ -87,6 +87,37 @@ class EventsService
|
|||
@liveAnnouncementService.show(data.title, data.desc)
|
||||
@rootScope.$digest()
|
||||
|
||||
liveNotifications: ->
|
||||
if not @.auth.userData?
|
||||
return
|
||||
userId = @.auth.userData.get('id')
|
||||
|
||||
subscribe = () =>
|
||||
@.subscribe null, "live_notifications.#{userId}", (data) =>
|
||||
notification = new Notification(data.title, {
|
||||
icon: "/#{window._version}/images/favicon.png",
|
||||
body: data.body,
|
||||
tag: data.id
|
||||
})
|
||||
notification.onshow = () =>
|
||||
if data.timeout and data.timeout > 0
|
||||
setTimeout =>
|
||||
notification.close()
|
||||
,
|
||||
data.timeout
|
||||
|
||||
if data.url
|
||||
notification.onclick = () =>
|
||||
window.open data.url
|
||||
if !Notification
|
||||
console.log("This browser does not support desktop notification")
|
||||
else if Notification.permission == "granted"
|
||||
subscribe()
|
||||
else if Notification.permission != 'denied'
|
||||
Notification.requestPermission (permission) =>
|
||||
if (permission == "granted")
|
||||
subscribe()
|
||||
|
||||
###########################################
|
||||
# Heartbeat (Ping - Pong)
|
||||
###########################################
|
||||
|
@ -216,6 +247,7 @@ class EventsService
|
|||
@.sendMessage(message)
|
||||
@.startHeartBeatMessages()
|
||||
@.notifications()
|
||||
@.liveNotifications()
|
||||
|
||||
onMessage: (event) ->
|
||||
@.log.debug "WebSocket message received: #{event.data}"
|
||||
|
|
|
@ -625,6 +625,7 @@ PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm, $translate) ->
|
|||
tags: issue.tags
|
||||
is_blocked: issue.is_blocked
|
||||
blocked_note: issue.blocked_note
|
||||
due_date: issue.due_date
|
||||
}
|
||||
|
||||
onSuccess = ->
|
||||
|
|
|
@ -151,7 +151,12 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
|||
@.refreshTagsColors().then () =>
|
||||
@kanbanUserstoriesService.replaceModel(us)
|
||||
|
||||
@scope.$on "kanban:us:deleted", (event, us) =>
|
||||
@.filtersReloadContent()
|
||||
|
||||
@scope.$on("assigned-to:added", @.onAssignedToChanged)
|
||||
@scope.$on("assigned-user:added", @.onAssignedUsersChanged)
|
||||
@scope.$on("assigned-user:deleted", @.onAssignedUsersDeleted)
|
||||
@scope.$on("kanban:us:move", @.moveUs)
|
||||
@scope.$on("kanban:show-userstories-for-status", @.loadUserStoriesForStatus)
|
||||
@scope.$on("kanban:hide-userstories-for-status", @.hideUserStoriesForStatus)
|
||||
|
@ -165,7 +170,7 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
|||
|
||||
editUs: (id) ->
|
||||
us = @kanbanUserstoriesService.getUs(id)
|
||||
us = us.set('loading', true)
|
||||
us = us.set('loading-edit', true)
|
||||
@kanbanUserstoriesService.replace(us)
|
||||
|
||||
@rs.userstories.getByRef(us.getIn(['model', 'project']), us.getIn(['model', 'ref']))
|
||||
|
@ -173,9 +178,27 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
|||
@rs2.attachments.list("us", us.get('id'), us.getIn(['model', 'project'])).then (attachments) =>
|
||||
@rootscope.$broadcast("usform:edit", editingUserStory, attachments.toJS())
|
||||
|
||||
us = us.set('loading', false)
|
||||
us = us.set('loading-edit', false)
|
||||
@kanbanUserstoriesService.replace(us)
|
||||
|
||||
deleteUs: (id) ->
|
||||
us = @kanbanUserstoriesService.getUs(id)
|
||||
us = us.set('loading-delete', true)
|
||||
|
||||
@rs.userstories.getByRef(us.getIn(['model', 'project']), us.getIn(['model', 'ref']))
|
||||
.then (deletingUserStory) =>
|
||||
us = us.set('loading-delete', false)
|
||||
title = @translate.instant("US.TITLE_DELETE_ACTION")
|
||||
message = deletingUserStory.subject
|
||||
@confirm.askOnDelete(title, message).then (askResponse) =>
|
||||
promise = @repo.remove(deletingUserStory)
|
||||
promise.then =>
|
||||
@scope.$broadcast("kanban:us:deleted")
|
||||
askResponse.finish()
|
||||
promise.then null, ->
|
||||
askResponse.finish(false)
|
||||
@confirm.notify("error")
|
||||
|
||||
showPlaceHolder: (statusId) ->
|
||||
if @scope.usStatusList[0].id == statusId &&
|
||||
!@kanbanUserstoriesService.userstoriesRaw.length
|
||||
|
@ -194,6 +217,10 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
|||
|
||||
@rootscope.$broadcast("assigned-to:add", us)
|
||||
|
||||
changeUsAssignedUsers: (id) ->
|
||||
us = @kanbanUserstoriesService.getUsModel(id)
|
||||
@rootscope.$broadcast("assigned-user:add", us)
|
||||
|
||||
onAssignedToChanged: (ctx, userid, usModel) ->
|
||||
usModel.assigned_to = userid
|
||||
|
||||
|
@ -204,6 +231,37 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
|||
if @.isFilterDataTypeSelected('assigned_to') || @.isFilterDataTypeSelected('role')
|
||||
@.filtersReloadContent()
|
||||
|
||||
onAssignedUsersChanged: (ctx, userid, usModel) ->
|
||||
assignedUsers = _.clone(usModel.assigned_users, false)
|
||||
assignedUsers.push(userid)
|
||||
assignedUsers = _.uniq(assignedUsers)
|
||||
usModel.assigned_users = assignedUsers
|
||||
if not usModel.assigned_to
|
||||
usModel.assigned_to = userid
|
||||
@kanbanUserstoriesService.replaceModel(usModel)
|
||||
|
||||
promise = @repo.save(usModel)
|
||||
promise.then null, ->
|
||||
console.log "FAIL" # TODO
|
||||
|
||||
onAssignedUsersDeleted: (ctx, userid, usModel) ->
|
||||
assignedUsersIds = _.clone(usModel.assigned_users, false)
|
||||
assignedUsersIds = _.pull(assignedUsersIds, userid)
|
||||
assignedUsersIds = _.uniq(assignedUsersIds)
|
||||
usModel.assigned_users = assignedUsersIds
|
||||
|
||||
# Update as
|
||||
if usModel.assigned_to not in assignedUsersIds and assignedUsersIds.length > 0
|
||||
usModel.assigned_to = assignedUsersIds[0]
|
||||
if assignedUsersIds.length == 0
|
||||
usModel.assigned_to = null
|
||||
|
||||
@kanbanUserstoriesService.replaceModel(usModel)
|
||||
|
||||
promise = @repo.save(usModel)
|
||||
promise.then null, ->
|
||||
console.log "FAIL" # TODO
|
||||
|
||||
refreshTagsColors: ->
|
||||
return @rs.projects.tagsColors(@scope.projectId).then (tags_colors) =>
|
||||
@scope.project.tags_colors = tags_colors._attrs
|
||||
|
|
|
@ -308,6 +308,9 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
|
|||
@.refreshTagsColors().then () =>
|
||||
@taskboardTasksService.replaceModel(task)
|
||||
|
||||
@scope.$on "taskboard:task:deleted", (event, task) =>
|
||||
@.loadTasks()
|
||||
|
||||
@scope.$on("taskboard:task:move", @.taskMove)
|
||||
@scope.$on("assigned-to:added", @.onAssignedToChanged)
|
||||
|
||||
|
@ -432,7 +435,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
|
|||
editTask: (id) ->
|
||||
task = @.taskboardTasksService.getTask(id)
|
||||
|
||||
task = task.set('loading', true)
|
||||
task = task.set('loading-edit', true)
|
||||
@taskboardTasksService.replace(task)
|
||||
|
||||
@rs.tasks.getByRef(task.getIn(['model', 'project']), task.getIn(['model', 'ref'])).then (editingTask) =>
|
||||
|
@ -441,6 +444,25 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
|
|||
task = task.set('loading', false)
|
||||
@taskboardTasksService.replace(task)
|
||||
|
||||
deleteTask: (id) ->
|
||||
task = @.taskboardTasksService.getTask(id)
|
||||
task = task.set('loading-delete', true)
|
||||
|
||||
@rs.tasks.getByRef(task.getIn(['model', 'project']), task.getIn(['model', 'ref']))
|
||||
.then (deletingTask) =>
|
||||
task = task.set('loading-delete', false)
|
||||
title = @translate.instant("TASK.TITLE_DELETE_ACTION")
|
||||
message = deletingTask.subject
|
||||
@confirm.askOnDelete(title, message).then (askResponse) =>
|
||||
promise = @repo.remove(deletingTask)
|
||||
promise.then =>
|
||||
@scope.$broadcast("taskboard:task:deleted")
|
||||
askResponse.finish()
|
||||
promise.then null, ->
|
||||
askResponse.finish(false)
|
||||
@confirm.notify("error")
|
||||
|
||||
|
||||
taskMove: (ctx, task, oldStatusId, usId, statusId, order) ->
|
||||
task = @taskboardTasksService.getTaskModel(task.get('id'))
|
||||
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
###
|
||||
# Copyright (C) 2014-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
# Copyright (C) 2014-2017 Jesús Espino Garcia <jespinog@gmail.com>
|
||||
# Copyright (C) 2014-2017 David Barragán Merino <bameda@dbarragan.com>
|
||||
# Copyright (C) 2014-2017 Alejandro Alonso <alejandro.alonso@kaleidos.net>
|
||||
# Copyright (C) 2014-2017 Juan Francisco Alcántara <juanfran.alcantara@kaleidos.net>
|
||||
# Copyright (C) 2014-2017 Xavi Julian <xavier.julian@kaleidos.net>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# File: modules/user-settings/live-notifications.coffee
|
||||
###
|
||||
|
||||
taiga = @.taiga
|
||||
mixOf = @.taiga.mixOf
|
||||
bindOnce = @.taiga.bindOnce
|
||||
|
||||
module = angular.module("taigaUserSettings")
|
||||
|
||||
|
||||
#############################################################################
|
||||
## User settings Controller
|
||||
#############################################################################
|
||||
|
||||
class UserLiveNotificationsController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||
@.$inject = [
|
||||
"$scope",
|
||||
"$rootScope",
|
||||
"$tgRepo",
|
||||
"$tgConfirm",
|
||||
"$tgResources",
|
||||
"$routeParams",
|
||||
"$q",
|
||||
"$tgLocation",
|
||||
"$tgNavUrls",
|
||||
"$tgAuth",
|
||||
"tgErrorHandlingService"
|
||||
]
|
||||
|
||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @auth, @errorHandlingService) ->
|
||||
@scope.sectionName = "USER_SETTINGS.NOTIFICATIONS.LIVE_SECTION_NAME"
|
||||
@scope.user = @auth.getUser()
|
||||
promise = @.loadInitialData()
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
loadInitialData: ->
|
||||
return @rs.notifyPolicies.list().then (notifyPolicies) =>
|
||||
@scope.notifyPolicies = notifyPolicies
|
||||
return notifyPolicies
|
||||
|
||||
module.controller("UserLiveNotificationsController", UserLiveNotificationsController)
|
||||
|
||||
|
||||
#############################################################################
|
||||
## User Notifications Directive
|
||||
#############################################################################
|
||||
|
||||
UserLiveNotificationsDirective = () ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgUserLiveNotifications", UserLiveNotificationsDirective)
|
||||
|
||||
|
||||
#############################################################################
|
||||
## User Notifications List Directive
|
||||
#############################################################################
|
||||
|
||||
UserLiveNotificationsListDirective = ($repo, $confirm, $compile) ->
|
||||
template = _.template("""
|
||||
<% _.each(notifyPolicies, function (notifyPolicy, index) { %>
|
||||
<div class="policy-table-row" data-index="<%- index %>">
|
||||
<div class="policy-table-project"><span><%- notifyPolicy.project_name %></span></div>
|
||||
<div class="policy-table-all">
|
||||
<fieldset>
|
||||
<input type="radio"
|
||||
name="policy-<%- notifyPolicy.id %>" id="policy-all-<%- notifyPolicy.id %>"
|
||||
value="2" <% if (notifyPolicy.live_notify_level == 2) { %>checked="checked"<% } %>/>
|
||||
<label for="policy-all-<%- notifyPolicy.id %>"
|
||||
translate="USER_SETTINGS.NOTIFICATIONS.OPTION_ALL"></label>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="policy-table-involved">
|
||||
<fieldset>
|
||||
<input type="radio"
|
||||
name="policy-<%- notifyPolicy.id %>" id="policy-involved-<%- notifyPolicy.id %>"
|
||||
value="1" <% if (notifyPolicy.live_notify_level == 1) { %>checked="checked"<% } %> />
|
||||
<label for="policy-involved-<%- notifyPolicy.id %>"
|
||||
translate="USER_SETTINGS.NOTIFICATIONS.OPTION_INVOLVED"></label>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="policy-table-none">
|
||||
<fieldset>
|
||||
<input type="radio"
|
||||
name="policy-<%- notifyPolicy.id %>" id="policy-none-<%- notifyPolicy.id %>"
|
||||
value="3" <% if (notifyPolicy.live_notify_level == 3) { %>checked="checked"<% } %> />
|
||||
<label for="policy-none-<%- notifyPolicy.id %>"
|
||||
translate="USER_SETTINGS.NOTIFICATIONS.OPTION_NONE"></label>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<% }) %>
|
||||
""")
|
||||
|
||||
link = ($scope, $el, $attrs) ->
|
||||
render = ->
|
||||
$el.off()
|
||||
|
||||
ctx = {notifyPolicies: $scope.notifyPolicies}
|
||||
html = template(ctx)
|
||||
|
||||
$el.html($compile(html)($scope))
|
||||
|
||||
$el.on "change", "input[type=radio]", (event) ->
|
||||
target = angular.element(event.currentTarget)
|
||||
|
||||
policyIndex = target.parents(".policy-table-row").data('index')
|
||||
policy = $scope.notifyPolicies[policyIndex]
|
||||
prev_level = policy.live_notify_level
|
||||
policy.live_notify_level = parseInt(target.val(), 10)
|
||||
|
||||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
target.parents(".policy-table-row")
|
||||
.find("input[value=#{prev_level}]")
|
||||
.prop("checked", true)
|
||||
|
||||
$repo.save(policy).then(onSuccess, onError)
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
bindOnce($scope, $attrs.ngModel, render)
|
||||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgUserLiveNotificationsList", ["$tgRepo", "$tgConfirm", "$compile",
|
||||
UserLiveNotificationsListDirective])
|
|
@ -47,7 +47,8 @@
|
|||
"RELATED_USERSTORIES": "Related user stories",
|
||||
"CARD": {
|
||||
"ASSIGN_TO": "Assign To",
|
||||
"EDIT": "Edit card"
|
||||
"EDIT": "Edit card",
|
||||
"DELETE": "Delete card"
|
||||
},
|
||||
"FORM_ERRORS": {
|
||||
"DEFAULT_MESSAGE": "This value seems to be invalid.",
|
||||
|
@ -143,11 +144,14 @@
|
|||
"SEVERITY": "Severity",
|
||||
"PRIORITY": "Priority",
|
||||
"ASSIGNED_TO": "Assigned to",
|
||||
"ASSIGNED_USERS": "Assigned users",
|
||||
"POINTS": "Points",
|
||||
"IS_BLOCKED": "is blocked",
|
||||
"REF": "Ref",
|
||||
"VOTES": "Votes",
|
||||
"SPRINT": "Sprint"
|
||||
"SPRINT": "Sprint",
|
||||
"DUE_DATE": "Due date",
|
||||
"DUE_DATE_REASON": "Due date reason"
|
||||
},
|
||||
"ROLES": {
|
||||
"ALL": "All"
|
||||
|
@ -162,6 +166,17 @@
|
|||
"TITLE_ACTION_EDIT_ASSIGNMENT": "Edit assignment",
|
||||
"SELF": "Assign to me"
|
||||
},
|
||||
"DUE_DATE": {
|
||||
"TITLE_ACTION_SET_DUE_DATE": "Set due date",
|
||||
"DUE_SOON": "due soon",
|
||||
"PAST_DUE": "past due",
|
||||
"NO_LONGER_APPLICABLE": "no longer applicable"
|
||||
},
|
||||
"ASSIGNED_USERS": {
|
||||
"ADD": "Select assigned user",
|
||||
"ADD_ASSIGNED": "Add assigned",
|
||||
"TITLE_LIGHTBOX_DELETE_ASSIGNED": "Delete assigned..."
|
||||
},
|
||||
"STATUS": {
|
||||
"CLOSED": "Closed",
|
||||
"OPEN": "Open"
|
||||
|
@ -1076,6 +1091,10 @@
|
|||
"EDIT_US": "Edit user story",
|
||||
"CONFIRM_CLOSE": "You have not saved changes.\nAre you sure you want to close the form?"
|
||||
},
|
||||
"DELETE_DUE_DATE": {
|
||||
"TITLE": "Delete due date",
|
||||
"SUBTITLE": "Are you sure you want to delete this due date?"
|
||||
},
|
||||
"DELETE_SPRINT": {
|
||||
"TITLE": "Delete sprint"
|
||||
},
|
||||
|
@ -1106,6 +1125,19 @@
|
|||
"WARNING": "The email will be received by the project admins",
|
||||
"PLACEHOLDER": "Write your message",
|
||||
"SEND": "Send"
|
||||
},
|
||||
"SET_DUE_DATE": {
|
||||
"TITLE": "Set due date",
|
||||
"PLACEHOLDER_DUE_DATE": "Select date",
|
||||
"REASON_FOR_DUE_DATE": "Reason for the due date",
|
||||
"PLACEHOLDER_REASON_FOR_DUE_DATE": "Why does this US need a due date?",
|
||||
"SUGGESTIONS": {
|
||||
"IN_ONE_WEEK": "In one week",
|
||||
"IN_TWO_WEEKS": "In two weeks",
|
||||
"IN_ONE_MONTH": "In one month",
|
||||
"IN_THREE_MONTHS": "In three months"
|
||||
},
|
||||
"TITLE_ACTION_DELETE_DUE_DATE": "Delete due date"
|
||||
}
|
||||
},
|
||||
"EPIC": {
|
||||
|
@ -1194,6 +1226,7 @@
|
|||
"CLIENT_REQUIREMENT": "Client Requirement",
|
||||
"BLOCKED": "Blocked",
|
||||
"VALUES": {
|
||||
"NOT_SET": "not set",
|
||||
"UNASSIGNED": "unassigned"
|
||||
},
|
||||
"FIELDS": {
|
||||
|
@ -1202,6 +1235,8 @@
|
|||
"STATUS": "status",
|
||||
"TYPE": "type",
|
||||
"ASSIGNED_TO": "assigned to",
|
||||
"ASSIGNED_USERS": "assigned users",
|
||||
"DUE_DATE": "due date",
|
||||
"MILESTONE": "sprint",
|
||||
"COLOR": "color"
|
||||
}
|
||||
|
@ -1345,6 +1380,7 @@
|
|||
"SAVED": "Our Oompa Loompas saved all your changes!",
|
||||
"CLOSE": "Close notification",
|
||||
"MAIL": "Notifications By Mail",
|
||||
"DESKTOP": "Desktop notifications using browser alerts",
|
||||
"ASK_DELETE": "Are you sure you want to delete?"
|
||||
},
|
||||
"CANCEL_ACCOUNT": {
|
||||
|
@ -1460,9 +1496,11 @@
|
|||
"SECTION_TITLE": "User Settings",
|
||||
"USER_PROFILE": "User profile",
|
||||
"CHANGE_PASSWORD": "Change password",
|
||||
"EMAIL_NOTIFICATIONS": "Email notifications"
|
||||
"EMAIL_NOTIFICATIONS": "Email notifications",
|
||||
"DESKTOP_NOTIFICATIONS": "Desktop notifications"
|
||||
},
|
||||
"NOTIFICATIONS": {
|
||||
"LIVE_SECTION_NAME": "Desktop Notifications",
|
||||
"SECTION_NAME": "Email Notifications",
|
||||
"COLUMN_PROJECT": "Project",
|
||||
"COLUMN_RECEIVE_ALL": "Receive All",
|
||||
|
|
|
@ -1673,4 +1673,4 @@
|
|||
"RESULTS": "Resultados de búsqueda"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,11 @@
|
|||
ng-if="vm.item.getIn(['model', 'total_points'])"
|
||||
) {{"COMMON.FIELDS.POINTS" | translate}} {{vm.item.getIn(['model', 'total_points'])}}
|
||||
.card-statistics
|
||||
tg-due-date.statistic.card-due-date(
|
||||
due-date="vm.item.getIn(['model', 'due_date'])"
|
||||
due-date-status="vm.item.getIn(['model', 'due_date_status'])"
|
||||
is-closed="vm.item.getIn(['model', 'is_closed'])"
|
||||
)
|
||||
.statistic.card-iocaine(
|
||||
ng-if="vm.item.getIn(['model', 'is_iocaine'])"
|
||||
title="{{'COMMON.IOCAINE_TEXT' | translate}}"
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
.card-owner-actions(
|
||||
ng-if="vm.visible('owner')"
|
||||
tg-check-permission="{{vm.getPermissionsKey()}}"
|
||||
tg-check-permission="{{vm.getModifyPermisionKey()}}"
|
||||
)
|
||||
a.e2e-assign.card-owner-assign(
|
||||
ng-click="!$event.ctrlKey && !$event.metaKey && vm.onClickAssignedTo({id: vm.item.get('id')})"
|
||||
|
@ -29,10 +29,20 @@
|
|||
tg-svg(svg-icon="icon-add-user")
|
||||
span(translate="COMMON.CARD.ASSIGN_TO")
|
||||
|
||||
a.e2e-edit.card-edit(
|
||||
href=""
|
||||
ng-click="!$event.ctrlKey && !$event.metaKey && vm.onClickEdit({id: vm.item.get('id')})"
|
||||
tg-loading="vm.item.get('loading')"
|
||||
)
|
||||
tg-svg(svg-icon="icon-edit")
|
||||
span(translate="COMMON.CARD.EDIT")
|
||||
div.card-actions
|
||||
a.e2e-edit.card-edit(
|
||||
href=""
|
||||
ng-click="!$event.ctrlKey && !$event.metaKey && vm.onClickEdit({id: vm.item.get('id')})"
|
||||
tg-loading="vm.item.get('loading-edit')"
|
||||
title="{{ 'COMMON.CARD.EDIT' | translate }}"
|
||||
)
|
||||
tg-svg(svg-icon="icon-edit")
|
||||
|
||||
a.e2e-edit.card-delete(
|
||||
href=""
|
||||
ng-click="!$event.ctrlKey && !$event.metaKey && vm.onClickDelete({id: vm.item.get('id')})"
|
||||
tg-loading="vm.item.get('loading-delete')"
|
||||
title="{{ 'COMMON.CARD.DELETE' | translate }}"
|
||||
tg-check-permission="{{vm.getDeletePermisionKey()}}"
|
||||
)
|
||||
tg-svg(svg-icon="icon-trash")
|
||||
|
|
|
@ -39,11 +39,11 @@ class CardController
|
|||
closedTasksPercent: () ->
|
||||
return @.getClosedTasks().size * 100 / @.item.getIn(['model', 'tasks']).size
|
||||
|
||||
getPermissionsKey: () ->
|
||||
if @.type == 'task'
|
||||
return 'modify_task'
|
||||
else
|
||||
return 'modify_us'
|
||||
getModifyPermisionKey: () ->
|
||||
return if @.type == 'task' then 'modify_task' else 'modify_us'
|
||||
|
||||
getDeletePermisionKey: () ->
|
||||
return if @.type == 'task' then 'delete_task' else 'delete_us'
|
||||
|
||||
_setVisibility: () ->
|
||||
visibility = {
|
||||
|
|
|
@ -31,6 +31,7 @@ cardDirective = () ->
|
|||
onToggleFold: "&",
|
||||
onClickAssignedTo: "&",
|
||||
onClickEdit: "&",
|
||||
onClickDelete: "&",
|
||||
project: "=",
|
||||
item: "=",
|
||||
zoom: "=",
|
||||
|
|
|
@ -119,6 +119,14 @@
|
|||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 .5rem;
|
||||
}
|
||||
.card-delete:hover {
|
||||
color: $red-light;
|
||||
}
|
||||
.icon {
|
||||
@include svg-size(1.2rem);
|
||||
display: inline-block;
|
||||
|
@ -129,7 +137,7 @@
|
|||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: .6rem 1rem;
|
||||
padding: .6rem .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
label.due-date-button.button-gray.is-editable(
|
||||
ng-if="vm.visible()"
|
||||
ng-disabled="vm.disabled()"
|
||||
ng-class="vm.color()"
|
||||
ng-attr-title="{{ vm.title() }}"
|
||||
ng-click="vm.setDueDate()"
|
||||
)
|
||||
tg-svg(svg-icon="icon-clock")
|
|
@ -0,0 +1,71 @@
|
|||
###
|
||||
# Copyright (C) 2014-2018 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# File: due-date-controller.coffee
|
||||
###
|
||||
|
||||
class DueDateController
|
||||
@.$inject = [
|
||||
"$translate"
|
||||
"tgLightboxFactory"
|
||||
]
|
||||
|
||||
constructor: (@translate, @tgLightboxFactory) ->
|
||||
|
||||
visible: () ->
|
||||
return @.format == 'button' or @.dueDate?
|
||||
|
||||
disabled: () ->
|
||||
return @.isClosed
|
||||
|
||||
color: () ->
|
||||
colors = {
|
||||
'no_longer_applicable': 'closed',
|
||||
'due_soon': 'due-soon',
|
||||
'past_due': 'past-due',
|
||||
'set': 'due-set',
|
||||
}
|
||||
return colors[@.dueDateStatus] or ''
|
||||
|
||||
title: () ->
|
||||
if @.format == 'button'
|
||||
return if @.dueDate then @._formatTitle() else 'Edit due date'
|
||||
|
||||
return @._formatTitle()
|
||||
|
||||
_formatTitle: () ->
|
||||
dueDateStatus = 'closed'
|
||||
titles = {
|
||||
'no_longer_applicable': 'COMMON.DUE_DATE.NO_LONGER_APPLICABLE',
|
||||
'due_soon': 'COMMON.DUE_DATE.DUE_SOON',
|
||||
'past_due': 'COMMON.DUE_DATE.PAST_DUE',
|
||||
}
|
||||
prettyDate = @translate.instant("COMMON.PICKERDATE.FORMAT")
|
||||
formatedDate = moment(@.dueDate).format(prettyDate)
|
||||
|
||||
if not titles[@.dueDateStatus]
|
||||
return formatedDate
|
||||
return "#{formatedDate} (#{@translate.instant(titles[@.dueDateStatus])})"
|
||||
|
||||
setDueDate: () ->
|
||||
return if @.disabled()
|
||||
@tgLightboxFactory.create(
|
||||
"tg-lb-set-due-date",
|
||||
{"class": "lightbox lightbox-set-due-date"},
|
||||
{"object": @.item}
|
||||
)
|
||||
|
||||
angular.module('taigaComponents').controller('DueDate', DueDateController)
|
|
@ -0,0 +1,7 @@
|
|||
span.due-date-icon
|
||||
tg-svg(
|
||||
ng-if="vm.visible()"
|
||||
svg-icon="icon-clock"
|
||||
ng-class="vm.color()"
|
||||
ng-attr-title="{{ vm.title() }}"
|
||||
)
|
|
@ -0,0 +1,43 @@
|
|||
###
|
||||
# Copyright (C) 2014-2018 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# File: due-date.directive.coffee
|
||||
###
|
||||
|
||||
module = angular.module("taigaComponents")
|
||||
|
||||
dueDateDirective = () ->
|
||||
templateUrl = (el, attrs) ->
|
||||
if attrs.format
|
||||
return "components/due-date/due-date-" + attrs.format + ".html"
|
||||
return "components/due-date/due-date-icon.html"
|
||||
|
||||
return {
|
||||
link: (scope) ->
|
||||
controller: "DueDate",
|
||||
controllerAs: "vm",
|
||||
bindToController: true,
|
||||
templateUrl: templateUrl,
|
||||
scope: {
|
||||
dueDate: '=',
|
||||
dueDateStatus: '=',
|
||||
isClosed: '=',
|
||||
item: '=',
|
||||
format: '@'
|
||||
}
|
||||
}
|
||||
|
||||
module.directive('tgDueDate', dueDateDirective)
|
|
@ -0,0 +1,69 @@
|
|||
tg-due-date .due-date-button {
|
||||
background: $gray-light;
|
||||
display: inline-block;
|
||||
margin-right: .5rem;
|
||||
padding: 1rem;
|
||||
transition: background .2s linear;
|
||||
transition-delay: .1s;
|
||||
&.closed,
|
||||
&.closed[disabled] {
|
||||
background: $gray-lighter;
|
||||
}
|
||||
&.due-set {
|
||||
background: $yellow-green;
|
||||
}
|
||||
&.due-soon {
|
||||
background: $my-sin;
|
||||
}
|
||||
&.past-due {
|
||||
background: $red-light;
|
||||
}
|
||||
&:hover {
|
||||
background: $gray;
|
||||
}
|
||||
&.editable {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
tg-due-date .due-date-icon {
|
||||
display: inline-block;
|
||||
line-height: .1rem;
|
||||
margin: 0 .25rem;
|
||||
position: relative;
|
||||
top: .1rem;
|
||||
svg {
|
||||
fill: $gray-light;
|
||||
height: 1.1rem;
|
||||
transition: fill .2s ease-in;
|
||||
width: 1.1rem;
|
||||
}
|
||||
.closed svg {
|
||||
fill: $gray-lighter;
|
||||
}
|
||||
.due-set svg {
|
||||
fill: $yellow-green;
|
||||
}
|
||||
.due-soon svg {
|
||||
fill: $my-sin;
|
||||
}
|
||||
.past-due svg {
|
||||
fill: $red-light;
|
||||
}
|
||||
}
|
||||
|
||||
.backlog-table-body .user-story-name .due-date-icon {
|
||||
top: .25rem;
|
||||
}
|
||||
|
||||
.issues-table .subject .due-date-icon {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.card-statistics .due-date-icon {
|
||||
margin: 0;
|
||||
svg {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
}
|
|
@ -33,6 +33,16 @@
|
|||
)
|
||||
include history-templates/history-assigned
|
||||
|
||||
.diff-wrapper(
|
||||
ng-if="vm.type == 'assigned_users'"
|
||||
)
|
||||
include history-templates/history-assigned-users
|
||||
|
||||
.diff-wrapper(
|
||||
ng-if="vm.type == 'due_date'"
|
||||
)
|
||||
include history-templates/history-due-date
|
||||
|
||||
.diff-wrapper(
|
||||
ng-if="vm.type == 'tags'"
|
||||
)
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
.diff-status-wrapper
|
||||
span.key(
|
||||
translate="ACTIVITY.FIELDS.ASSIGNED_USERS"
|
||||
)
|
||||
span.diff(ng-if="vm.diff[0]") {{vm.diff[0]}}
|
||||
span.diff(ng-if="!vm.diff[0]" translate="ACTIVITY.VALUES.UNASSIGNED")
|
||||
|
||||
tg-svg(
|
||||
svg-icon="icon-arrow-right"
|
||||
)
|
||||
|
||||
span.diff(ng-if="vm.diff[1]") {{vm.diff[1]}}
|
||||
span.diff(ng-if="!vm.diff[1]" translate="ACTIVITY.VALUES.UNASSIGNED")
|
|
@ -0,0 +1,13 @@
|
|||
.diff-status-wrapper
|
||||
span.key(
|
||||
translate="ACTIVITY.FIELDS.DUE_DATE"
|
||||
)
|
||||
span.diff(ng-if="vm.diff[0]") {{vm.diff[0] | momentFormat:'DD MMM YYYY'}}
|
||||
span.diff(ng-if="!vm.diff[0]" translate="ACTIVITY.VALUES.NOT_SET")
|
||||
|
||||
tg-svg(
|
||||
svg-icon="icon-arrow-right"
|
||||
)
|
||||
|
||||
span.diff(ng-if="vm.diff[1]") {{vm.diff[1] | momentFormat:'DD MMM YYYY'}}
|
||||
span.diff(ng-if="!vm.diff[1]" translate="ACTIVITY.VALUES.NOT_SET")
|
|
@ -122,6 +122,7 @@ Resource = (urlsService, http, paginateResponseService) ->
|
|||
service.watchProject = (projectId, notifyLevel) ->
|
||||
data = {
|
||||
notify_level: notifyLevel
|
||||
live_notify_level: notifyLevel
|
||||
}
|
||||
url = urlsService.resolve("project-watch", projectId)
|
||||
return http.post(url, data)
|
||||
|
|
|
@ -31,12 +31,15 @@ class UserTimelineItemTitle
|
|||
'description_diff': 'COMMON.FIELDS.DESCRIPTION',
|
||||
'points': 'COMMON.FIELDS.POINTS',
|
||||
'assigned_to': 'COMMON.FIELDS.ASSIGNED_TO',
|
||||
'assigned_users': 'COMMON.FIELDS.ASSIGNED_USERS',
|
||||
'severity': 'ISSUES.FIELDS.SEVERITY',
|
||||
'priority': 'ISSUES.FIELDS.PRIORITY',
|
||||
'type': 'ISSUES.FIELDS.TYPE',
|
||||
'is_iocaine': 'TASK.FIELDS.IS_IOCAINE',
|
||||
'is_blocked': 'COMMON.FIELDS.IS_BLOCKED',
|
||||
'color': 'COMMON.FIELDS.COLOR'
|
||||
'color': 'COMMON.FIELDS.COLOR',
|
||||
'due_date': 'COMMON.FIELDS.DUE_DATE',
|
||||
'due_date_reason': 'COMMON.FIELDS.DUE_DATE_REASON',
|
||||
}
|
||||
|
||||
_params: {
|
||||
|
@ -69,6 +72,18 @@ class UserTimelineItemTitle
|
|||
if value == null && timeline.getIn(["data", "value_diff", "key"]) == 'assigned_to'
|
||||
value = @translate.instant('ACTIVITY.VALUES.UNASSIGNED')
|
||||
|
||||
# assigned_users to unasigned
|
||||
if value == null && timeline.getIn(["data", "value_diff", "key"]) == 'assigned_users'
|
||||
value = @translate.instant('ACTIVITY.VALUES.UNASSIGNED')
|
||||
|
||||
# due date
|
||||
else if timeline.getIn(["data", "value_diff", "key"]) == 'due_date'
|
||||
if value
|
||||
prettyDate = @translate.instant("COMMON.PICKERDATE.FORMAT")
|
||||
value = moment(value, "YYYY-MM-DD").format(prettyDate)
|
||||
else
|
||||
value = @translate.instant('ACTIVITY.VALUES.NOT_SET')
|
||||
|
||||
new_value = value
|
||||
else
|
||||
new_value = timeline.getIn(["data", "value_diff", "value"]).first().get(1)
|
||||
|
@ -170,7 +185,8 @@ class UserTimelineItemTitle
|
|||
|
||||
getTitle: (timeline, event, type) ->
|
||||
params = @._getParams(timeline, event, type)
|
||||
|
||||
# console.log(timeline)
|
||||
# console.log(event)
|
||||
paramsKeys = {}
|
||||
Object.keys(params).forEach (key) -> paramsKeys[key] = '{{' +key + '}}'
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ class UserTimelineService extends taiga.Service
|
|||
'status',
|
||||
'subject',
|
||||
'description_diff',
|
||||
'assigned_users',
|
||||
'assigned_to',
|
||||
'points',
|
||||
'severity',
|
||||
|
@ -48,7 +49,9 @@ class UserTimelineService extends taiga.Service
|
|||
'blocked',
|
||||
'moveInBacklog',
|
||||
'milestone',
|
||||
'color'
|
||||
'color',
|
||||
'due_date',
|
||||
'due_date_reason'
|
||||
]
|
||||
|
||||
_invalid: [
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
.assigned-title(ng-if="!isAssigned") {{ "COMMON.ASSIGNED_TO.NOT_ASSIGNED" | translate }}
|
||||
.assigned-title(ng-if="isAssigned") {{ "COMMON.FIELDS.ASSIGNED_TO" | translate }}
|
||||
|
||||
.tg-assigned-users
|
||||
.not-assigned-users(ng-if="!isAssigned")
|
||||
.user-avatar(ng-class="{'is-iocaine': isIocaine}")
|
||||
img(
|
||||
tg-avatar=""
|
||||
alt="{{ 'COMMON.ASSIGNED_TO.ASSIGN' | translate }}"
|
||||
)
|
||||
|
||||
//- .iocaine-symbol(ng-if="isIocaine" title="{{ 'TASK.TITLE_ACTION_IOCAINE' | translate }}")
|
||||
//- tg-svg(svg-icon="icon-iocaine")
|
||||
.assigned-to
|
||||
.assigned-users-options(ng-if="isEditable")
|
||||
a(
|
||||
href=""
|
||||
title="{{ 'COMMON.ASSIGNED_TO.TITLE_ACTION_EDIT_ASSIGNMENT'|translate }}"
|
||||
class="user-assigned"
|
||||
ng-class="{editable: isEditable}"
|
||||
ng-click="openAssignedUsers()"
|
||||
)
|
||||
span.assigned-name {{ "COMMON.ASSIGNED_TO.ASSIGN" | translate }}
|
||||
|
||||
span(ng-if="!isAssigned")
|
||||
span(translate="COMMON.OR")
|
||||
|
|
||||
a.assign-to-me(
|
||||
href="#"
|
||||
title="{{'COMMON.ASSIGNED_TO.SELF' | translate}}"
|
||||
ng-click="assignToMe()"
|
||||
)
|
||||
span {{ "COMMON.ASSIGNED_TO.SELF" | translate }}
|
||||
|
||||
.user-list-single(ng-repeat="assignedUser in assignedUsers")
|
||||
.user-list-avatar
|
||||
img(
|
||||
tg-avatar="assignedUser"
|
||||
alt="{{assignedUser.full_name_display}}"
|
||||
)
|
||||
.user-list-name.assigned-users-options
|
||||
a(
|
||||
href=""
|
||||
title="{{ 'COMMON.ASSIGNED_TO.TITLE_ACTION_EDIT_ASSIGNMENT'|translate }}"
|
||||
class!="user-assigned <% if (isEditable) { %>editable<% }; %>"
|
||||
)
|
||||
span.assigned-name {{assignedUser.full_name_display}}
|
||||
tg-svg.remove-user(
|
||||
ng-if="isEditable",
|
||||
data-assigned-user-id="{{assignedUser.id}}"
|
||||
svg-icon="icon-close",
|
||||
title="{{'COMMON.ASSIGNED_TO.DELETE_ASSIGNMENT' | translate}}"
|
||||
)
|
||||
|
||||
|
||||
.tg-add-assigned(ng-if="isAssigned && isEditable")
|
||||
tg-svg.add-assigned(
|
||||
ng-if="isEditable",
|
||||
data-assigned-user-id="{{assignedUser.id}}",
|
||||
ng-click="openAssignedUsers()",
|
||||
svg-icon="icon-add",
|
||||
title="{{'COMMON.ASSIGNED_USERS.ADD_ASSIGNED' | translate}}"
|
||||
)
|
||||
span {{ "COMMON.ASSIGNED_USERS.ADD_ASSIGNED" | translate }}
|
|
@ -0,0 +1,48 @@
|
|||
//- <% if (selected) { %>
|
||||
<% _.each(selected, function(user) { %>
|
||||
.user-list-multiple.is-active(data-user-id!="<%- user.id %>")
|
||||
.user-list-avatar
|
||||
a(
|
||||
href=""
|
||||
title="{{'COMMON.ASSIGNED_TO' | translate}}"
|
||||
)
|
||||
img(
|
||||
style!="background: <%- user.avatar.bg %>"
|
||||
src!="<%- user.avatar.url %>"
|
||||
)
|
||||
a.user-list-name(
|
||||
href=""
|
||||
title!="<%- user.full_name_display %>"
|
||||
ng-non-bindable
|
||||
)
|
||||
| <%-user.full_name_display %>
|
||||
tg-svg.remove-assigned-to(
|
||||
svg-icon="icon-close",
|
||||
svg-title-translate="COMMON.ASSIGNED_TO.REMOVE_ASSIGNED",
|
||||
data-user-id!="<%- user.id %>"
|
||||
)
|
||||
<% }) %>
|
||||
|
||||
<% _.each(users, function(user) { %>
|
||||
.user-list-single(data-user-id!="<%- user.id %>")
|
||||
.user-list-avatar
|
||||
a(
|
||||
href="#"
|
||||
title="{{'COMMON.ASSIGNED_TO.TITLE' | translate}}"
|
||||
)
|
||||
img(
|
||||
style!="background: <%- user.avatar.bg %>"
|
||||
src!="<%- user.avatar.url %>"
|
||||
)
|
||||
a.user-list-name(
|
||||
href=""
|
||||
title!="<%- user.full_name_display %>"
|
||||
ng-non-bindable
|
||||
)
|
||||
| <%- user.full_name_display %>
|
||||
<% }) %>
|
||||
|
||||
<% if (showMore) { %>
|
||||
.more-watchers
|
||||
span(translate="COMMON.ASSIGNED_TO.TOO_MANY")
|
||||
<% } %>
|
|
@ -0,0 +1,8 @@
|
|||
tg-lightbox-close
|
||||
|
||||
div.form
|
||||
h2.title(translate="COMMON.ASSIGNED_USERS.ADD")
|
||||
fieldset
|
||||
input(type="text", data-maxlength="500", placeholder="{{'LIGHTBOX.ASSIGNED_TO.SEARCH' | translate}}", ng-model="usersSearch")
|
||||
div.assigned-to-list
|
||||
//- The content of this is rendered by directive
|
|
@ -0,0 +1,44 @@
|
|||
tg-lightbox-close
|
||||
|
||||
form
|
||||
h2.title(translate="LIGHTBOX.SET_DUE_DATE.TITLE")
|
||||
|
||||
fieldset.date
|
||||
input.due-date.no-focus(
|
||||
type="text"
|
||||
name="due_date"
|
||||
picker-value="{{ new_due_date }}"
|
||||
data-required="true"
|
||||
tg-date-selector
|
||||
placeholder="{{'LIGHTBOX.SET_DUE_DATE.PLACEHOLDER_DUE_DATE' | translate}}"
|
||||
)
|
||||
|
||||
ul.due-date-suggestions
|
||||
li.suggestion.clickable(data-quantity=1, data-unit="weeks")
|
||||
span {{ 'LIGHTBOX.SET_DUE_DATE.SUGGESTIONS.IN_ONE_WEEK' | translate }}
|
||||
li.suggestion.clickable(data-quantity=2, data-unit="weeks")
|
||||
span {{ 'LIGHTBOX.SET_DUE_DATE.SUGGESTIONS.IN_TWO_WEEKS' | translate }}
|
||||
li.suggestion.clickable(data-quantity=1, data-unit="months")
|
||||
span {{ 'LIGHTBOX.SET_DUE_DATE.SUGGESTIONS.IN_ONE_MONTH' | translate }}
|
||||
li.suggestion.clickable(data-quantity=3, data-unit="months")
|
||||
span {{ 'LIGHTBOX.SET_DUE_DATE.SUGGESTIONS.IN_THREE_MONTHS' | translate }}
|
||||
|
||||
fieldset.reason
|
||||
span {{ 'LIGHTBOX.SET_DUE_DATE.REASON_FOR_DUE_DATE' | translate }}
|
||||
textarea.due-date-reason.no-focus(
|
||||
name="due_date_reason"
|
||||
ng-attr-placeholder="{{'LIGHTBOX.SET_DUE_DATE.PLACEHOLDER_REASON_FOR_DUE_DATE' | translate}}"
|
||||
ng-model="object.due_date_reason"
|
||||
)
|
||||
|
||||
button.button-green.submit-button(
|
||||
type="submit"
|
||||
title="{{'COMMON.SAVE' | translate}}"
|
||||
translate="COMMON.SAVE"
|
||||
)
|
||||
|
||||
a.delete-due-date(
|
||||
href=""
|
||||
title="{{'LIGHTBOX.SET_DUE_DATE.TITLE_ACTION_DELETE_DUE_DATE' | translate}}"
|
||||
)
|
||||
tg-svg(svg-icon="icon-trash")
|
|
@ -27,6 +27,11 @@
|
|||
)
|
||||
span(tg-bo-ref="us.ref")
|
||||
span(ng-bind-html="us.subject | emojify")
|
||||
tg-due-date(
|
||||
due-date="us.due_date"
|
||||
due-date-status="us.due_date_status"
|
||||
ng-if="us.due_date"
|
||||
)
|
||||
tg-belong-to-epics(
|
||||
format="pill"
|
||||
ng-if="us.epics"
|
||||
|
|
|
@ -54,6 +54,11 @@ section.issues-table.basic-table(ng-class="{empty: !issues.length}")
|
|||
title="{{issue.blocked_note}}"
|
||||
) {{'ISSUES.TABLE.BLOCKED' | translate}}
|
||||
span(ng-bind-html="issue.subject | emojify")
|
||||
tg-due-date(
|
||||
due-date="issue.due_date"
|
||||
due-date-status="issue.due_date_status"
|
||||
ng-if="issue.due_date"
|
||||
)
|
||||
|
||||
|
||||
div.issue-field(tg-issue-status-inline-edition="issue")
|
||||
|
|
|
@ -80,7 +80,8 @@ div.kanban-table(
|
|||
tg-bind-scope,
|
||||
on-toggle-fold="ctrl.toggleFold(id)"
|
||||
on-click-edit="ctrl.editUs(id)"
|
||||
on-click-assigned-to="ctrl.changeUsAssignedTo(id)"
|
||||
on-click-delete="ctrl.deleteUs(id)"
|
||||
on-click-assigned-to="ctrl.changeUsAssignedUsers(id)"
|
||||
project="project"
|
||||
item="us"
|
||||
zoom="ctrl.zoom"
|
||||
|
|
|
@ -82,6 +82,7 @@ div.taskboard-table(
|
|||
tg-bind-scope,
|
||||
on-toggle-fold="ctrl.toggleFold(id)"
|
||||
on-click-edit="ctrl.editTask(id)"
|
||||
on-click-delete="ctrl.deleteTask(id)"
|
||||
on-click-assigned-to="ctrl.changeTaskAssignedTo(id)"
|
||||
project="project"
|
||||
item="task"
|
||||
|
@ -127,6 +128,7 @@ div.taskboard-table(
|
|||
tg-class-permission="{'readonly': '!modify_task'}"
|
||||
on-toggle-fold="ctrl.toggleFold(id)"
|
||||
on-click-edit="ctrl.editTask(id)"
|
||||
on-click-delete="ctrl.deleteTask(id)"
|
||||
on-click-assigned-to="ctrl.changeTaskAssignedTo(id)"
|
||||
project="project"
|
||||
item="task"
|
||||
|
|
|
@ -10,6 +10,9 @@ section.admin-menu
|
|||
li#usersettingsmenu-mail-notifications
|
||||
a(href="", tg-nav="user-settings-mail-notifications", title="{{ 'USER_SETTINGS.MENU.EMAIL_NOTIFICATIONS' | translate }}")
|
||||
span.title(translate="USER_SETTINGS.MENU.EMAIL_NOTIFICATIONS")
|
||||
li#usersettingsmenu-live-notifications
|
||||
a(href="", tg-nav="user-settings-live-notifications", title="{{ 'USER_SETTINGS.MENU.DESKTOP_NOTIFICATIONS' | translate }}")
|
||||
span.title(translate="USER_SETTINGS.MENU.DESKTOP_NOTIFICATIONS")
|
||||
li#usersettings-contrib(ng-repeat="plugin in userSettingsPlugins")
|
||||
a(
|
||||
href=""
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
section.policy-table
|
||||
div.policy-table-header
|
||||
div.policy-table-row
|
||||
div.policy-table-project
|
||||
span(translate="USER_SETTINGS.NOTIFICATIONS.COLUMN_PROJECT")
|
||||
div.policy-table-all
|
||||
span(translate="USER_SETTINGS.NOTIFICATIONS.COLUMN_RECEIVE_ALL")
|
||||
div.policy-table-involved
|
||||
span(translate="USER_SETTINGS.NOTIFICATIONS.COLUMN_ONLY_INVOLVED")
|
||||
div.policy-table-none
|
||||
span(translate="USER_SETTINGS.NOTIFICATIONS.COLUMN_NO_NOTIFICATIONS")
|
||||
div.policy-table-body(tg-user-live-notifications-list, ng-model="notifyPolicies")
|
|
@ -113,6 +113,14 @@ div.wrapper(
|
|||
)
|
||||
|
||||
section.ticket-detail-settings
|
||||
tg-due-date(
|
||||
tg-check-permission="modify_issue"
|
||||
due-date="issue.due_date"
|
||||
due-date-status="issue.due_date_status"
|
||||
is-closed="issue.is_closed"
|
||||
item="issue"
|
||||
format="button"
|
||||
)
|
||||
tg-promote-issue-to-us-button(
|
||||
tg-check-permission="add_us",
|
||||
ng-model="issue"
|
||||
|
|
|
@ -46,4 +46,4 @@ div.wrapper(
|
|||
div.lightbox.lightbox-generic-bulk(tg-lb-create-bulk-userstories)
|
||||
include ../includes/modules/lightbox-us-bulk
|
||||
|
||||
div.lightbox.lightbox-select-user(tg-lb-assignedto)
|
||||
div.lightbox.lightbox-select-user(tg-lb-assigned-users)
|
||||
|
|
|
@ -3,7 +3,11 @@
|
|||
tg-nav="project-tasks-detail:project=project.slug,ref=task.ref")
|
||||
span #<%- task.ref %>
|
||||
span(ng-non-bindable) <%= emojify(task.subject) %>
|
||||
|
||||
tg-due-date(
|
||||
due-date="task.due_date"
|
||||
due-date-status="task.due_date_status"
|
||||
ng-if="task.due_date"
|
||||
)
|
||||
.task-settings
|
||||
<% if(perms.modify_task) { %>
|
||||
a.edit-task(
|
||||
|
|
|
@ -102,6 +102,14 @@ div.wrapper(
|
|||
)
|
||||
|
||||
section.ticket-detail-settings
|
||||
tg-due-date(
|
||||
tg-check-permission="modify_task"
|
||||
due-date="task.due_date"
|
||||
due-date-status="task.due_date_status"
|
||||
is-closed="task.is_closed"
|
||||
item="task"
|
||||
format="button"
|
||||
)
|
||||
tg-task-is-iocaine-button(ng-model="task")
|
||||
tg-block-button(tg-check-permission="modify_task", ng-model="task")
|
||||
tg-delete-button(
|
||||
|
|
|
@ -95,8 +95,8 @@ div.wrapper(
|
|||
|
||||
tg-us-estimation.ticket-estimation(ng-model="us")
|
||||
|
||||
section.ticket-assigned-to(
|
||||
tg-assigned-to
|
||||
section.ticket-assigned-users(
|
||||
tg-assigned-users
|
||||
ng-model="us"
|
||||
required-perm="modify_us"
|
||||
)
|
||||
|
@ -127,6 +127,14 @@ div.wrapper(
|
|||
) {{'US.TRIBE.PUBLISH_INFO' | translate}}
|
||||
|
||||
section.ticket-detail-settings
|
||||
tg-due-date(
|
||||
tg-check-permission="modify_us"
|
||||
due-date="us.due_date"
|
||||
due-date-status="us.due_date_status"
|
||||
is-closed="us.is_closed"
|
||||
item="us"
|
||||
format="button"
|
||||
)
|
||||
tg-us-team-requirement-button(ng-model="us")
|
||||
tg-us-client-requirement-button(ng-model="us")
|
||||
tg-block-button(
|
||||
|
@ -146,4 +154,5 @@ div.wrapper(
|
|||
ng-model="us"
|
||||
)
|
||||
div.lightbox.lightbox-select-user(tg-lb-assignedto)
|
||||
div.lightbox.lightbox-select-user(tg-lb-assigned-users)
|
||||
div.lightbox.lightbox-select-user(tg-lb-watchers)
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
doctype html
|
||||
|
||||
div.wrapper(
|
||||
tg-user-live-notifications
|
||||
ng-controller="UserLiveNotificationsController as ctrl",
|
||||
ng-init="section='live-notifications'"
|
||||
)
|
||||
|
||||
sidebar.menu-secondary.sidebar.settings-nav(tg-user-settings-navigation="live-notifications")
|
||||
include ../includes/modules/user-settings-menu
|
||||
|
||||
section.main.admin-common
|
||||
header
|
||||
h1
|
||||
span.green {{sectionName | translate}}
|
||||
|
||||
p.total(translate="NOTIFICATION.DESKTOP")
|
||||
|
||||
include ../includes/modules/user-settings/live-notifications-table
|
|
@ -12,6 +12,19 @@
|
|||
border: 0;
|
||||
}
|
||||
}
|
||||
.user-list-multiple {
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
border-bottom: 1px solid $whitish;
|
||||
display: flex;
|
||||
padding: .25rem 0;
|
||||
vertical-align: middle;
|
||||
|
||||
&:last-child {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
.user-list-avatar {
|
||||
flex-basis: 3rem;
|
||||
margin-right: .25rem;
|
||||
|
@ -61,6 +74,37 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-list-multiple {
|
||||
&:hover,
|
||||
&.selected {
|
||||
background: rgba(lighten($primary-light, 30%), .3);
|
||||
cursor: pointer;
|
||||
}
|
||||
&:hover {
|
||||
transition: background .3s linear;
|
||||
transition-delay: .2s;
|
||||
}
|
||||
&.is-active {
|
||||
background: rgba(lighten($primary-light, 30%), .3);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: background .3s linear;
|
||||
transition-delay: .1s;
|
||||
}
|
||||
.remove-assigned-to {
|
||||
display: block;
|
||||
fill: $grayer;
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 1.5rem;
|
||||
transition: all .2s ease-in;
|
||||
&:hover {
|
||||
fill: $red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ticket-watchers {
|
||||
|
@ -92,3 +136,35 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ticket-assigned-users {
|
||||
@include user-list;
|
||||
margin-top: 1rem;
|
||||
.user-list-single {
|
||||
flex-grow: 1;
|
||||
&:hover {
|
||||
.remove-user {
|
||||
opacity: 1;
|
||||
transition: opacity .2s ease-in;
|
||||
}
|
||||
}
|
||||
}
|
||||
.user-list-name {
|
||||
@include font-type(text);
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
.remove-user {
|
||||
cursor: pointer;
|
||||
fill: currentColor;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
right: .5rem;
|
||||
top: 0;
|
||||
transition: all .2s ease-in;
|
||||
&:hover {
|
||||
fill: $red;
|
||||
transition: color .3s ease-in;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
.ticket-assigned-users {
|
||||
align-items: center;
|
||||
border-bottom: 1px solid $gray-light;
|
||||
border-top: 1px solid $gray-light;
|
||||
margin-bottom: 1rem;
|
||||
padding: .5rem 0;
|
||||
position: relative;
|
||||
|
||||
.loading-spinner {
|
||||
@include loading-spinner;
|
||||
margin: 1rem auto;
|
||||
max-height: 2rem;
|
||||
max-width: 2rem;
|
||||
}
|
||||
|
||||
.assigned-title {
|
||||
@include font-size(small);
|
||||
@include font-type(light);
|
||||
color: $gray;
|
||||
display: block;
|
||||
margin: .2rem 0 .25rem;
|
||||
}
|
||||
|
||||
.tg-assigned-users {
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tg-add-assigned {
|
||||
margin-top: .25rem;
|
||||
|
||||
.add-assigned {
|
||||
fill: $gray;
|
||||
opacity: 1;
|
||||
right: .5rem;
|
||||
top: 2rem;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
fill: $red;
|
||||
transition: fill .2s;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
@include font-size(small);
|
||||
@include font-type(light);
|
||||
color: $gray;
|
||||
margin: .2rem .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.assigned-users-options {
|
||||
align-content: center;
|
||||
display: flex;
|
||||
a {
|
||||
margin-right: .2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.user-assigned,
|
||||
.assign-to-me {
|
||||
color: $primary;
|
||||
&.editable {
|
||||
color: $primary;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.not-assigned-users {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
.assigned-title {
|
||||
@include font-size(small);
|
||||
@include font-type(light);
|
||||
color: $gray;
|
||||
display: block;
|
||||
margin: .2rem 0 .25rem;
|
||||
}
|
||||
.assigned-to {
|
||||
flex-grow: 1;
|
||||
margin-left: .5rem;
|
||||
}
|
||||
|
||||
.assigned-to-options {
|
||||
align-content: center;
|
||||
display: flex;
|
||||
a {
|
||||
margin-right: .2rem;
|
||||
}
|
||||
}
|
||||
.user-assigned,
|
||||
.assign-to-me {
|
||||
color: $primary;
|
||||
cursor: default;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.icon {
|
||||
fill: currentColor;
|
||||
height: .75rem;
|
||||
width: .75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
flex-basis: 3rem;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
&.is-iocaine {
|
||||
img {
|
||||
filter: hue-rotate(150deg) saturate(200%);
|
||||
}
|
||||
}
|
||||
.iocaine-symbol {
|
||||
left: -.5rem;
|
||||
position: absolute;
|
||||
top: -.75rem;
|
||||
z-index: 9;
|
||||
svg {
|
||||
background: $grayer;
|
||||
border-radius: .25rem;
|
||||
fill: $white;
|
||||
min-height: 1.75rem;
|
||||
min-width: 1.75rem;
|
||||
padding: .25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -540,3 +540,63 @@
|
|||
width: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
.lightbox-set-due-date {
|
||||
z-index: 9999;
|
||||
form {
|
||||
flex-basis: 600px;
|
||||
flex-flow: 0;
|
||||
max-width: 600px;
|
||||
}
|
||||
.date {
|
||||
margin: 2rem 0 1rem;
|
||||
}
|
||||
.reason textarea {
|
||||
margin-top: .5rem;
|
||||
}
|
||||
.due-date-suggestions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
margin: 1rem 0 .5rem;
|
||||
}
|
||||
.suggestion {
|
||||
background: rgba($gray-lighter, .2);
|
||||
color: $gray-lighter;
|
||||
justify-content: flex-start;
|
||||
margin: 0 .5rem .5rem;
|
||||
min-height: 2rem;
|
||||
padding: .5rem .75rem;
|
||||
position: relative;
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
&:nth-child(4n + 4) {
|
||||
margin-right: 0;
|
||||
}
|
||||
&.clickable {
|
||||
&:hover,
|
||||
&.active {
|
||||
background: rgba($primary-light, .9);
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
.delete-due-date {
|
||||
@include font-size(small);
|
||||
color: $gray;
|
||||
float: right;
|
||||
margin: 1rem .25rem 0 0;
|
||||
transition: color .3s linear;
|
||||
.icon {
|
||||
fill: currentColor;
|
||||
}
|
||||
&:hover {
|
||||
color: $red;
|
||||
transition: color .3s linear;
|
||||
.icon {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -244,6 +244,10 @@
|
|||
class="path1"
|
||||
d="M202.24-0.179v129.093l-202.24-0.11v895.375h729.6v-234.419h-64v170.419h-601.6v-644.385h601.6v287.086h64v-474.076h-64v0.11h-138.24v-129.093h-325.12zM266.24 63.821h197.12v96.44h0.32v32.653h201.92v58.88h-601.6v-58.88h202.24v-129.093zM129.165 393.242v64h468.838v-64h-468.836zM522.76 515.302l-181.020 181.018 181.020 181.020 45.256-45.253-103.759-103.767h559.744v-64h-559.749l103.764-103.764-45.253-45.256zM129.165 541.722v64h228.086v-64h-228.083zM129.165 690.202v64h150.246v-64h-150.246zM129.165 833.562v64h258.854v-64h-258.854z"></path>
|
||||
</symbol>
|
||||
<symbol id="icon-clock" viewBox="0 0 400 400.00001">
|
||||
<title>clock</title>
|
||||
<path d="M200 0a200 200 0 1 0 0 400 200 200 0 0 0 0-400zm0 25a175 175 0 1 1 0 350 175 175 0 0 1 0-350zm13 53h-25v113l-87 1v25l112-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="icon-document" viewBox="0 0 1024 1024">
|
||||
<title>document</title>
|
||||
<path
|
||||
|
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
@ -8,6 +8,7 @@ $blackish: #212121;
|
|||
$grayer: #212121;
|
||||
$gray: #757575;
|
||||
$gray-light: #757575;
|
||||
$gray-lighter: #B8B8B8;
|
||||
$whitish: #c1c1c1;
|
||||
$white: #fff;
|
||||
|
||||
|
@ -19,9 +20,11 @@ $primary-dark: #000;
|
|||
// Mass white
|
||||
$mass-white: #f5f5f5;
|
||||
|
||||
//Warning colors
|
||||
//Status colors
|
||||
$red-light: #ff0062;
|
||||
$red: #ff2400;
|
||||
$my-sin: #fcaf3e;
|
||||
$yellow-green: #9dce0a;
|
||||
|
||||
//Card color
|
||||
$card: #F0EFD1;
|
||||
|
|
|
@ -8,6 +8,7 @@ $blackish: #212121;
|
|||
$grayer: #424242;
|
||||
$gray: #757575;
|
||||
$gray-light: #BDBDBD;
|
||||
$gray-lighter: #B8B8B8;
|
||||
$whitish: #EEEEEE;
|
||||
$white: #fff;
|
||||
|
||||
|
@ -19,9 +20,11 @@ $primary-light: #8c9eff;
|
|||
$primary: #3f51b5;
|
||||
$primary-dark: #1a237e;
|
||||
|
||||
//Warning colors
|
||||
// Status colors
|
||||
$red-light: #ff5252;
|
||||
$red: #f44336;
|
||||
$my-sin: #fcaf3e;
|
||||
$yellow-green: #9dce0a;
|
||||
|
||||
//Card color
|
||||
$card: #fff8e4;
|
||||
|
|
|
@ -8,6 +8,7 @@ $blackish: #050505;
|
|||
$grayer: #444;
|
||||
$gray: #555;
|
||||
$gray-light: #767676;
|
||||
$gray-lighter: #B8B8B8;
|
||||
$whitish: #e4e3e3;
|
||||
$white: #fff;
|
||||
|
||||
|
@ -19,9 +20,11 @@ $primary-light: #9dce0a;
|
|||
$primary: #5b8200;
|
||||
$primary-dark: #879b89;
|
||||
|
||||
//Warning colors
|
||||
//Status colors
|
||||
$red-light: #ff8282;
|
||||
$red: #f00;
|
||||
$my-sin: #fcaf3e;
|
||||
$yellow-green: #9dce0a;
|
||||
|
||||
//Card color
|
||||
$card: #fff8e4;
|
||||
|
|
Loading…
Reference in New Issue