Merge branch 'master' into stable (v. 1.2.0)
35
CHANGELOG.md
|
@ -1,22 +1,24 @@
|
|||
# Changelog #
|
||||
|
||||
## 1.2.0 (Unreleased)
|
||||
## 1.2.0 Picea obovata (2014-11-04)
|
||||
|
||||
### Features
|
||||
|
||||
- US/Task/Issue visualization and edition refactor. Now only one view for both.
|
||||
- Multiple User stories Drag & Drop in the backlog.
|
||||
- Added beta ribbon.
|
||||
- Add visual difference to closed USs in backlog panel.
|
||||
- Show crerated date of attachments in the hover of the filename.
|
||||
- Show info about maximun size allowed for avatar and attachments files.
|
||||
- Add beta ribbon.
|
||||
- Support for custom text when inviting users.
|
||||
|
||||
### Misc
|
||||
- Lots of small and not so small bugfixes
|
||||
|
||||
- TAIGA loves Movember! The logo has a beautiful mustache this month.
|
||||
- Lots of small and not so small bugfixes.
|
||||
|
||||
|
||||
## 1.1.0 (2014-10-13)
|
||||
|
||||
### Misc ###
|
||||
|
||||
- Fix bug related to stange behavior of browser autofill and angularjs on login page.
|
||||
- Fix bug on userstories ordering on sprints.
|
||||
- Fix bug of projects list visualization on project nav on first page loading.
|
||||
## 1.1.0 Alnus maximowiczii (2014-10-13)
|
||||
|
||||
### Features ###
|
||||
|
||||
|
@ -24,14 +26,21 @@
|
|||
- Changed configuration format from coffeescript file to json.
|
||||
- Add builtin analytics support.
|
||||
|
||||
## 1.0.0 (2014-10-07)
|
||||
|
||||
### Misc ###
|
||||
|
||||
- Lots of small and not so small bugfixes
|
||||
- Fix bug related to stange behavior of browser autofill and angularjs on login page.
|
||||
- Fix bug on userstories ordering on sprints.
|
||||
- Fix bug of projects list visualization on project nav on first page loading.
|
||||
|
||||
|
||||
## 1.0.0 (2014-10-07)
|
||||
|
||||
### Features ###
|
||||
|
||||
- Redesign for taskboard and backlog summaries
|
||||
- Allow feedback for users from the platform
|
||||
- Real time changes for backlog, taskboard, kanban and issues
|
||||
|
||||
### Misc ###
|
||||
|
||||
- Lots of small and not so small bugfixes
|
||||
|
|
|
@ -52,30 +52,22 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
|||
# User stories
|
||||
$routeProvider.when("/project/:pslug/us/:usref",
|
||||
{templateUrl: "/partials/us-detail.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||
$routeProvider.when("/project/:pslug/us/:usref/edit",
|
||||
{templateUrl: "/partials/us-detail-edit.html"})
|
||||
|
||||
# Tasks
|
||||
$routeProvider.when("/project/:pslug/task/:taskref",
|
||||
{templateUrl: "/partials/task-detail.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||
$routeProvider.when("/project/:pslug/task/:taskref/edit",
|
||||
{templateUrl: "/partials/task-detail-edit.html"})
|
||||
|
||||
# Wiki
|
||||
$routeProvider.when("/project/:pslug/wiki",
|
||||
{redirectTo: (params) -> "/project/#{params.pslug}/wiki/home"}, )
|
||||
$routeProvider.when("/project/:pslug/wiki/:slug",
|
||||
{templateUrl: "/partials/wiki.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||
$routeProvider.when("/project/:pslug/wiki/:slug/edit",
|
||||
{templateUrl: "/partials/wiki-edit.html"})
|
||||
|
||||
# Issues
|
||||
$routeProvider.when("/project/:pslug/issues",
|
||||
{templateUrl: "/partials/issues.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||
$routeProvider.when("/project/:pslug/issue/:issueref",
|
||||
{templateUrl: "/partials/issues-detail.html"})
|
||||
$routeProvider.when("/project/:pslug/issue/:issueref/edit",
|
||||
{templateUrl: "/partials/issues-detail-edit.html"})
|
||||
{templateUrl: "/partials/issues-detail.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||
|
||||
# Admin
|
||||
$routeProvider.when("/project/:pslug/admin/project-profile/details",
|
||||
|
@ -136,6 +128,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
|||
{templateUrl: "/partials/error.html"})
|
||||
$routeProvider.when("/not-found",
|
||||
{templateUrl: "/partials/not-found.html"})
|
||||
$routeProvider.when("/permission-denied",
|
||||
{templateUrl: "/partials/permission-denied.html"})
|
||||
|
||||
$routeProvider.otherwise({redirectTo: '/not-found'})
|
||||
$locationProvider.html5Mode(true)
|
||||
|
|
|
@ -22,6 +22,16 @@
|
|||
class TaigaBase
|
||||
class TaigaService extends TaigaBase
|
||||
class TaigaController extends TaigaBase
|
||||
onInitialDataError: (xhr) =>
|
||||
if xhr
|
||||
if xhr.status == 404
|
||||
@location.path(@navUrls.resolve("not-found"))
|
||||
@location.replace()
|
||||
else if xhr.status == 403
|
||||
@location.path(@navUrls.resolve("permission-denied"))
|
||||
@location.replace()
|
||||
|
||||
return @q.reject(xhr)
|
||||
|
||||
@.taiga.Base = TaigaBase
|
||||
@.taiga.Service = TaigaService
|
||||
|
|
|
@ -24,13 +24,19 @@ debounce = @.taiga.debounce
|
|||
|
||||
module = angular.module("taigaKanban")
|
||||
|
||||
MAX_MEMBERSHIP_FIELDSETS = 6
|
||||
MAX_MEMBERSHIP_FIELDSETS = 4
|
||||
|
||||
#############################################################################
|
||||
## Create Members Lightbox Directive
|
||||
#############################################################################
|
||||
|
||||
CreateMembersDirective = ($rs, $rootScope, $confirm, lightboxService) ->
|
||||
extraTextTemplate = """
|
||||
<fieldset class="extra-text">
|
||||
<textarea placeholder="(Optional) Add a personalized text to the invitation. Tell something lovely to your new members ;-)"></textarea>
|
||||
</fieldset>
|
||||
"""
|
||||
|
||||
template = _.template("""
|
||||
<div class="add-member-wrapper">
|
||||
<fieldset>
|
||||
|
@ -53,11 +59,14 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, lightboxService) ->
|
|||
return template(ctx)
|
||||
|
||||
resetForm = ->
|
||||
$el.find("form > .add-member-wrapper").remove()
|
||||
$el.find("form textarea").remove("")
|
||||
$el.find("form .add-member-wrapper").remove()
|
||||
|
||||
invitations = $el.find(".add-member-forms")
|
||||
invitations.html(extraTextTemplate)
|
||||
|
||||
title = $el.find("h2")
|
||||
fieldSet = createFieldSet()
|
||||
title.after(fieldSet)
|
||||
invitations.prepend(fieldSet)
|
||||
|
||||
$scope.$on "membersform:new", ->
|
||||
resetForm()
|
||||
|
@ -91,7 +100,7 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, lightboxService) ->
|
|||
fieldSet.after(newFieldSet)
|
||||
|
||||
if $el.find(".add-member-wrapper").length == MAX_MEMBERSHIP_FIELDSETS
|
||||
$el.find("fieldset:last > a").removeClass("icon-plus add-fieldset")
|
||||
$el.find(".add-member-wrapper fieldset:last > a").removeClass("icon-plus add-fieldset")
|
||||
.addClass("icon-delete delete-fieldset")
|
||||
|
||||
$el.on "click", ".button-green", debounce 2000, (event) ->
|
||||
|
@ -112,12 +121,10 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, lightboxService) ->
|
|||
#checksley find new fields
|
||||
form.destroy()
|
||||
form.initialize()
|
||||
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
memberWrappers = $el.find("form > .add-member-wrapper")
|
||||
|
||||
memberWrappers = $el.find("form .add-member-wrapper")
|
||||
memberWrappers = _.filter memberWrappers, (mw) ->
|
||||
angular.element(mw).find("input").hasClass('checksley-ok')
|
||||
|
||||
|
@ -132,7 +139,9 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, lightboxService) ->
|
|||
}
|
||||
|
||||
if invitations.length
|
||||
$rs.memberships.bulkCreateMemberships($scope.project.id, invitations).then(onSuccess, onError)
|
||||
invitation_extra_text = $el.find("form textarea").val()
|
||||
|
||||
$rs.memberships.bulkCreateMemberships($scope.project.id, invitations, invitation_extra_text).then(onSuccess, onError)
|
||||
|
||||
return {link: link}
|
||||
|
||||
|
|
|
@ -58,11 +58,7 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai
|
|||
promise.then =>
|
||||
@appTitle.set("Membership - " + @scope.project.name)
|
||||
|
||||
promise.then null, (xhr) =>
|
||||
if xhr and xhr.status == 404
|
||||
@location.path(@navUrls.resolve("not-found"))
|
||||
@location.replace()
|
||||
return @q.reject(xhr)
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
@scope.$on "membersform:new:success", =>
|
||||
@.loadMembers()
|
||||
|
@ -119,11 +115,11 @@ paginatorTemplate = """
|
|||
<% } %>
|
||||
|
||||
<% _.each(pages, function(item) { %>
|
||||
<li class="<%= item.classes %>">
|
||||
<li class="<%- item.classes %>">
|
||||
<% if (item.type === "page") { %>
|
||||
<a href="" data-pagenum="<%= item.num %>"><%= item.num %></a>
|
||||
<a href="" data-pagenum="<%- item.num %>"><%- item.num %></a>
|
||||
<% } else if (item.type === "page-active") { %>
|
||||
<span class="active"><%= item.num %></span>
|
||||
<span class="active"><%- item.num %></span>
|
||||
<% } else { %>
|
||||
<span>...</span>
|
||||
<% } %>
|
||||
|
@ -237,7 +233,7 @@ module.directive("tgMemberships", MembershipsDirective)
|
|||
MembershipsRowAvatarDirective = ($log) ->
|
||||
template = _.template("""
|
||||
<figure class="avatar">
|
||||
<img src="<%= imgurl %>" alt="<%- full_name %>">
|
||||
<img src="<%- imgurl %>" alt="<%- full_name %>">
|
||||
<figcaption>
|
||||
<span class="name"><%- full_name %></span>
|
||||
<span class="email"><%- email %></span>
|
||||
|
@ -432,18 +428,18 @@ MembershipsRowActionsDirective = ($log, $repo, $rs, $confirm) ->
|
|||
event.preventDefault()
|
||||
|
||||
title = "Delete member" # TODO: i18n
|
||||
subtitle = if member.user then member.full_name else "the invitation to #{member.email}" # TODO: i18n
|
||||
message = if member.user then member.full_name else "the invitation to #{member.email}" # TODO: i18n
|
||||
|
||||
$confirm.ask(title, subtitle).then (finish) ->
|
||||
$confirm.askOnDelete(title, message).then (finish) ->
|
||||
onSuccess = ->
|
||||
finish()
|
||||
$ctrl.loadMembers()
|
||||
$confirm.notify("success", null, "We've deleted #{subtitle}.") # TODO: i18n
|
||||
$confirm.notify("success", null, "We've deleted #{message}.") # TODO: i18n
|
||||
|
||||
onError = ->
|
||||
finish(false)
|
||||
# TODO: i18in
|
||||
$confirm.notify("error", null, "We have not been able to delete #{subtitle}.")
|
||||
$confirm.notify("error", null, "We have not been able to delete #{message}.")
|
||||
|
||||
$repo.remove(member).then(onSuccess, onError)
|
||||
|
||||
|
|
|
@ -57,11 +57,7 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
promise.then =>
|
||||
@appTitle.set("Project profile - " + @scope.sectionName + " - " + @scope.project.name)
|
||||
|
||||
promise.then null, (xhr) =>
|
||||
if xhr and xhr.status == 404
|
||||
@location.path(@navUrls.resolve("not-found"))
|
||||
@location.replace()
|
||||
return @q.reject(xhr)
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
@scope.$on "project:loaded", =>
|
||||
@appTitle.set("Project profile - " + @scope.sectionName + " - " + @scope.project.name)
|
||||
|
@ -96,7 +92,7 @@ module.controller("ProjectProfileController", ProjectProfileController)
|
|||
## Project Profile Directive
|
||||
#############################################################################
|
||||
|
||||
ProjectProfileDirective = ($repo, $confirm, $loading) ->
|
||||
ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
form = $el.find("form").checksley({"onlyOneErrorElement": true})
|
||||
submit = (target) =>
|
||||
|
@ -108,6 +104,8 @@ ProjectProfileDirective = ($repo, $confirm, $loading) ->
|
|||
promise.then ->
|
||||
$loading.finish(target)
|
||||
$confirm.notify("success")
|
||||
newUrl = $navurls.resolve("project-admin-project-profile-details", {project: $scope.project.slug})
|
||||
$location.path(newUrl)
|
||||
$scope.$emit("project:loaded", $scope.project)
|
||||
|
||||
promise.then null, (data) ->
|
||||
|
@ -132,7 +130,7 @@ ProjectProfileDirective = ($repo, $confirm, $loading) ->
|
|||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgProjectProfile", ["$tgRepo", "$tgConfirm", "$tgLoading", ProjectProfileDirective])
|
||||
module.directive("tgProjectProfile", ["$tgRepo", "$tgConfirm", "$tgLoading", "$tgNavUrls", "$tgLocation", ProjectProfileDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
|
|
|
@ -57,11 +57,7 @@ class ProjectValuesController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
promise.then () =>
|
||||
@appTitle.set("Project values - " + @scope.sectionName + " - " + @scope.project.name)
|
||||
|
||||
promise.then null, (xhr) =>
|
||||
if xhr and xhr.status == 404
|
||||
@location.path(@navUrls.resolve("not-found"))
|
||||
@location.replace()
|
||||
return @q.reject(xhr)
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
@scope.$on("admin:project-values:move", @.moveValue)
|
||||
|
||||
|
@ -170,8 +166,8 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame) ->
|
|||
promise = $repo.save(value)
|
||||
promise.then =>
|
||||
row = target.parents(".row.table-main")
|
||||
row.hide()
|
||||
row.siblings(".visualization").css("display": "flex")
|
||||
row.addClass("hidden")
|
||||
row.siblings(".visualization").removeClass('hidden')
|
||||
|
||||
promise.then null, (data) ->
|
||||
$confirm.notify("error")
|
||||
|
@ -181,9 +177,9 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame) ->
|
|||
row = target.parents(".row.table-main")
|
||||
value = target.scope().value
|
||||
$scope.$apply ->
|
||||
row.hide()
|
||||
row.addClass("hidden")
|
||||
value.revert()
|
||||
row.siblings(".visualization").css("display": "flex")
|
||||
row.siblings(".visualization").removeClass('hidden')
|
||||
|
||||
$el.on "submit", "form", (event) ->
|
||||
event.preventDefault()
|
||||
|
@ -195,7 +191,7 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame) ->
|
|||
|
||||
$el.on "click", ".show-add-new", (event) ->
|
||||
event.preventDefault()
|
||||
$el.find(".new-value").css('display': 'flex')
|
||||
$el.find(".new-value").removeClass('hidden')
|
||||
|
||||
goToBottomList(true)
|
||||
|
||||
|
@ -231,9 +227,10 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame) ->
|
|||
target = angular.element(event.currentTarget)
|
||||
|
||||
row = target.parents(".row.table-main")
|
||||
row.hide()
|
||||
row.addClass("hidden")
|
||||
|
||||
editionRow = row.siblings(".edition")
|
||||
editionRow.css("display": "flex")
|
||||
editionRow.removeClass('hidden')
|
||||
editionRow.find('input:visible').first().focus().select()
|
||||
|
||||
$el.on "keyup", ".edition input", (event) ->
|
||||
|
@ -264,12 +261,13 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame) ->
|
|||
choices[option.id] = option.name
|
||||
|
||||
#TODO: i18n
|
||||
title = "Delete"
|
||||
title = "Delete value"
|
||||
subtitle = value.name
|
||||
replacement = "All items with this value will be changed to"
|
||||
if _.keys(choices).length == 0
|
||||
return $confirm.error("You can't delete all values.")
|
||||
|
||||
return $confirm.askChoice(title, subtitle, choices).then (response) ->
|
||||
return $confirm.askChoice(title, subtitle, choices, replacement).then (response) ->
|
||||
onSucces = ->
|
||||
$ctrl.loadValues().finally ->
|
||||
response.finish()
|
||||
|
|
|
@ -58,11 +58,7 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil
|
|||
promise.then () =>
|
||||
@appTitle.set("Roles - " + @scope.project.name)
|
||||
|
||||
promise.then null, (xhr) =>
|
||||
if xhr and xhr.status == 404
|
||||
@location.path(@navUrls.resolve("not-found"))
|
||||
@location.replace()
|
||||
return @q.reject(xhr)
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
loadProject: ->
|
||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||
|
@ -93,8 +89,10 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil
|
|||
|
||||
delete: ->
|
||||
# TODO: i18n
|
||||
title = "Delete Role"
|
||||
title = "Delete Role" # TODO: i18n
|
||||
subtitle = @scope.role.name
|
||||
replacement = "All the users with this role will be moved to" # TODO: i18n
|
||||
warning = "<strong>Be careful, all role estimations will be removed</strong>" # TODO: i18n
|
||||
|
||||
choices = {}
|
||||
for role in @scope.roles
|
||||
|
@ -102,9 +100,9 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil
|
|||
choices[role.id] = role.name
|
||||
|
||||
if _.keys(choices).length == 0
|
||||
return @confirm.error("You can't delete all values.")
|
||||
return @confirm.error("You can't delete all values.") # TODO: i18n
|
||||
|
||||
return @confirm.askChoice(title, subtitle, choices).then (response) =>
|
||||
return @confirm.askChoice(title, subtitle, choices, replacement, warning).then (response) =>
|
||||
promise = @repo.remove(@scope.role, {moveTo: response.selected})
|
||||
promise.then =>
|
||||
@.loadProject()
|
||||
|
@ -127,6 +125,47 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil
|
|||
|
||||
module.controller("RolesController", RolesController)
|
||||
|
||||
EditRoleDirective = ($repo, $confirm) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
toggleView = ->
|
||||
$el.find('.total').toggle()
|
||||
$el.find('.edit-role').toggle()
|
||||
|
||||
submit = () ->
|
||||
$scope.role.name = $el.find("input").val()
|
||||
|
||||
promise = $repo.save($scope.role)
|
||||
|
||||
promise.then ->
|
||||
$confirm.notify("success")
|
||||
|
||||
promise.then null, (data) ->
|
||||
$confirm.notify("error")
|
||||
|
||||
toggleView()
|
||||
|
||||
$el.on "click", "a.icon-edit", ->
|
||||
toggleView()
|
||||
$el.find("input").focus()
|
||||
|
||||
$el.on "click", "a.save", submit
|
||||
|
||||
$el.on "keyup", "input", ->
|
||||
if event.keyCode == 13 # Enter key
|
||||
submit()
|
||||
else if event.keyCode == 27 # ESC key
|
||||
toggleView()
|
||||
|
||||
$scope.$on "role:changed", ->
|
||||
if $el.find('.edit-role').is(":visible")
|
||||
toggleView()
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgEditRole", ["$tgRepo", "$tgConfirm", EditRoleDirective])
|
||||
|
||||
RolesDirective = ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
###
|
||||
|
||||
taiga = @.taiga
|
||||
debounce = @.taiga.debounce
|
||||
|
||||
module = angular.module("taigaAuth", ["taigaResources"])
|
||||
|
||||
|
@ -156,7 +157,7 @@ PublicRegisterMessageDirective = ($config, $navUrls) ->
|
|||
template = _.template("""
|
||||
<p class="login-text">
|
||||
<span>Not registered yet?</span>
|
||||
<a href="<%= url %>" tg-nav="register" title="Register"> create your free account here</a>
|
||||
<a href="<%- url %>" tg-nav="register" title="Register"> create your free account here</a>
|
||||
</p>""")
|
||||
|
||||
templateFn = ->
|
||||
|
@ -225,7 +226,7 @@ RegisterDirective = ($auth, $confirm, $location, $navUrls, $config, $analytics)
|
|||
$location.replace()
|
||||
|
||||
$scope.data = {}
|
||||
form = $el.find("form").checksley()
|
||||
form = $el.find("form").checksley({onlyOneErrorElement: true})
|
||||
|
||||
onSuccessSubmit = (response) ->
|
||||
$analytics.trackEvent("auth", "register", "user registration", 1)
|
||||
|
@ -238,7 +239,7 @@ RegisterDirective = ($auth, $confirm, $location, $navUrls, $config, $analytics)
|
|||
|
||||
form.setErrors(response.data)
|
||||
|
||||
submit = ->
|
||||
submit = debounce 2000, =>
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
|
@ -278,7 +279,7 @@ ForgotPasswordDirective = ($auth, $confirm, $location, $navUrls) ->
|
|||
$confirm.notify("light-error", "According to our Oompa Loompas,
|
||||
your are not registered yet.") #TODO: i18n
|
||||
|
||||
submit = ->
|
||||
submit = debounce 2000, =>
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
|
@ -323,7 +324,7 @@ ChangePasswordFromRecoveryDirective = ($auth, $confirm, $location, $params, $nav
|
|||
$confirm.notify("light-error", "One of our Oompa Loompas say
|
||||
'#{response.data._error_message}'.") #TODO: i18n
|
||||
|
||||
submit = ->
|
||||
submit = debounce 2000, =>
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
|
@ -362,7 +363,7 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics
|
|||
|
||||
# Login form
|
||||
$scope.dataLogin = {token: token}
|
||||
loginForm = $el.find("form.login-form").checksley()
|
||||
loginForm = $el.find("form.login-form").checksley({onlyOneErrorElement: true})
|
||||
|
||||
onSuccessSubmitLogin = (response) ->
|
||||
$analytics.trackEvent("auth", "invitationAccept", "invitation accept with existing user", 1)
|
||||
|
@ -374,7 +375,7 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics
|
|||
$confirm.notify("light-error", "According to our Oompa Loompas, your are not registered yet or
|
||||
typed an invalid password.") #TODO: i18n
|
||||
|
||||
submitLogin = ->
|
||||
submitLogin = debounce 2000, =>
|
||||
if not loginForm.validate()
|
||||
return
|
||||
|
||||
|
@ -403,7 +404,7 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics
|
|||
$confirm.notify("light-error", "According to our Oompa Loompas, that
|
||||
username or email is already in use.") #TODO: i18n
|
||||
|
||||
submitRegister = ->
|
||||
submitRegister = debounce 2000, =>
|
||||
if not registerForm.validate()
|
||||
return
|
||||
|
||||
|
|
|
@ -40,18 +40,18 @@ BacklogFiltersDirective = ($log, $location) ->
|
|||
<% _.each(filters, function(f) { %>
|
||||
<% if (f.selected) { %>
|
||||
<a class="single-filter active"
|
||||
data-type="<%= f.type %>"
|
||||
data-id="<%= f.id %>">
|
||||
<span class="name" <% if (f.color){ %>style="border-left: 3px solid <%= f.color %>;"<% } %>>
|
||||
data-type="<%- f.type %>"
|
||||
data-id="<%- f.id %>">
|
||||
<span class="name" <% if (f.color){ %>style="border-left: 3px solid <%- f.color %>;"<% } %>>
|
||||
<%- f.name %>
|
||||
</span>
|
||||
<span class="number"><%- f.count %></span>
|
||||
</a>
|
||||
<% } else { %>
|
||||
<a class="single-filter"
|
||||
data-type="<%= f.type %>"
|
||||
data-id="<%= f.id %>">
|
||||
<span class="name" <% if (f.color){ %>style="border-left: 3px solid <%= f.color %>;"<% } %>>
|
||||
data-type="<%- f.type %>"
|
||||
data-id="<%- f.id %>">
|
||||
<span class="name" <% if (f.color){ %>style="border-left: 3px solid <%- f.color %>;"<% } %>>
|
||||
<%- f.name %>
|
||||
</span>
|
||||
<span class="number"><%- f.count %></span>
|
||||
|
@ -63,9 +63,9 @@ BacklogFiltersDirective = ($log, $location) ->
|
|||
templateSelected = _.template("""
|
||||
<% _.each(filters, function(f) { %>
|
||||
<a class="single-filter selected"
|
||||
data-type="<%= f.type %>"
|
||||
data-id="<%= f.id %>">
|
||||
<span class="name" <% if (f.color){ %>style="border-left: 3px solid <%= f.color %>;"<% } %>>
|
||||
data-type="<%- f.type %>"
|
||||
data-id="<%- f.id %>">
|
||||
<span class="name" <% if (f.color){ %>style="border-left: 3px solid <%- f.color %>;"<% } %>>
|
||||
<%- f.name %></span>
|
||||
<span class="icon icon-delete"></span>
|
||||
</a>
|
||||
|
@ -79,14 +79,14 @@ BacklogFiltersDirective = ($log, $location) ->
|
|||
|
||||
showFilters = (title, type) ->
|
||||
$el.find(".filters-cats").hide()
|
||||
$el.find(".filter-list").show()
|
||||
$el.find(".filter-list").removeClass("hidden")
|
||||
$el.find("h2.breadcrumb").removeClass("hidden")
|
||||
$el.find("h2 a.subfilter span.title").html(title)
|
||||
$el.find("h2 a.subfilter span.title").prop("data-type", type)
|
||||
|
||||
showCategories = ->
|
||||
$el.find(".filters-cats").show()
|
||||
$el.find(".filter-list").hide()
|
||||
$el.find(".filter-list").addClass("hidden")
|
||||
$el.find("h2.breadcrumb").addClass("hidden")
|
||||
|
||||
initializeSelectedFilters = (filters) ->
|
||||
|
|
|
@ -86,9 +86,9 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading)
|
|||
remove = ->
|
||||
#TODO: i18n
|
||||
title = "Delete sprint"
|
||||
subtitle = $scope.sprint.name
|
||||
message = $scope.sprint.name
|
||||
|
||||
$confirm.ask(title, subtitle).then (finish) =>
|
||||
$confirm.askOnDelete(title, message).then (finish) =>
|
||||
onSuccess = ->
|
||||
finish()
|
||||
$scope.milestonesCounter -= 1
|
||||
|
|
|
@ -74,11 +74,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
|||
tgLoader.pageLoaded()
|
||||
|
||||
# On Error
|
||||
promise.then null, (xhr) =>
|
||||
if xhr and xhr.status == 404
|
||||
@location.path(@navUrls.resolve("not-found"))
|
||||
@location.replace()
|
||||
return @q.reject(xhr)
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
initializeEventHandlers: ->
|
||||
@scope.$on "usform:bulk:success", =>
|
||||
|
@ -431,6 +427,8 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
|||
@scope.filters = {}
|
||||
|
||||
plainTags = _.flatten(_.filter(_.map(@scope.userstories, "tags")))
|
||||
plainTags.sort()
|
||||
|
||||
@scope.filters.tags = _.map _.countBy(plainTags), (v, k) =>
|
||||
obj = {
|
||||
id: k,
|
||||
|
@ -469,9 +467,9 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
|||
deleteUserStory: (us) ->
|
||||
#TODO: i18n
|
||||
title = "Delete User Story"
|
||||
subtitle = us.subject
|
||||
message = us.subject
|
||||
|
||||
@confirm.ask(title, subtitle).then (finish) =>
|
||||
@confirm.askOnDelete(title, message).then (finish) =>
|
||||
# We modify the userstories in scope so the user doesn't see the removed US for a while
|
||||
@scope.userstories = _.without(@scope.userstories, us)
|
||||
@filterVisibleUserstories()
|
||||
|
|
|
@ -21,45 +21,26 @@
|
|||
|
||||
taiga = @.taiga
|
||||
|
||||
mixOf = @.taiga.mixOf
|
||||
toggleText = @.taiga.toggleText
|
||||
scopeDefer = @.taiga.scopeDefer
|
||||
bindOnce = @.taiga.bindOnce
|
||||
groupBy = @.taiga.groupBy
|
||||
|
||||
module = angular.module("taigaBacklog")
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Sprint Directive
|
||||
## Sprint Actions Directive
|
||||
#############################################################################
|
||||
|
||||
BacklogSprintDirective = ($repo, $rootscope) ->
|
||||
## Common parts
|
||||
linkCommon = ($scope, $el, $attrs, $ctrl) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$scope.$watch $attrs.tgBacklogSprint, (sprint) ->
|
||||
sprint = $scope.$eval($attrs.tgBacklogSprint)
|
||||
|
||||
if $scope.$first
|
||||
$el.addClass("sprint-current")
|
||||
$el.find(".sprint-table").addClass('open')
|
||||
|
||||
else if sprint.closed
|
||||
$el.addClass("sprint-closed")
|
||||
|
||||
else if not $scope.$first and not sprint.closed
|
||||
$el.addClass("sprint-old-open")
|
||||
|
||||
# Update progress bars
|
||||
$scope.$watch $attrs.tgBacklogSprint, (value) ->
|
||||
sprint = $scope.$eval($attrs.tgBacklogSprint)
|
||||
|
||||
if sprint.total_points
|
||||
progressPercentage = Math.round(100 * (sprint.closed_points / sprint.total_points))
|
||||
else
|
||||
progressPercentage = 0
|
||||
|
||||
$el.find(".current-progress").css("width", "#{progressPercentage}%")
|
||||
|
||||
$el.find(".sprint-table").disableSelection()
|
||||
|
||||
# Event Handlers
|
||||
$el.on "click", ".sprint-name > .icon-arrow-up", (event) ->
|
||||
target = $(event.currentTarget)
|
||||
|
@ -67,15 +48,92 @@ BacklogSprintDirective = ($repo, $rootscope) ->
|
|||
$el.find(".sprint-table").toggleClass('open')
|
||||
|
||||
$el.on "click", ".sprint-name > .icon-edit", (event) ->
|
||||
sprint = $scope.$eval($attrs.tgBacklogSprint)
|
||||
$rootscope.$broadcast("sprintform:edit", sprint)
|
||||
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$ctrl = $el.closest("div.wrapper").controller()
|
||||
linkCommon($scope, $el, $attrs, $ctrl)
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {link: link}
|
||||
|
||||
module.directive("tgBacklogSprint", ["$tgRepo", "$rootScope", BacklogSprintDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Sprint Header Directive
|
||||
#############################################################################
|
||||
|
||||
BacklogSprintHeaderDirective = ($navUrls) ->
|
||||
template = _.template("""
|
||||
<div class="sprint-name">
|
||||
<a class="icon icon-arrow-up" href="" title="Compact Sprint"></a>
|
||||
|
||||
<% if(isVisible){ %>
|
||||
<a href="<%- taskboardUrl %>" title="'Go to the taskboard of '<%- name %>'">
|
||||
<span><%- name %></span>
|
||||
</a>
|
||||
<% } %>
|
||||
|
||||
<% if(isEditable){ %>
|
||||
<a class="icon icon-edit" href="" title="Edit Sprint"></a>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<div class="sprint-summary">
|
||||
<div class="sprint-date"><%- estimatedDateRange %></div>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="number"><%- closedPoints %></span>
|
||||
<span class="description">closed</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="number"><%- totalPoints %></span>
|
||||
<span class="description">total</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
""")
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
isEditable = ->
|
||||
return $scope.project.my_permissions.indexOf("modify_milestone") != -1
|
||||
|
||||
isVisible = ->
|
||||
return $scope.project.my_permissions.indexOf("view_milestones") != -1
|
||||
|
||||
render = (sprint) ->
|
||||
taskboardUrl = $navUrls.resolve("project-taskboard",
|
||||
{project: $scope.project.slug, sprint: sprint.slug})
|
||||
|
||||
start = moment(sprint.estimated_start).format("DD MMM YYYY")
|
||||
finish = moment(sprint.estimated_finish).format("DD MMM YYYY")
|
||||
estimatedDateRange = "#{start}-#{finish}"
|
||||
|
||||
ctx = {
|
||||
name: sprint.name
|
||||
taskboardUrl: taskboardUrl
|
||||
estimatedDateRange: estimatedDateRange
|
||||
closedPoints: sprint.closed_points or 0
|
||||
totalPoints: sprint.total_points or 0
|
||||
isVisible: isVisible()
|
||||
isEditable: isEditable()
|
||||
}
|
||||
$el.html(template(ctx))
|
||||
|
||||
|
||||
$scope.$watch $attrs.ngModel, (sprint) ->
|
||||
render(sprint)
|
||||
|
||||
$scope.$on "sprintform:edit:success", ->
|
||||
render($model.$modelValue)
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgBacklogSprintHeader", ["$tgNavUrls", "$tgRepo", "$rootScope", BacklogSprintHeaderDirective])
|
||||
|
|
|
@ -46,6 +46,7 @@ urls = {
|
|||
"home": "/"
|
||||
"error": "/error"
|
||||
"not-found": "/not-found"
|
||||
"permission-denied": "/permission-denied"
|
||||
|
||||
"login": "/login"
|
||||
"forgot-password": "/forgot-password"
|
||||
|
@ -66,17 +67,13 @@ urls = {
|
|||
"project-search": "/project/:project/search"
|
||||
|
||||
"project-userstories-detail": "/project/:project/us/:ref"
|
||||
"project-userstories-detail-edit": "/project/:project/us/:ref/edit"
|
||||
|
||||
"project-tasks-detail": "/project/:project/task/:ref"
|
||||
"project-tasks-detail-edit": "/project/:project/task/:ref/edit"
|
||||
|
||||
"project-issues-detail": "/project/:project/issue/:ref"
|
||||
"project-issues-detail-edit": "/project/:project/issue/:ref/edit"
|
||||
|
||||
"project-wiki": "/project/:project/wiki",
|
||||
"project-wiki-page": "/project/:project/wiki/:slug",
|
||||
"project-wiki-page-edit": "/project/:project/wiki/:slug/edit",
|
||||
|
||||
# Admin
|
||||
"project-admin-home": "/project/:project/admin/project-profile/details"
|
||||
|
|
|
@ -72,10 +72,12 @@ class AttachmentsController extends taiga.Controller
|
|||
@.attachments.push(data)
|
||||
@rootscope.$broadcast("attachment:create")
|
||||
|
||||
promise = promise.then null, (data) ->
|
||||
promise = promise.then null, (data) =>
|
||||
@scope.$emit("attachments:size-error") if data.status == 413
|
||||
index = @.uploadingAttachments.indexOf(attachment)
|
||||
@.uploadingAttachments.splice(index, 1)
|
||||
@confirm.notify("error", null, "We have not been able to upload '#{attachment.name}'.")
|
||||
@confirm.notify("error", "We have not been able to upload '#{attachment.name}'.
|
||||
#{data.data._error_message}")
|
||||
return @q.reject(data)
|
||||
|
||||
return promise
|
||||
|
@ -109,7 +111,8 @@ class AttachmentsController extends taiga.Controller
|
|||
@.updateCounters()
|
||||
@rootscope.$broadcast("attachment:edit")
|
||||
|
||||
onError = =>
|
||||
onError = (response) =>
|
||||
$scope.$emit("attachments:size-error") if response.status == 413
|
||||
@confirm.notify("error")
|
||||
return @q.reject()
|
||||
|
||||
|
@ -127,9 +130,9 @@ class AttachmentsController extends taiga.Controller
|
|||
# Remove one concrete attachment.
|
||||
removeAttachment: (attachment) ->
|
||||
title = "Delete attachment" #TODO: i18in
|
||||
subtitle = "the attachment '#{attachment.name}'" #TODO: i18in
|
||||
message = "the attachment '#{attachment.name}'" #TODO: i18in
|
||||
|
||||
return @confirm.ask(title, subtitle).then (finish) =>
|
||||
return @confirm.askOnDelete(title, message).then (finish) =>
|
||||
onSuccess = =>
|
||||
finish()
|
||||
index = @.attachments.indexOf(attachment)
|
||||
|
@ -139,7 +142,7 @@ class AttachmentsController extends taiga.Controller
|
|||
|
||||
onError = =>
|
||||
finish(false)
|
||||
@confirm.notify("error", null, "We have not been able to delete #{subtitle}.")
|
||||
@confirm.notify("error", null, "We have not been able to delete #{message}.")
|
||||
return @q.reject()
|
||||
|
||||
return @repo.remove(attachment).then(onSuccess, onError)
|
||||
|
@ -151,7 +154,7 @@ class AttachmentsController extends taiga.Controller
|
|||
return not item.is_deprecated
|
||||
|
||||
|
||||
AttachmentsDirective = ($confirm) ->
|
||||
AttachmentsDirective = ($config, $confirm) ->
|
||||
template = _.template("""
|
||||
<section class="attachments">
|
||||
<div class="attachments-header">
|
||||
|
@ -159,7 +162,11 @@ AttachmentsDirective = ($confirm) ->
|
|||
<span class="attachments-num" tg-bind-html="ctrl.attachmentsCount"></span>
|
||||
<span class="attachments-text">attachments</span>
|
||||
</h3>
|
||||
<div tg-check-permission="modify_<%- type %>" title="Add new attachment" class="add-attach">
|
||||
<div tg-check-permission="modify_<%- type %>" class="add-attach"
|
||||
title="Add new attachment. <%- maxFileSizeMsg %>">
|
||||
<% if (maxFileSize){ %>
|
||||
<span class="size-info hidden">[Max. size: <%- maxFileSize %>]</span>
|
||||
<% }; %>
|
||||
<label for="add-attach" class="icon icon-plus related-tasks-buttons"></label>
|
||||
<input id="add-attach" type="file" multiple="multiple"/>
|
||||
</div>
|
||||
|
@ -195,7 +202,6 @@ AttachmentsDirective = ($confirm) ->
|
|||
</div>
|
||||
</section>""")
|
||||
|
||||
|
||||
link = ($scope, $el, $attrs, $ctrls) ->
|
||||
$ctrl = $ctrls[0]
|
||||
$model = $ctrls[1]
|
||||
|
@ -222,6 +228,12 @@ AttachmentsDirective = ($confirm) ->
|
|||
$ctrl.reorderAttachment(attachment, newIndex)
|
||||
$ctrl.saveAttachments()
|
||||
|
||||
showSizeInfo = ->
|
||||
$el.find(".size-info").removeClass("hidden")
|
||||
|
||||
$scope.$on "attachments:size-error", ->
|
||||
showSizeInfo()
|
||||
|
||||
$el.on "change", ".attachments-header input", (event) ->
|
||||
files = _.toArray(event.target.files)
|
||||
return if files.length < 1
|
||||
|
@ -249,7 +261,16 @@ AttachmentsDirective = ($confirm) ->
|
|||
$el.off()
|
||||
|
||||
templateFn = ($el, $attrs) ->
|
||||
return template({type: $attrs.type})
|
||||
maxFileSize = $config.get("maxUploadFileSize", null)
|
||||
maxFileSize = sizeFormat(maxFileSize) if maxFileSize
|
||||
maxFileSizeMsg = if maxFileSize then "Maximum upload size is #{maxFileSize}" else "" # TODO: i18n
|
||||
|
||||
ctx = {
|
||||
type: $attrs.type
|
||||
maxFileSize: maxFileSize
|
||||
maxFileSizeMsg: maxFileSizeMsg
|
||||
}
|
||||
return template(ctx)
|
||||
|
||||
return {
|
||||
require: ["tgAttachments", "ngModel"]
|
||||
|
@ -261,13 +282,13 @@ AttachmentsDirective = ($confirm) ->
|
|||
template: templateFn
|
||||
}
|
||||
|
||||
module.directive("tgAttachments", ["$tgConfirm", AttachmentsDirective])
|
||||
module.directive("tgAttachments", ["$tgConfig", "$tgConfirm", AttachmentsDirective])
|
||||
|
||||
|
||||
AttachmentDirective = ->
|
||||
template = _.template("""
|
||||
<div class="attachment-name">
|
||||
<a href="<%- url %>" title="<%- name %>" target="_blank">
|
||||
<a href="<%- url %>" title="<%- name %> uploaded on <%- created_date %>" target="_blank">
|
||||
<span class="icon icon-documents"></span>
|
||||
<span><%- name %><span>
|
||||
</a>
|
||||
|
@ -291,7 +312,7 @@ AttachmentDirective = ->
|
|||
templateEdit = _.template("""
|
||||
<div class="attachment-name">
|
||||
<span class="icon.icon-document"></span>
|
||||
<a href="<%- url %>" title="<%- name %>" target="_blank"><%- name %></a>
|
||||
<a href="<%- url %>" title="<%- name %> uploaded on <%- created_date %>" target="_blank"><%- name %></a>
|
||||
</div>
|
||||
<div class="attachment-size">
|
||||
<span><%- size %></span>
|
||||
|
@ -320,6 +341,7 @@ AttachmentDirective = ->
|
|||
ctx = {
|
||||
id: attachment.id
|
||||
name: attachment.name
|
||||
created_date: moment(attachment.created_date).format("DD MMM YYYY [at] hh:mm") #TODO: i18n
|
||||
url: attachment.url
|
||||
size: sizeFormat(attachment.size)
|
||||
description: attachment.description
|
||||
|
|
|
@ -24,6 +24,7 @@ bindOnce = @.taiga.bindOnce
|
|||
|
||||
module = angular.module("taigaCommon")
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Date Range Directive (used mainly for sprint date range)
|
||||
#############################################################################
|
||||
|
@ -46,6 +47,33 @@ DateRangeDirective = ->
|
|||
module.directive("tgDateRange", DateRangeDirective)
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Date Selector Directive (using pikaday)
|
||||
#############################################################################
|
||||
|
||||
DateSelectorDirective =->
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
selectedDate = null
|
||||
$el.picker = new Pikaday({
|
||||
field: $el[0]
|
||||
format: "DD MMM YYYY"
|
||||
onSelect: (date) =>
|
||||
selectedDate = date
|
||||
onOpen: =>
|
||||
$el.picker.setDate(selectedDate) if selectedDate?
|
||||
})
|
||||
|
||||
$scope.$watch $attrs.ngModel, (val) ->
|
||||
$el.picker.setDate(val) if val?
|
||||
|
||||
return {
|
||||
link: link
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgDateSelector", DateSelectorDirective)
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Sprint Progress Bar Directive
|
||||
#############################################################################
|
||||
|
@ -76,99 +104,133 @@ module.directive("tgSprintProgressbar", SprintProgressBarDirective)
|
|||
|
||||
|
||||
#############################################################################
|
||||
## Date Selector Directive (using pikaday)
|
||||
## Created-by display directive
|
||||
#############################################################################
|
||||
|
||||
DateSelectorDirective =->
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
selectedDate = null
|
||||
$el.picker = new Pikaday({
|
||||
field: $el[0]
|
||||
format: "DD MMM YYYY"
|
||||
onSelect: (date) =>
|
||||
selectedDate = date
|
||||
onOpen: =>
|
||||
$el.picker.setDate(selectedDate) if selectedDate?
|
||||
})
|
||||
CreatedByDisplayDirective = ->
|
||||
# Display the owner information (full name and photo) and the date of
|
||||
# creation of an object (like USs, tasks and issues).
|
||||
#
|
||||
# Example:
|
||||
# div.us-created-by(tg-created-by-display, ng-model="us")
|
||||
#
|
||||
# Requirements:
|
||||
# - model object must have the attributes 'created_date' and
|
||||
# 'owner'(ng-model)
|
||||
# - scope.usersById object is required.
|
||||
|
||||
$scope.$watch $attrs.ngModel, (val) ->
|
||||
$el.picker.setDate(val) if val?
|
||||
template = _.template("""
|
||||
<div class="user-avatar">
|
||||
<img src="<%- owner.photo %>" alt="<%- owner.full_name_display %>" />
|
||||
</div>
|
||||
|
||||
<div class="created-by">
|
||||
<span class="created-title">Created by <%- owner.full_name_display %></span>
|
||||
<span class="created-date"><%- date %></span>
|
||||
</div>
|
||||
""") # TODO: i18n
|
||||
|
||||
link = ($scope, $el, $attrs) ->
|
||||
render = (model) ->
|
||||
html = template({
|
||||
owner: $scope.usersById?[model.owner]
|
||||
date: moment(model.created_date).format("DD MMM YYYY HH:mm")
|
||||
})
|
||||
$el.html(html)
|
||||
|
||||
bindOnce $scope, $attrs.ngModel, (model) ->
|
||||
render(model) if model?
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgDateSelector", DateSelectorDirective)
|
||||
module.directive("tgCreatedByDisplay", CreatedByDisplayDirective)
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Watchers directive
|
||||
#############################################################################
|
||||
|
||||
WatchersDirective = ($rootscope, $confirm) ->
|
||||
WatchersDirective = ($rootscope, $confirm, $repo) ->
|
||||
# You have to include a div with the tg-lb-watchers directive in the page
|
||||
# where use this directive
|
||||
#
|
||||
# TODO: i18n
|
||||
template = _.template("""
|
||||
<% if(isEditable){ %>
|
||||
<div class="watchers-header">
|
||||
<span class="title">watchers</span>
|
||||
<% if (editable) { %>
|
||||
<a href="" title="Add watcher" class="icon icon-plus add-watcher"></a>
|
||||
<% } %>
|
||||
</div>
|
||||
<% } else if(watchers.length > 0){ %>
|
||||
<div class="watchers-header">
|
||||
<span class="title">watchers</span>
|
||||
</div>
|
||||
<% }; %>
|
||||
|
||||
<% _.each(watchers, function(watcher) { %>
|
||||
<div class="watcher-single">
|
||||
<div class="watcher-avatar">
|
||||
<a class="avatar" href="" title="Assigned to">
|
||||
<img src="<%= watcher.photo %>" alt="<%- watcher.full_name_display %>">
|
||||
</a>
|
||||
<span class="avatar" href="" title="<%- watcher.full_name_display %>">
|
||||
<img src="<%- watcher.photo %>" alt="<%- watcher.full_name_display %>">
|
||||
</span>
|
||||
</div>
|
||||
<div class="watcher-name">
|
||||
<span>
|
||||
<%- watcher.full_name_display %>
|
||||
</span>
|
||||
<span><%- watcher.full_name_display %></span>
|
||||
|
||||
<% if (editable) { %>
|
||||
<% if(isEditable){ %>
|
||||
<a class="icon icon-delete"
|
||||
data-watcher-id="<%= watcher.id %>" href="" title="delete-watcher">
|
||||
data-watcher-id="<%- watcher.id %>" href="" title="delete-watcher">
|
||||
</a>
|
||||
<% } %>
|
||||
<% }; %>
|
||||
</div>
|
||||
</div>
|
||||
<% }); %>
|
||||
""")
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
editable = $attrs.editable?
|
||||
isEditable = ->
|
||||
return $scope.project?.my_permissions?.indexOf($attrs.requiredPerm) != -1
|
||||
|
||||
save = (model) ->
|
||||
promise = $repo.save($model.$modelValue)
|
||||
promise.then ->
|
||||
$confirm.notify("success")
|
||||
watchers = _.map(model.watchers, (watcherId) -> $scope.usersById[watcherId])
|
||||
renderWatchers(watchers)
|
||||
$rootscope.$broadcast("history:reload")
|
||||
promise.then null, ->
|
||||
model.revert()
|
||||
$confirm.notify("error")
|
||||
|
||||
renderWatchers = (watchers) ->
|
||||
html = template({watchers: watchers, editable:editable})
|
||||
ctx = {
|
||||
watchers: watchers
|
||||
isEditable: isEditable()
|
||||
}
|
||||
html = template(ctx)
|
||||
$el.html(html)
|
||||
|
||||
if watchers.length == 0
|
||||
if editable
|
||||
if isEditable() and watchers.length == 0
|
||||
$el.find(".title").text("Add watchers")
|
||||
$el.find(".watchers-header").addClass("no-watchers")
|
||||
else
|
||||
$el.find(".watchers-header").hide()
|
||||
|
||||
$scope.$watch $attrs.ngModel, (item) ->
|
||||
return if not item?
|
||||
watchers = _.map(item.watchers, (watcherId) -> $scope.usersById[watcherId])
|
||||
renderWatchers(watchers)
|
||||
|
||||
if not editable
|
||||
$el.find(".add-watcher").remove()
|
||||
|
||||
$el.on "click", ".icon-delete", (event) ->
|
||||
event.preventDefault()
|
||||
return if not isEditable()
|
||||
target = angular.element(event.currentTarget)
|
||||
watcherId = target.data("watcher-id")
|
||||
|
||||
title = "Remove watcher"
|
||||
subtitle = $scope.usersById[watcherId].full_name_display
|
||||
title = "Delete watcher"
|
||||
message = $scope.usersById[watcherId].full_name_display
|
||||
|
||||
$confirm.ask(title, subtitle).then (finish) =>
|
||||
$confirm.askOnDelete(title, message).then (finish) =>
|
||||
finish()
|
||||
watcherIds = _.clone($model.$modelValue.watchers, false)
|
||||
watcherIds = _.pull(watcherIds, watcherId)
|
||||
|
@ -176,9 +238,11 @@ WatchersDirective = ($rootscope, $confirm) ->
|
|||
item = $model.$modelValue.clone()
|
||||
item.watchers = watcherIds
|
||||
$model.$setViewValue(item)
|
||||
save(item)
|
||||
|
||||
$el.on "click", ".add-watcher", (event) ->
|
||||
event.preventDefault()
|
||||
return if not isEditable()
|
||||
$scope.$apply ->
|
||||
$rootscope.$broadcast("watcher:add", $model.$modelValue)
|
||||
|
||||
|
@ -189,86 +253,398 @@ WatchersDirective = ($rootscope, $confirm) ->
|
|||
|
||||
item = $model.$modelValue.clone()
|
||||
item.watchers = watchers
|
||||
|
||||
$model.$setViewValue(item)
|
||||
save(item)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (item) ->
|
||||
return if not item?
|
||||
watchers = _.map(item.watchers, (watcherId) -> $scope.usersById[watcherId])
|
||||
renderWatchers(watchers)
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {link:link, require:"ngModel"}
|
||||
|
||||
module.directive("tgWatchers", ["$rootScope", "$tgConfirm", WatchersDirective])
|
||||
module.directive("tgWatchers", ["$rootScope", "$tgConfirm", "$tgRepo", WatchersDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Assigned to directive
|
||||
#############################################################################
|
||||
|
||||
AssignedToDirective = ($rootscope, $confirm) ->
|
||||
AssignedToDirective = ($rootscope, $confirm, $repo, $loading) ->
|
||||
# You have to include a div with the tg-lb-assignedto directive in the page
|
||||
# where use this directive
|
||||
#
|
||||
# TODO: i18n
|
||||
template = _.template("""
|
||||
<% if (assignedTo) { %>
|
||||
<div class="user-avatar">
|
||||
<img src="<%= assignedTo.photo %>" alt="<%- assignedTo.full_name_display %>" />
|
||||
<img src="<%- assignedTo.photo %>" alt="<%- assignedTo.full_name_display %>" />
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="assigned-to">
|
||||
<span class="assigned-title">Assigned to</span>
|
||||
|
||||
<a href="" title="edit assignment" class="user-assigned <% if (editable) { %> editable <% } %>">
|
||||
<a href="" title="edit assignment" class="user-assigned <% if(isEditable){ %>editable<% }; %>">
|
||||
<span class="assigned-name">
|
||||
<% if (assignedTo) { %>
|
||||
<%- assignedTo.full_name_display %>
|
||||
<% } else { %>
|
||||
Not assigned
|
||||
<% } %>
|
||||
<% if (editable) { %>
|
||||
<span class="icon icon-arrow-bottom"></span>
|
||||
<% } %>
|
||||
</span>
|
||||
<% if(isEditable){ %><span class="icon icon-arrow-bottom"></span><% }; %>
|
||||
</a>
|
||||
<% if (editable && assignedTo!==null) { %>
|
||||
<% if (assignedTo!==null && isEditable) { %>
|
||||
<a href="" title="delete assignment" class="icon icon-delete"></a>
|
||||
<% } %>
|
||||
</div>
|
||||
""")
|
||||
""") # TODO: i18n
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
editable = $attrs.editable?
|
||||
isEditable = ->
|
||||
return $scope.project?.my_permissions?.indexOf($attrs.requiredPerm) != -1
|
||||
|
||||
save = (model) ->
|
||||
$loading.start($el)
|
||||
|
||||
promise = $repo.save($model.$modelValue)
|
||||
promise.then ->
|
||||
$loading.finish($el)
|
||||
$confirm.notify("success")
|
||||
renderAssignedTo(model)
|
||||
$rootscope.$broadcast("history:reload")
|
||||
promise.then null, ->
|
||||
model.revert()
|
||||
$confirm.notify("error")
|
||||
$loading.finish($el)
|
||||
|
||||
renderAssignedTo = (issue) ->
|
||||
assignedToId = issue?.assigned_to
|
||||
assignedTo = null
|
||||
assignedTo = $scope.usersById[assignedToId] if assignedToId?
|
||||
html = template({assignedTo: assignedTo, editable:editable})
|
||||
assignedTo = if assignedToId? then $scope.usersById[assignedToId] else null
|
||||
|
||||
ctx = {
|
||||
assignedTo: assignedTo
|
||||
isEditable: isEditable()
|
||||
}
|
||||
html = template(ctx)
|
||||
$el.html(html)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (instance) ->
|
||||
renderAssignedTo(instance)
|
||||
|
||||
if editable
|
||||
$el.on "click", ".user-assigned", (event) ->
|
||||
event.preventDefault()
|
||||
return if not isEditable()
|
||||
$scope.$apply ->
|
||||
$rootscope.$broadcast("assigned-to:add", $model.$modelValue)
|
||||
|
||||
$el.on "click", ".icon-delete", (event) ->
|
||||
event.preventDefault()
|
||||
title = "Remove assigned to"
|
||||
subtitle = ""
|
||||
return if not isEditable()
|
||||
title = "Are you sure you want to leave it unassigned?" # TODO: i18n
|
||||
|
||||
$confirm.ask(title, subtitle).then (finish) =>
|
||||
$confirm.ask(title).then (finish) =>
|
||||
finish()
|
||||
$model.$modelValue.assigned_to = null
|
||||
renderAssignedTo($model.$modelValue)
|
||||
save($model.$modelValue)
|
||||
|
||||
$scope.$on "assigned-to:added", (ctx, userId) ->
|
||||
$scope.$on "assigned-to:added", (ctx, userId, item) ->
|
||||
return if item.id != $model.$modelValue.id
|
||||
$model.$modelValue.assigned_to = userId
|
||||
renderAssignedTo($model.$modelValue)
|
||||
save($model.$modelValue)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (instance) ->
|
||||
renderAssignedTo(instance)
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link:link,
|
||||
require:"ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgAssignedTo", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgLoading", AssignedToDirective])
|
||||
|
||||
module.directive("tgAssignedTo", ["$rootScope", "$tgConfirm", AssignedToDirective])
|
||||
|
||||
#############################################################################
|
||||
## Block Button directive
|
||||
#############################################################################
|
||||
|
||||
BlockButtonDirective = ($rootscope, $loading) ->
|
||||
template = """
|
||||
<a href="#" class="button button-gray item-block">Block</a>
|
||||
<a href="#" class="button button-red item-unblock">Unblock</a>
|
||||
"""
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
isEditable = ->
|
||||
return $scope.project.my_permissions.indexOf("modify_us") != -1
|
||||
|
||||
$scope.$watch $attrs.ngModel, (item) ->
|
||||
return if not item
|
||||
|
||||
if isEditable()
|
||||
$el.find('.item-block').addClass('editable')
|
||||
|
||||
if item.is_blocked
|
||||
$el.find('.item-block').hide()
|
||||
$el.find('.item-unblock').show()
|
||||
else
|
||||
$el.find('.item-block').show()
|
||||
$el.find('.item-unblock').hide()
|
||||
|
||||
$el.on "click", ".item-block", (event) ->
|
||||
event.preventDefault()
|
||||
$rootscope.$broadcast("block", $model.$modelValue)
|
||||
|
||||
$el.on "click", ".item-unblock", (event) ->
|
||||
event.preventDefault()
|
||||
$loading.start($el.find(".item-unblock"))
|
||||
finish = ->
|
||||
$loading.finish($el.find(".item-unblock"))
|
||||
|
||||
$rootscope.$broadcast("unblock", $model.$modelValue, finish)
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
template: template
|
||||
}
|
||||
|
||||
module.directive("tgBlockButton", ["$rootScope", "$tgLoading", BlockButtonDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Delete Button directive
|
||||
#############################################################################
|
||||
|
||||
DeleteButtonDirective = ($log, $repo, $confirm, $location) ->
|
||||
template = """
|
||||
<a href="" class="button button-red">Delete</a>
|
||||
""" #TODO: i18n
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
if not $attrs.onDeleteGoToUrl
|
||||
return $log.error "DeleteButtonDirective requires on-delete-go-to-url set in scope."
|
||||
if not $attrs.onDeleteTitle
|
||||
return $log.error "DeleteButtonDirective requires on-delete-title set in scope."
|
||||
|
||||
$el.on "click", ".button", (event) ->
|
||||
title = $scope.$eval($attrs.onDeleteTitle)
|
||||
subtitle = $model.$modelValue.subject
|
||||
|
||||
$confirm.askOnDelete(title, subtitle).then (finish) =>
|
||||
promise = $repo.remove($model.$modelValue)
|
||||
promise.then =>
|
||||
finish()
|
||||
url = $scope.$eval($attrs.onDeleteGoToUrl)
|
||||
$location.path(url)
|
||||
promise.then null, =>
|
||||
finish(false)
|
||||
$confirm.notify("error")
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
template: template
|
||||
}
|
||||
|
||||
module.directive("tgDeleteButton", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", DeleteButtonDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Editable subject directive
|
||||
#############################################################################
|
||||
|
||||
EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading) ->
|
||||
template = """
|
||||
<div class="view-subject">
|
||||
{{ item.subject }}
|
||||
<a class="edit icon icon-edit" href="" title="Edit" />
|
||||
</div>
|
||||
<div class="edit-subject">
|
||||
<input type="text" ng-model="item.subject" data-required="true" data-maxlength="500"/>
|
||||
<span class="save-container">
|
||||
<a class="save icon icon-floppy" href="" title="Save" />
|
||||
</span>
|
||||
</div>
|
||||
"""
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
|
||||
isEditable = ->
|
||||
return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1
|
||||
|
||||
save = ->
|
||||
$model.$modelValue.subject = $scope.item.subject
|
||||
$loading.start($el.find('.save-container'))
|
||||
promise = $repo.save($model.$modelValue)
|
||||
promise.then ->
|
||||
$confirm.notify("success")
|
||||
$rootscope.$broadcast("history:reload")
|
||||
$el.find('.edit-subject').hide()
|
||||
$el.find('.view-subject').show()
|
||||
promise.then null, ->
|
||||
$confirm.notify("error")
|
||||
promise.finally ->
|
||||
$loading.finish($el.find('.save-container'))
|
||||
|
||||
$el.click ->
|
||||
return if not isEditable()
|
||||
$el.find('.edit-subject').show()
|
||||
$el.find('.view-subject').hide()
|
||||
$el.find('input').focus()
|
||||
|
||||
$el.on "click", ".save", ->
|
||||
save()
|
||||
|
||||
$el.on "keyup", "input", ->
|
||||
if event.keyCode == 13
|
||||
save()
|
||||
else if event.keyCode == 27
|
||||
$model.$modelValue.revert()
|
||||
$el.find('div.edit-subject').hide()
|
||||
$el.find('div.view-subject').show()
|
||||
|
||||
$el.find('div.edit-subject').hide()
|
||||
$el.find('div.view-subject span.edit').hide()
|
||||
|
||||
|
||||
$scope.$watch $attrs.ngModel, (value) ->
|
||||
return if not value
|
||||
$scope.item = value
|
||||
|
||||
if not isEditable()
|
||||
$el.find('.view-subject .edit').remove()
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
template: template
|
||||
}
|
||||
|
||||
module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading",
|
||||
EditableSubjectDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Editable subject directive
|
||||
#############################################################################
|
||||
|
||||
EditableDescriptionDirective = ($window, $document, $rootscope, $repo, $confirm, $compile, $loading) ->
|
||||
template = """
|
||||
<div class="view-description">
|
||||
<section class="us-content wysiwyg"
|
||||
tg-bind-html="item.description_html || noDescriptionMsg"></section>
|
||||
<span class="edit icon icon-edit" href="" title="Edit" />
|
||||
</div>
|
||||
<div class="edit-description">
|
||||
<textarea placeholder="Empty space is so boring... go on be descriptive... A rose by any other name would smell as sweet..."
|
||||
ng-model="item.description"
|
||||
tg-markitup="tg-markitup"></textarea>
|
||||
<span class="save-container">
|
||||
<a class="save icon icon-floppy" href="" title="Save" />
|
||||
</span>
|
||||
</div>
|
||||
""" # TODO: i18n
|
||||
noDescriptionMegEditMode = """
|
||||
<p class="no-description editable">
|
||||
Empty space is so boring...
|
||||
go on be descriptive...
|
||||
A rose by any other name would smell as sweet...
|
||||
</p>
|
||||
""" # TODO: i18n
|
||||
noDescriptionMegReadMode = """
|
||||
<p class="no-description">
|
||||
No description yet.
|
||||
</p>
|
||||
""" # TODO: i18n
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
$el.find('.edit-description').hide()
|
||||
$el.find('.view-description .edit').hide()
|
||||
|
||||
isEditable = ->
|
||||
return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1
|
||||
|
||||
getSelectedText = ->
|
||||
if $window.getSelection
|
||||
return $window.getSelection().toString()
|
||||
else if $document.selection
|
||||
return $document.selection.createRange().text
|
||||
return null
|
||||
|
||||
$el.on "mouseup", ".view-description", (event) ->
|
||||
# We want to dettect the a inside the div so we use the target and
|
||||
# not the currentTarget
|
||||
target = angular.element(event.target)
|
||||
return if not isEditable()
|
||||
return if target.is('a')
|
||||
return if getSelectedText()
|
||||
|
||||
$el.find('.edit-description').show()
|
||||
$el.find('.view-description').hide()
|
||||
$el.find('textarea').focus()
|
||||
|
||||
$el.on "click", ".save", ->
|
||||
$model.$modelValue.description = $scope.item.description
|
||||
|
||||
$loading.start($el.find('.save-container'))
|
||||
promise = $repo.save($model.$modelValue)
|
||||
promise.then ->
|
||||
$confirm.notify("success")
|
||||
$rootscope.$broadcast("history:reload")
|
||||
$el.find('.edit-description').hide()
|
||||
$el.find('.view-description').show()
|
||||
promise.then null, ->
|
||||
$confirm.notify("error")
|
||||
promise.finally ->
|
||||
$loading.finish($el.find('.save-container'))
|
||||
|
||||
$el.on "keyup", "textarea", ->
|
||||
if event.keyCode == 27
|
||||
$scope.item.revert()
|
||||
$el.find('.edit-description').hide()
|
||||
$el.find('.view-description').show()
|
||||
|
||||
$scope.$watch $attrs.ngModel, (value) ->
|
||||
return if not value
|
||||
$scope.item = value
|
||||
|
||||
if isEditable()
|
||||
$el.find('.view-description .edit').show()
|
||||
$el.find('.view-description .us-content').addClass('editable')
|
||||
$scope.noDescriptionMsg = noDescriptionMegEditMode
|
||||
else
|
||||
$scope.noDescriptionMsg = noDescriptionMegReadMode
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
template: template
|
||||
}
|
||||
|
||||
module.directive("tgEditableDescription", ["$window", "$document", "$rootScope", "$tgRepo", "$tgConfirm",
|
||||
"$compile", "$tgLoading", EditableDescriptionDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
|
@ -308,7 +684,7 @@ ListItemUsStatusDirective = ->
|
|||
ListItemAssignedtoDirective = ->
|
||||
template = _.template("""
|
||||
<figure class="avatar">
|
||||
<img src="<%= imgurl %>" alt="<%- name %>"/>
|
||||
<img src="<%- imgurl %>" alt="<%- name %>"/>
|
||||
<figcaption><%- name %></figcaption>
|
||||
</figure>
|
||||
""")
|
||||
|
@ -377,6 +753,7 @@ ListItemSeverityDirective = ->
|
|||
template: template
|
||||
}
|
||||
|
||||
|
||||
ListItemTypeDirective = ->
|
||||
template = """
|
||||
<div class="level"></div>
|
||||
|
|
|
@ -50,14 +50,13 @@ class ConfirmService extends taiga.Service
|
|||
|
||||
el.off(".confirm-dialog")
|
||||
|
||||
ask: (title, subtitle, message=null, lightboxSelector=".lightbox-confirm-delete") ->
|
||||
ask: (title, subtitle, message, lightboxSelector=".lightbox-generic-ask") ->
|
||||
el = angular.element(lightboxSelector)
|
||||
|
||||
# Render content
|
||||
el.find("h2.title").html(title)
|
||||
el.find("span.subtitle").html(subtitle)
|
||||
if message
|
||||
el.find("span.delete-question").html(message)
|
||||
el.find("span.message").html(message)
|
||||
|
||||
defered = @q.defer()
|
||||
|
||||
|
@ -80,13 +79,27 @@ class ConfirmService extends taiga.Service
|
|||
|
||||
return defered.promise
|
||||
|
||||
askChoice: (title, subtitle, choices, lightboxSelector=".lightbox-ask-choice") ->
|
||||
askOnDelete: (title, message) ->
|
||||
return @.ask(title, "Are you sure you want to delete?", message) #TODO: i18n
|
||||
|
||||
askChoice: (title, subtitle, choices, replacement, warning, lightboxSelector=".lightbox-ask-choice") ->
|
||||
el = angular.element(lightboxSelector)
|
||||
|
||||
# Render content
|
||||
el.find("h2.title").html(title)
|
||||
el.find("span.subtitle").html(subtitle)
|
||||
choicesField = el.find("select.choices")
|
||||
el.find(".title").html(title)
|
||||
el.find(".subtitle").html(subtitle)
|
||||
|
||||
if replacement
|
||||
el.find(".replacement").html(replacement)
|
||||
else
|
||||
el.find(".replacement").remove()
|
||||
|
||||
if warning
|
||||
el.find(".warning").html(warning)
|
||||
else
|
||||
el.find(".warning").remove()
|
||||
|
||||
choicesField = el.find(".choices")
|
||||
choicesField.html('')
|
||||
_.each choices, (value, key) ->
|
||||
choicesField.append(angular.element("<option value='#{key}'>#{value}</option>"))
|
||||
|
|
|
@ -84,11 +84,11 @@ HistoryDirective = ($log, $loading) ->
|
|||
<div class="activity-fromto">
|
||||
<p>
|
||||
<strong> from </strong> <br />
|
||||
<span><%= point[0] %></span>
|
||||
<span><%- point[0] %></span>
|
||||
</p>
|
||||
<p>
|
||||
<strong> to </strong> <br />
|
||||
<span><%= point[1] %></span>
|
||||
<span><%- point[1] %></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -103,11 +103,11 @@ HistoryDirective = ($log, $loading) ->
|
|||
<div class="activity-fromto">
|
||||
<p>
|
||||
<strong> from </strong> <br />
|
||||
<span><%= from %></span>
|
||||
<span><%- from %></span>
|
||||
</p>
|
||||
<p>
|
||||
<strong> to </strong> <br />
|
||||
<span><%= to %></span>
|
||||
<span><%- to %></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -120,12 +120,12 @@ HistoryDirective = ($log, $loading) ->
|
|||
<div class="activity-fromto">
|
||||
<% _.each(diff, function(change) { %>
|
||||
<p>
|
||||
<strong><%= change.name %> from </strong> <br />
|
||||
<span><%= change.from %></span>
|
||||
<strong><%- change.name %> from </strong> <br />
|
||||
<span><%- change.from %></span>
|
||||
</p>
|
||||
<p>
|
||||
<strong><%= change.name %> to </strong> <br />
|
||||
<span><%= change.to %></span>
|
||||
<strong><%- change.name %> to </strong> <br />
|
||||
<span><%- change.to %></span>
|
||||
</p>
|
||||
<% }) %>
|
||||
</div>
|
||||
|
|
|
@ -127,16 +127,33 @@ module.directive("lightbox", ["lightboxService", LightboxDirective])
|
|||
|
||||
# Issue/Userstory blocking message lightbox directive.
|
||||
|
||||
BlockLightboxDirective = (lightboxService) ->
|
||||
BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loading) ->
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
$el.find("h2.title").text($attrs.title)
|
||||
|
||||
$scope.$on "block", ->
|
||||
$el.find(".reason").val($model.$modelValue.blocked_note)
|
||||
lightboxService.open($el)
|
||||
|
||||
$scope.$on "unblock", ->
|
||||
$model.$modelValue.is_blocked = false
|
||||
$model.$modelValue.blocked_note_html = ""
|
||||
$scope.$on "unblock", (event, model, finishCallback) ->
|
||||
item = $model.$modelValue.clone()
|
||||
item.is_blocked = false
|
||||
item.blocked_note = ""
|
||||
|
||||
promise = $tgrepo.save(item)
|
||||
promise.then ->
|
||||
$confirm.notify("success")
|
||||
$rootscope.$broadcast("history:reload")
|
||||
$model.$setViewValue(item)
|
||||
finishCallback()
|
||||
|
||||
promise.then null, ->
|
||||
$confirm.notify("error")
|
||||
item.revert()
|
||||
$model.$setViewValue(item)
|
||||
|
||||
promise.finally ->
|
||||
finishCallback()
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
@ -144,19 +161,34 @@ BlockLightboxDirective = (lightboxService) ->
|
|||
$el.on "click", ".button-green", (event) ->
|
||||
event.preventDefault()
|
||||
|
||||
$scope.$apply ->
|
||||
$model.$modelValue.is_blocked = true
|
||||
$model.$modelValue.blocked_note = $el.find(".reason").val()
|
||||
item = $model.$modelValue.clone()
|
||||
item.is_blocked = true
|
||||
item.blocked_note = $el.find(".reason").val()
|
||||
$model.$setViewValue(item)
|
||||
|
||||
$loading.start($el.find(".button-green"))
|
||||
|
||||
promise = $tgrepo.save($model.$modelValue)
|
||||
promise.then ->
|
||||
$confirm.notify("success")
|
||||
$rootscope.$broadcast("history:reload")
|
||||
|
||||
promise.then null, ->
|
||||
$confirm.notify("error")
|
||||
item.revert()
|
||||
$model.$setViewValue(item)
|
||||
|
||||
promise.finally ->
|
||||
$loading.finish($el.find(".button-green"))
|
||||
lightboxService.close($el)
|
||||
|
||||
return {
|
||||
templateUrl: "/partials/views/modules/lightbox-block.html"
|
||||
link:link,
|
||||
require:"ngModel"
|
||||
link: link
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgLbBlock", ["lightboxService", BlockLightboxDirective])
|
||||
module.directive("tgLbBlock", ["$rootScope", "$tgRepo", "$tgConfirm", "lightboxService", "$tgLoading", BlockLightboxDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
|
@ -202,10 +234,10 @@ module.directive("tgBlockingMessageInput", ["$log", BlockingMessageInputDirectiv
|
|||
|
||||
CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService, $loading) ->
|
||||
link = ($scope, $el, attrs) ->
|
||||
isNew = true
|
||||
$scope.isNew = true
|
||||
|
||||
$scope.$on "usform:new", (ctx, projectId, status, statusList) ->
|
||||
isNew = true
|
||||
$scope.isNew = true
|
||||
$scope.usStatusList = statusList
|
||||
|
||||
$scope.us = {
|
||||
|
@ -229,7 +261,7 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService,
|
|||
|
||||
$scope.$on "usform:edit", (ctx, us) ->
|
||||
$scope.us = us
|
||||
isNew = false
|
||||
$scope.isNew = false
|
||||
|
||||
# Update texts for edition
|
||||
$el.find(".button-green span").html("Save") #TODO: i18n
|
||||
|
@ -264,7 +296,7 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService,
|
|||
|
||||
$loading.start(target)
|
||||
|
||||
if isNew
|
||||
if $scope.isNew
|
||||
promise = $repo.create("userstories", $scope.us)
|
||||
broadcastEvent = "usform:new:success"
|
||||
else
|
||||
|
@ -358,7 +390,7 @@ usersTemplate = _.template("""
|
|||
<div class="watcher-single active">
|
||||
<div class="watcher-avatar">
|
||||
<a href="" title="Assigned to" class="avatar">
|
||||
<img src="<%= selected.photo %>"/>
|
||||
<img src="<%- selected.photo %>"/>
|
||||
</a>
|
||||
</div>
|
||||
<a href="" title="<%- selected.full_name_display %>" class="watcher-name">
|
||||
|
@ -372,7 +404,7 @@ usersTemplate = _.template("""
|
|||
<div class="watcher-single" data-user-id="<%- user.id %>">
|
||||
<div class="watcher-avatar">
|
||||
<a href="#" title="Assigned to" class="avatar">
|
||||
<img src="<%= user.photo %>" />
|
||||
<img src="<%- user.photo %>" />
|
||||
</a>
|
||||
</div>
|
||||
<a href="" title="<%- user.full_name_display %>" class="watcher-name">
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
###
|
||||
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
|
||||
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
|
||||
#
|
||||
# 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/common/raven-logger.coffee
|
||||
###
|
||||
|
||||
|
||||
taiga = @.taiga
|
||||
|
||||
module = angular.module("taigaCommon")
|
||||
|
||||
ExceptionHandlerFactory = ($log, @config) ->
|
||||
ravenConfig = @config.get("ravenConfig", null)
|
||||
if ravenConfig
|
||||
$log.debug "Using the RavenJS exception handler."
|
||||
Raven.config(ravenConfig).install()
|
||||
return (exception, cause) ->
|
||||
$log.error.apply($log, arguments)
|
||||
Raven.captureException(exception)
|
||||
|
||||
else
|
||||
$log.debug "Using the default logging exception handler."
|
||||
return (exception, cause) ->
|
||||
$log.error.apply($log, arguments)
|
||||
|
||||
module.factory("$exceptionHandler", ["$log", "$tgConfig", ExceptionHandlerFactory])
|
|
@ -43,6 +43,9 @@ TagsDirective = ->
|
|||
$ctrl.$formatters.push(formatter)
|
||||
$ctrl.$parsers.push(parser)
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
require: "ngModel"
|
||||
link: link
|
||||
|
@ -73,6 +76,7 @@ ColorizeTagsDirective = ->
|
|||
link = ($scope, $el, $attrs, $ctrl) ->
|
||||
render = (srcTags) ->
|
||||
template = templates[$attrs.tgColorizeTagsType]
|
||||
srcTags.sort()
|
||||
tags = _.map srcTags, (tag) ->
|
||||
color = $scope.project.tags_colors[tag]
|
||||
return {name: tag, color: color}
|
||||
|
@ -83,86 +87,118 @@ ColorizeTagsDirective = ->
|
|||
$scope.$watch $attrs.tgColorizeTags, (tags) ->
|
||||
render(tags) if tags?
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {link: link}
|
||||
|
||||
module.directive("tgColorizeTags", ColorizeTagsDirective)
|
||||
|
||||
|
||||
#############################################################################
|
||||
## TagLine (possible should be moved as generic directive)
|
||||
## TagLine Directive (for Lightboxes)
|
||||
#############################################################################
|
||||
|
||||
TagLineDirective = ($log, $rs) ->
|
||||
# Main directive template (rendered by angular)
|
||||
LbTagLineDirective = ($rs) ->
|
||||
ENTER_KEY = 13
|
||||
|
||||
template = """
|
||||
<div class="tags-container"></div>
|
||||
<input type="text" placeholder="Write tag..." class="tag-input" />
|
||||
<a href="" title="Save" class="save icon icon-floppy"></a>
|
||||
"""
|
||||
<input type="text" placeholder="I'm it! Tag me..." class="tag-input" />
|
||||
<a href="" title="Save" class="save icon icon-floppy hidden"></a>
|
||||
""" # TODO: i18n
|
||||
|
||||
# Tags template (rendered manually using lodash)
|
||||
templateTags = _.template("""
|
||||
<% _.each(tags, function(tag) { %>
|
||||
<div class="tag" style="border-left: 5px solid <%- tag.color %>;">
|
||||
<span class="tag" style="border-left: 5px solid <%- tag.color %>;">
|
||||
<span class="tag-name"><%- tag.name %></span>
|
||||
<% if (editable) { %>
|
||||
<a href="" title="delete tag" class="icon icon-delete"></a>
|
||||
<% } %>
|
||||
</div>
|
||||
<% }); %>""")
|
||||
</span>
|
||||
<% }); %>
|
||||
""") # TODO: i18n
|
||||
|
||||
renderTags = ($el, tags, editable, tagsColors) ->
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
## Render
|
||||
renderTags = (tags, tagsColors) ->
|
||||
ctx = {
|
||||
tags: _.map(tags, (t) -> {name: t, color: tagsColors[t]})
|
||||
editable: editable
|
||||
}
|
||||
html = templateTags(ctx)
|
||||
$el.find("div.tags-container").html(html)
|
||||
|
||||
normalizeTags = (tags) ->
|
||||
tags = _.map(tags, trim)
|
||||
tags = _.map(tags, (x) -> x.toLowerCase())
|
||||
return _.uniq(tags)
|
||||
showSaveButton = -> $el.find(".save").removeClass("hidden")
|
||||
hideSaveButton = -> $el.find(".save").addClass("hidden")
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
editable = if $attrs.editable == "true" then true else false
|
||||
$el.addClass("tags-block")
|
||||
resetInput = ->
|
||||
$el.find("input").val("")
|
||||
$el.find("input").autocomplete("close")
|
||||
|
||||
## Aux methods
|
||||
addValue = (value) ->
|
||||
value = trim(value)
|
||||
return if value.length <= 0
|
||||
value = trim(value.toLowerCase())
|
||||
return if value.length == 0
|
||||
|
||||
tags = _.clone($model.$modelValue, false)
|
||||
tags = [] if not tags?
|
||||
tags.push(value)
|
||||
tags.push(value) if value not in tags
|
||||
|
||||
$scope.$apply ->
|
||||
$model.$setViewValue(normalizeTags(tags))
|
||||
$model.$setViewValue(tags)
|
||||
|
||||
deleteValue = (value) ->
|
||||
value = trim(value.toLowerCase())
|
||||
return if value.length == 0
|
||||
|
||||
tags = _.clone($model.$modelValue, false)
|
||||
tags = _.pull(tags, value)
|
||||
|
||||
$scope.$apply ->
|
||||
$model.$setViewValue(tags)
|
||||
|
||||
saveInputTag = () ->
|
||||
input = $el.find('input')
|
||||
value = $el.find("input").val()
|
||||
|
||||
addValue(input.val())
|
||||
input.val("")
|
||||
input.autocomplete("close")
|
||||
$el.find('.save').hide()
|
||||
addValue(value)
|
||||
resetInput()
|
||||
hideSaveButton()
|
||||
|
||||
$scope.$watch $attrs.ngModel, (val) ->
|
||||
tags_colors = if $scope.project?.tags_colors? then $scope.project.tags_colors else []
|
||||
renderTags($el, val, editable, tags_colors)
|
||||
## Events
|
||||
$el.on "keypress", "input", (event) ->
|
||||
return if event.keyCode != ENTER_KEY
|
||||
event.preventDefault()
|
||||
|
||||
bindOnce $scope, "projectId", (projectId) ->
|
||||
# If not editable, no tags preloading is needed.
|
||||
return if not editable
|
||||
$el.on "keyup", "input", (event) ->
|
||||
target = angular.element(event.currentTarget)
|
||||
|
||||
if event.keyCode == ENTER_KEY
|
||||
saveInputTag()
|
||||
else
|
||||
if target.val().length
|
||||
showSaveButton()
|
||||
else
|
||||
hideSaveButton()
|
||||
|
||||
$el.on "click", ".save", (event) ->
|
||||
event.preventDefault()
|
||||
saveInputTag()
|
||||
|
||||
$el.on "click", ".icon-delete", (event) ->
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
|
||||
value = target.siblings(".tag-name").text()
|
||||
deleteValue(value)
|
||||
|
||||
bindOnce $scope, "project", (project) ->
|
||||
positioningFunction = (position, elements) ->
|
||||
menu = elements.element.element
|
||||
menu.css("width", elements.target.width)
|
||||
menu.css("top", position.top)
|
||||
menu.css("left", position.left)
|
||||
|
||||
$rs.projects.tags(projectId).then (data) ->
|
||||
$el.find("input").autocomplete({
|
||||
source: data
|
||||
source: _.keys(project.tags_colors)
|
||||
position: {
|
||||
my: "left top",
|
||||
using: positioningFunction
|
||||
|
@ -172,38 +208,12 @@ TagLineDirective = ($log, $rs) ->
|
|||
ui.item.value = ""
|
||||
})
|
||||
|
||||
if not editable
|
||||
$el.find("input").remove()
|
||||
$scope.$watch $attrs.ngModel, (tags) ->
|
||||
tagsColors = $scope.project?.tags_colors or []
|
||||
renderTags(tags, tagsColors)
|
||||
|
||||
$el.on "keypress", "input", (event) ->
|
||||
return if event.keyCode != 13
|
||||
event.preventDefault()
|
||||
|
||||
$el.on "keyup", "input", (event) ->
|
||||
target = angular.element(event.currentTarget)
|
||||
|
||||
if event.keyCode == 13
|
||||
saveInputTag()
|
||||
else if target.val().length
|
||||
$el.find('.save').show()
|
||||
else
|
||||
$el.find('.save').hide()
|
||||
|
||||
$el.on "click", ".save", saveInputTag
|
||||
|
||||
$el.on "click", ".icon-delete", (event) ->
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
value = trim(target.siblings(".tag-name").text())
|
||||
|
||||
if value.length <= 0
|
||||
return
|
||||
|
||||
tags = _.clone($model.$modelValue, false)
|
||||
tags = _.pull(tags, value)
|
||||
|
||||
$scope.$apply ->
|
||||
$model.$setViewValue(normalizeTags(tags))
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link:link,
|
||||
|
@ -211,4 +221,198 @@ TagLineDirective = ($log, $rs) ->
|
|||
template: template
|
||||
}
|
||||
|
||||
module.directive("tgTagLine", ["$log", "$tgResources", TagLineDirective])
|
||||
module.directive("tgLbTagLine", ["$tgResources", LbTagLineDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## TagLine Directive (for detail pages)
|
||||
#############################################################################
|
||||
|
||||
TagLineDirective = ($rootScope, $repo, $rs, $confirm) ->
|
||||
ENTER_KEY = 13
|
||||
ESC_KEY = 27
|
||||
|
||||
template = """
|
||||
<div class="tags-container"></div>
|
||||
<a href="#" class="add-tag hidden" title="Add tag">
|
||||
<span class="icon icon-plus"></span>
|
||||
<span class="add-tag-text">Add tag</span>
|
||||
</a>
|
||||
<input type="text" placeholder="I'm it! Tag me..." class="tag-input hidden" />
|
||||
<a href="" title="Save" class="save icon icon-floppy hidden"></a>
|
||||
""" # TODO: i18n
|
||||
|
||||
# Tags template (rendered manually using lodash)
|
||||
templateTags = _.template("""
|
||||
<% _.each(tags, function(tag) { %>
|
||||
<span class="tag" style="border-left: 5px solid <%- tag.color %>;">
|
||||
<span class="tag-name"><%- tag.name %></span>
|
||||
<% if (isEditable) { %>
|
||||
<a href="" title="delete tag" class="icon icon-delete"></a>
|
||||
<% } %>
|
||||
</span>
|
||||
<% }); %>
|
||||
""") # TODO: i18n
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
isEditable = ->
|
||||
return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1
|
||||
|
||||
## Render
|
||||
renderTags = (tags, tagsColors) ->
|
||||
ctx = {
|
||||
tags: _.map(tags, (t) -> {name: t, color: tagsColors[t]})
|
||||
isEditable: isEditable()
|
||||
}
|
||||
html = templateTags(ctx)
|
||||
$el.find("div.tags-container").html(html)
|
||||
|
||||
renderInReadModeOnly = ->
|
||||
$el.find(".add-tag").remove()
|
||||
$el.find("input").remove()
|
||||
$el.find(".save").remove()
|
||||
|
||||
showAddTagButton = -> $el.find(".add-tag").removeClass("hidden")
|
||||
hideAddTagButton = -> $el.find(".add-tag").addClass("hidden")
|
||||
|
||||
showAddTagButtonText = -> $el.find(".add-tag-text").removeClass("hidden")
|
||||
hideAddTagButtonText = -> $el.find(".add-tag-text").addClass("hidden")
|
||||
|
||||
showSaveButton = -> $el.find(".save").removeClass("hidden")
|
||||
hideSaveButton = -> $el.find(".save").addClass("hidden")
|
||||
|
||||
showInput = -> $el.find("input").removeClass("hidden").focus()
|
||||
hideInput = -> $el.find("input").addClass("hidden").blur()
|
||||
resetInput = ->
|
||||
$el.find("input").val("")
|
||||
$el.find("input").autocomplete("close")
|
||||
|
||||
## Aux methods
|
||||
addValue = (value) ->
|
||||
value = trim(value.toLowerCase())
|
||||
return if value.length == 0
|
||||
|
||||
tags = _.clone($model.$modelValue.tags, false)
|
||||
tags = [] if not tags?
|
||||
tags.push(value) if value not in tags
|
||||
|
||||
model = $model.$modelValue.clone()
|
||||
model.tags = tags
|
||||
$model.$setViewValue(model)
|
||||
|
||||
onSuccess = ->
|
||||
$rootScope.$broadcast("history:reload")
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
model.revert()
|
||||
$model.$setViewValue(model)
|
||||
$repo.save(model).then(onSuccess, onError)
|
||||
|
||||
deleteValue = (value) ->
|
||||
value = trim(value.toLowerCase())
|
||||
return if value.length == 0
|
||||
|
||||
tags = _.clone($model.$modelValue.tags, false)
|
||||
tags = _.pull(tags, value)
|
||||
|
||||
model = $model.$modelValue.clone()
|
||||
model.tags = tags
|
||||
$model.$setViewValue(model)
|
||||
|
||||
onSuccess = ->
|
||||
$rootScope.$broadcast("history:reload")
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
model.revert()
|
||||
$model.$setViewValue(model)
|
||||
$repo.save(model).then(onSuccess, onError)
|
||||
|
||||
saveInputTag = () ->
|
||||
value = $el.find("input").val()
|
||||
|
||||
addValue(value)
|
||||
resetInput()
|
||||
hideSaveButton()
|
||||
|
||||
## Events
|
||||
$el.on "keypress", "input", (event) ->
|
||||
return if event.keyCode not in [ENTER_KEY, ESC_KEY]
|
||||
event.preventDefault()
|
||||
|
||||
$el.on "keyup", "input", (event) ->
|
||||
target = angular.element(event.currentTarget)
|
||||
|
||||
if event.keyCode == ENTER_KEY
|
||||
saveInputTag()
|
||||
else if event.keyCode == ESC_KEY
|
||||
resetInput()
|
||||
hideInput()
|
||||
hideSaveButton()
|
||||
showAddTagButton()
|
||||
else
|
||||
if target.val().length
|
||||
showSaveButton()
|
||||
else
|
||||
hideSaveButton()
|
||||
|
||||
$el.on "click", ".save", (event) ->
|
||||
event.preventDefault()
|
||||
saveInputTag()
|
||||
|
||||
$el.on "click", ".add-tag", (event) ->
|
||||
event.preventDefault()
|
||||
hideAddTagButton()
|
||||
showInput()
|
||||
|
||||
$el.on "click", ".icon-delete", (event) ->
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
|
||||
value = target.siblings(".tag-name").text()
|
||||
deleteValue(value)
|
||||
|
||||
bindOnce $scope, "project", (project) ->
|
||||
if not isEditable()
|
||||
renderInReadModeOnly()
|
||||
return
|
||||
|
||||
showAddTagButton()
|
||||
|
||||
positioningFunction = (position, elements) ->
|
||||
menu = elements.element.element
|
||||
menu.css("width", elements.target.width)
|
||||
menu.css("top", position.top)
|
||||
menu.css("left", position.left)
|
||||
|
||||
$el.find("input").autocomplete({
|
||||
source: _.keys(project.tags_colors)
|
||||
position: {
|
||||
my: "left top",
|
||||
using: positioningFunction
|
||||
}
|
||||
select: (event, ui) ->
|
||||
addValue(ui.item.value)
|
||||
ui.item.value = ""
|
||||
})
|
||||
|
||||
$scope.$watch $attrs.ngModel, (model) ->
|
||||
return if not model
|
||||
|
||||
if model.tags?.length
|
||||
hideAddTagButtonText()
|
||||
else
|
||||
showAddTagButtonText()
|
||||
|
||||
tagsColors = $scope.project?.tags_colors or []
|
||||
renderTags(model.tags, tagsColors)
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link:link,
|
||||
require:"ngModel"
|
||||
template: template
|
||||
}
|
||||
|
||||
module.directive("tgTagLine", ["$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", TagLineDirective])
|
||||
|
|
|
@ -32,7 +32,7 @@ tgMarkitupDirective = ($rootscope, $rs, $tr) ->
|
|||
previewTemplate = _.template("""
|
||||
<div class="preview">
|
||||
<div class="actions">
|
||||
<a href="#" title="Edit">Edit</a>
|
||||
<a href="#" title="Edit" class="icon icon-edit edit"></a>
|
||||
</div>
|
||||
<div class="content wysiwyg">
|
||||
<%= data %>
|
||||
|
@ -96,17 +96,23 @@ tgMarkitupDirective = ($rootscope, $rs, $tr) ->
|
|||
onEnter:
|
||||
keepDefault: false
|
||||
replaceWith: (data) =>
|
||||
lines = data.textarea.value[0..(data.caretPosition - 1)].split("\n")
|
||||
lastLine = lines[lines.length - 1]
|
||||
lines = data.textarea.value.split("\n")
|
||||
cursorLine = data.textarea.value[0..(data.caretPosition - 1)].split("\n").length
|
||||
newLineContent = data.textarea.value[data.caretPosition..].split("\n")[0]
|
||||
lastLine = lines[cursorLine - 1]
|
||||
|
||||
# unordered list -
|
||||
match = lastLine.match /^(\s*- ).*/
|
||||
|
||||
if match
|
||||
emptyListItem = lastLine.match /^(\s*)\-\s$/
|
||||
|
||||
if emptyListItem
|
||||
markdownCaretPositon = removeEmptyLine(data.textarea, lines.length - 1, data.caretPosition)
|
||||
else
|
||||
breakLineAtBeginning = newLineContent.match /^(\s*)\-\s/
|
||||
|
||||
if !breakLineAtBeginning
|
||||
return "\n#{match[1]}" if match
|
||||
|
||||
# unordered list *
|
||||
|
@ -118,6 +124,9 @@ tgMarkitupDirective = ($rootscope, $rs, $tr) ->
|
|||
if emptyListItem
|
||||
markdownCaretPositon = removeEmptyLine(data.textarea, lines.length - 1, data.caretPosition)
|
||||
else
|
||||
breakLineAtBeginning = newLineContent.match /^(\s*)\*\s/
|
||||
|
||||
if !breakLineAtBeginning
|
||||
return "\n#{match[1]}" if match
|
||||
|
||||
# ordered list
|
||||
|
@ -129,6 +138,9 @@ tgMarkitupDirective = ($rootscope, $rs, $tr) ->
|
|||
if emptyListItem
|
||||
markdownCaretPositon = removeEmptyLine(data.textarea, lines.length - 1, data.caretPosition)
|
||||
else
|
||||
breakLineAtBeginning = newLineContent.match /^(\s*)(\d+)\.\s/
|
||||
|
||||
if !breakLineAtBeginning
|
||||
return "\n#{match[1] + (parseInt(match[2], 10) + 1)}. "
|
||||
|
||||
return "\n"
|
||||
|
|
|
@ -46,11 +46,12 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
"$log",
|
||||
"$appTitle",
|
||||
"$tgAnalytics",
|
||||
"$tgNavUrls"
|
||||
"$tgNavUrls",
|
||||
"tgLoader"
|
||||
]
|
||||
|
||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
|
||||
@log, @appTitle, @analytics, @navUrls) ->
|
||||
@log, @appTitle, @analytics, @navUrls, tgLoader) ->
|
||||
@scope.issueRef = @params.issueref
|
||||
@scope.sectionName = "Issue Details"
|
||||
@.initializeEventHandlers()
|
||||
|
@ -60,14 +61,11 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
# On Success
|
||||
promise.then =>
|
||||
@appTitle.set(@scope.issue.subject + " - " + @scope.project.name)
|
||||
@.initializeOnDeleteGoToUrl()
|
||||
tgLoader.pageLoaded()
|
||||
|
||||
# On Error
|
||||
promise.then null, (xhr) =>
|
||||
if xhr and xhr.status == 404
|
||||
@location.path(@navUrls.resolve("not-found"))
|
||||
@location.replace()
|
||||
return @q.reject(xhr)
|
||||
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
initializeEventHandlers: ->
|
||||
@scope.$on "attachment:create", =>
|
||||
|
@ -85,6 +83,13 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
@rootscope.$broadcast("history:reload")
|
||||
@.loadIssue()
|
||||
|
||||
initializeOnDeleteGoToUrl: ->
|
||||
ctx = {project: @scope.project.slug}
|
||||
if @scope.project.is_issues_activated
|
||||
@scope.onDeleteGoToUrl = @navUrls.resolve("project-issues", ctx)
|
||||
else
|
||||
@scope.onDeleteGoToUrl = @navUrls.resolve("project", ctx)
|
||||
|
||||
loadProject: ->
|
||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||
@scope.project = project
|
||||
|
@ -134,78 +139,24 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
.then(=> @.loadUsersAndRoles())
|
||||
.then(=> @.loadIssue())
|
||||
|
||||
block: ->
|
||||
@rootscope.$broadcast("block", @scope.issue)
|
||||
|
||||
unblock: ->
|
||||
@rootscope.$broadcast("unblock", @scope.issue)
|
||||
|
||||
delete: ->
|
||||
# TODO: i18n
|
||||
title = "Delete Issue"
|
||||
subtitle = @scope.issue.subject
|
||||
|
||||
@confirm.ask(title, subtitle).then (finish) =>
|
||||
promise = @.repo.remove(@scope.issue)
|
||||
promise.then =>
|
||||
finish()
|
||||
@location.path(@navUrls.resolve("project-issues", {project: @scope.project.slug}))
|
||||
promise.then null, =>
|
||||
finish(false)
|
||||
@confirm.notify("error")
|
||||
|
||||
module.controller("IssueDetailController", IssueDetailController)
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Issue Main Directive
|
||||
## Issue status display directive
|
||||
#############################################################################
|
||||
|
||||
IssueDirective = ($tgrepo, $log, $location, $confirm, $navUrls, $loading) ->
|
||||
linkSidebar = ($scope, $el, $attrs, $ctrl) ->
|
||||
IssueStatusDisplayDirective = ->
|
||||
# Display if a Issue is open or closed and its issueboard status.
|
||||
#
|
||||
# Example:
|
||||
# tg-issue-status-display(ng-model="issue")
|
||||
#
|
||||
# Requirements:
|
||||
# - Issue object (ng-model)
|
||||
# - scope.statusById object
|
||||
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$ctrl = $el.controller()
|
||||
linkSidebar($scope, $el, $attrs, $ctrl)
|
||||
|
||||
if $el.is("form")
|
||||
form = $el.checksley()
|
||||
|
||||
$el.on "click", ".save-issue", (event) ->
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
onSuccess = ->
|
||||
$loading.finish(target)
|
||||
$confirm.notify("success")
|
||||
ctx = {
|
||||
project: $scope.project.slug
|
||||
ref: $scope.issue.ref
|
||||
}
|
||||
$location.path($navUrls.resolve("project-issues-detail", ctx))
|
||||
|
||||
onError = ->
|
||||
$loading.finish(target)
|
||||
$confirm.notify("error")
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
$loading.start(target)
|
||||
$tgrepo.save($scope.issue).then(onSuccess, onError)
|
||||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgIssueDetail", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm", "$tgNavUrls",
|
||||
"$tgLoading", IssueDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Issue status directive
|
||||
#############################################################################
|
||||
|
||||
IssueStatusDirective = () ->
|
||||
# TODO: i18n
|
||||
template = _.template("""
|
||||
<h1>
|
||||
<span>
|
||||
<% if (status.is_closed) { %>
|
||||
Closed
|
||||
|
@ -213,171 +164,407 @@ IssueStatusDirective = () ->
|
|||
Open
|
||||
<% } %>
|
||||
</span>
|
||||
<span class="us-detail-status" style="color:<%= status.color %>"><%= status.name %></span>
|
||||
</h1>
|
||||
<div class="us-created-by">
|
||||
<div class="user-avatar">
|
||||
<img src="<%= owner.photo %>" alt="<%- owner.full_name_display %>" />
|
||||
</div>
|
||||
<span class="us-detail-status" style="color:<%- status.color %>">
|
||||
<%- status.name %>
|
||||
</span>
|
||||
""") # TODO: i18n
|
||||
|
||||
<div class="created-by">
|
||||
<span class="created-title">Created by <%- owner.full_name_display %></span>
|
||||
<span class="created-date"><%- date %></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="issue-data">
|
||||
<div class="type-data <% if (editable) { %>clickable<% } %>">
|
||||
<span class="level" style="background-color:<%= type.color %>"></span>
|
||||
<span class="type-status"><%= type.name %></span>
|
||||
<% if (editable) { %>
|
||||
<span class="icon icon-arrow-bottom"></span>
|
||||
<% } %>
|
||||
<span class="level-name">type</span>
|
||||
</div>
|
||||
<div class="severity-data <% if (editable) { %>clickable<% } %>">
|
||||
<span class="level" style="background-color:<%= severity.color %>"></span>
|
||||
<span class="severity-status"><%= severity.name %></span>
|
||||
<% if (editable) { %>
|
||||
<span class="icon icon-arrow-bottom"></span>
|
||||
<% } %>
|
||||
<span class="level-name">severity</span>
|
||||
</div>
|
||||
<div class="priority-data <% if (editable) { %>clickable<% } %>">
|
||||
<span class="level" style="background-color:<%= priority.color %>"></span>
|
||||
<span class="priority-status"><%= priority.name %></span>
|
||||
<% if (editable) { %>
|
||||
<span class="icon icon-arrow-bottom"></span>
|
||||
<% } %>
|
||||
<span class="level-name">priority</span>
|
||||
</div>
|
||||
<div class="status-data <% if (editable) { %>clickable<% } %>">
|
||||
<span class="level" style="background-color:<%= status.color %>"></span>
|
||||
<span class="status-status"><%= status.name %></span>
|
||||
<% if (editable) { %>
|
||||
<span class="icon icon-arrow-bottom"></span>
|
||||
<% } %>
|
||||
<span class="level-name">status</span>
|
||||
</div>
|
||||
</div>
|
||||
""")
|
||||
selectionTypeTemplate = _.template("""
|
||||
<ul class="popover pop-type">
|
||||
<% _.each(types, function(type) { %>
|
||||
<li><a href="" class="type" title="<%- type.name %>"
|
||||
data-type-id="<%- type.id %>"><%- type.name %></a></li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
""")
|
||||
selectionSeverityTemplate = _.template("""
|
||||
<ul class="popover pop-severity">
|
||||
<% _.each(severities, function(severity) { %>
|
||||
<li><a href="" class="severity" title="<%- severity.name %>"
|
||||
data-severity-id="<%- severity.id %>"><%- severity.name %></a></li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
""")
|
||||
selectionPriorityTemplate = _.template("""
|
||||
<ul class="popover pop-priority">
|
||||
<% _.each(priorities, function(priority) { %>
|
||||
<li><a href="" class="priority" title="<%- priority.name %>"
|
||||
data-priority-id="<%- priority.id %>"><%- priority.name %></a></li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
""")
|
||||
selectionStatusTemplate = _.template("""
|
||||
<ul class="popover pop-status">
|
||||
<% _.each(statuses, function(status) { %>
|
||||
<li><a href="" class="status" title="<%- status.name %>"
|
||||
data-status-id="<%- status.id %>"><%- status.name %></a></li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
""")
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
editable = $attrs.editable?
|
||||
|
||||
renderIssuestatus = (issue) ->
|
||||
owner = $scope.usersById?[issue.owner]
|
||||
date = moment(issue.created_date).format("DD MMM YYYY HH:mm")
|
||||
type = $scope.typeById[issue.type]
|
||||
status = $scope.statusById[issue.status]
|
||||
severity = $scope.severityById[issue.severity]
|
||||
priority = $scope.priorityById[issue.priority]
|
||||
link = ($scope, $el, $attrs) ->
|
||||
render = (issue) ->
|
||||
html = template({
|
||||
owner: owner
|
||||
date: date
|
||||
editable: editable
|
||||
status: status
|
||||
severity: severity
|
||||
priority: priority
|
||||
type: type
|
||||
status: $scope.statusById[issue.status]
|
||||
})
|
||||
$el.html(html)
|
||||
$el.find(".type-data").append(selectionTypeTemplate({types:$scope.typeList}))
|
||||
$el.find(".severity-data").append(selectionSeverityTemplate({severities:$scope.severityList}))
|
||||
$el.find(".priority-data").append(selectionPriorityTemplate({priorities:$scope.priorityList}))
|
||||
$el.find(".status-data").append(selectionStatusTemplate({statuses:$scope.statusList}))
|
||||
|
||||
$scope.$watch $attrs.ngModel, (issue) ->
|
||||
if issue?
|
||||
renderIssuestatus(issue)
|
||||
render(issue) if issue?
|
||||
|
||||
if editable
|
||||
$el.on "click", ".type-data", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
$el.find(".pop-type").popover().open()
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
$el.on "click", ".type", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
target = angular.element(event.currentTarget)
|
||||
$model.$modelValue.type = target.data("type-id")
|
||||
renderIssuestatus($model.$modelValue)
|
||||
$.fn.popover().closeAll()
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
$el.on "click", ".severity-data", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
$el.find(".pop-severity").popover().open()
|
||||
module.directive("tgIssueStatusDisplay", IssueStatusDisplayDirective)
|
||||
|
||||
$el.on "click", ".severity", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
target = angular.element(event.currentTarget)
|
||||
$model.$modelValue.severity = target.data("severity-id")
|
||||
renderIssuestatus($model.$modelValue)
|
||||
$.fn.popover().closeAll()
|
||||
|
||||
$el.on "click", ".priority-data", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
$el.find(".pop-priority").popover().open()
|
||||
#############################################################################
|
||||
## Issue status button directive
|
||||
#############################################################################
|
||||
|
||||
$el.on "click", ".priority", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
target = angular.element(event.currentTarget)
|
||||
$model.$modelValue.priority = target.data("priority-id")
|
||||
renderIssuestatus($model.$modelValue)
|
||||
$.fn.popover().closeAll()
|
||||
IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
||||
# Display the status of Issue and you can edit it.
|
||||
#
|
||||
# Example:
|
||||
# tg-issue-status-button(ng-model="issue")
|
||||
#
|
||||
# Requirements:
|
||||
# - Issue object (ng-model)
|
||||
# - scope.statusById object
|
||||
# - $scope.project.my_permissions
|
||||
|
||||
template = _.template("""
|
||||
<div class="status-data <% if(editable){ %>clickable<% }%>">
|
||||
<span class="level" style="background-color:<%- status.color %>"></span>
|
||||
<span class="status-status"><%- status.name %></span>
|
||||
<% if(editable){ %><span class="icon icon-arrow-bottom"></span><% }%>
|
||||
<span class="level-name">status</span>
|
||||
|
||||
<ul class="popover pop-status">
|
||||
<% _.each(statuses, function(st) { %>
|
||||
<li><a href="" class="status" title="<%- st.name %>"
|
||||
data-status-id="<%- st.id %>"><%- st.name %></a></li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
</div>
|
||||
""") #TODO: i18n
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
isEditable = ->
|
||||
return $scope.project.my_permissions.indexOf("modify_issue") != -1
|
||||
|
||||
render = (issue) =>
|
||||
status = $scope.statusById[issue.status]
|
||||
|
||||
html = template({
|
||||
status: status
|
||||
statuses: $scope.statusList
|
||||
editable: isEditable()
|
||||
})
|
||||
$el.html(html)
|
||||
|
||||
$el.on "click", ".status-data", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return if not isEditable()
|
||||
|
||||
$el.find(".pop-status").popover().open()
|
||||
|
||||
$el.on "click", ".status", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return if not isEditable()
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
$model.$modelValue.status = target.data("status-id")
|
||||
renderIssuestatus($model.$modelValue)
|
||||
|
||||
$.fn.popover().closeAll()
|
||||
|
||||
return {link:link, require:"ngModel"}
|
||||
issue = $model.$modelValue.clone()
|
||||
issue.status = target.data("status-id")
|
||||
$model.$setViewValue(issue)
|
||||
|
||||
module.directive("tgIssueStatus", IssueStatusDirective)
|
||||
$scope.$apply()
|
||||
|
||||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("history:reload")
|
||||
$loading.finish($el.find(".level-name"))
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
issue.revert()
|
||||
$model.$setViewValue(issue)
|
||||
$loading.finish($el.find(".level-name"))
|
||||
|
||||
$loading.start($el.find(".level-name"))
|
||||
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (issue) ->
|
||||
render(issue) if issue
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgIssueStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", IssueStatusButtonDirective])
|
||||
|
||||
#############################################################################
|
||||
## Issue type button directive
|
||||
#############################################################################
|
||||
|
||||
IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
||||
# Display the type of Issue and you can edit it.
|
||||
#
|
||||
# Example:
|
||||
# tg-issue-type-button(ng-model="issue")
|
||||
#
|
||||
# Requirements:
|
||||
# - Issue object (ng-model)
|
||||
# - scope.typeById object
|
||||
# - $scope.project.my_permissions
|
||||
|
||||
template = _.template("""
|
||||
<div class="type-data <% if(editable){ %>clickable<% }%>">
|
||||
<span class="level" style="background-color:<%- type.color %>"></span>
|
||||
<span class="type-type"><%- type.name %></span>
|
||||
<% if(editable){ %><span class="icon icon-arrow-bottom"></span><% }%>
|
||||
<span class="level-name">type</span>
|
||||
|
||||
<ul class="popover pop-type">
|
||||
<% _.each(typees, function(tp) { %>
|
||||
<li><a href="" class="type" title="<%- tp.name %>"
|
||||
data-type-id="<%- tp.id %>"><%- tp.name %></a></li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
</div>
|
||||
""") #TODO: i18n
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
isEditable = ->
|
||||
return $scope.project.my_permissions.indexOf("modify_issue") != -1
|
||||
|
||||
render = (issue) =>
|
||||
type = $scope.typeById[issue.type]
|
||||
|
||||
html = template({
|
||||
type: type
|
||||
typees: $scope.typeList
|
||||
editable: isEditable()
|
||||
})
|
||||
$el.html(html)
|
||||
|
||||
$el.on "click", ".type-data", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return if not isEditable()
|
||||
|
||||
$el.find(".pop-type").popover().open()
|
||||
|
||||
$el.on "click", ".type", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return if not isEditable()
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
|
||||
$.fn.popover().closeAll()
|
||||
|
||||
issue = $model.$modelValue.clone()
|
||||
issue.type = target.data("type-id")
|
||||
$model.$setViewValue(issue)
|
||||
|
||||
$scope.$apply()
|
||||
|
||||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("history:reload")
|
||||
$loading.finish($el.find(".level-name"))
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
issue.revert()
|
||||
$model.$setViewValue(issue)
|
||||
$loading.finish($el.find(".level-name"))
|
||||
$loading.start($el.find(".level-name"))
|
||||
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (issue) ->
|
||||
render(issue) if issue
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgIssueTypeButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", IssueTypeButtonDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Issue severity button directive
|
||||
#############################################################################
|
||||
|
||||
IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
||||
# Display the severity of Issue and you can edit it.
|
||||
#
|
||||
# Example:
|
||||
# tg-issue-severity-button(ng-model="issue")
|
||||
#
|
||||
# Requirements:
|
||||
# - Issue object (ng-model)
|
||||
# - scope.severityById object
|
||||
# - $scope.project.my_permissions
|
||||
|
||||
template = _.template("""
|
||||
<div class="severity-data <% if(editable){ %>clickable<% }%>">
|
||||
<span class="level" style="background-color:<%- severity.color %>"></span>
|
||||
<span class="severity-severity"><%- severity.name %></span>
|
||||
<% if(editable){ %><span class="icon icon-arrow-bottom"></span><% }%>
|
||||
<span class="level-name">severity</span>
|
||||
|
||||
<ul class="popover pop-severity">
|
||||
<% _.each(severityes, function(sv) { %>
|
||||
<li><a href="" class="severity" title="<%- sv.name %>"
|
||||
data-severity-id="<%- sv.id %>"><%- sv.name %></a></li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
</div>
|
||||
""") #TODO: i18n
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
isEditable = ->
|
||||
return $scope.project.my_permissions.indexOf("modify_issue") != -1
|
||||
|
||||
render = (issue) =>
|
||||
severity = $scope.severityById[issue.severity]
|
||||
|
||||
html = template({
|
||||
severity: severity
|
||||
severityes: $scope.severityList
|
||||
editable: isEditable()
|
||||
})
|
||||
$el.html(html)
|
||||
|
||||
$el.on "click", ".severity-data", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return if not isEditable()
|
||||
|
||||
$el.find(".pop-severity").popover().open()
|
||||
|
||||
$el.on "click", ".severity", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return if not isEditable()
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
|
||||
$.fn.popover().closeAll()
|
||||
|
||||
issue = $model.$modelValue.clone()
|
||||
issue.severity = target.data("severity-id")
|
||||
$model.$setViewValue(issue)
|
||||
|
||||
$scope.$apply()
|
||||
|
||||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("history:reload")
|
||||
$loading.finish($el.find(".level-name"))
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
issue.revert()
|
||||
$model.$setViewValue(issue)
|
||||
$loading.finish($el.find(".level-name"))
|
||||
$loading.start($el.find(".level-name"))
|
||||
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (issue) ->
|
||||
render(issue) if issue
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgIssueSeverityButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", IssueSeverityButtonDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Issue priority button directive
|
||||
#############################################################################
|
||||
|
||||
IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
||||
# Display the priority of Issue and you can edit it.
|
||||
#
|
||||
# Example:
|
||||
# tg-issue-priority-button(ng-model="issue")
|
||||
#
|
||||
# Requirements:
|
||||
# - Issue object (ng-model)
|
||||
# - scope.priorityById object
|
||||
# - $scope.project.my_permissions
|
||||
|
||||
template = _.template("""
|
||||
<div class="priority-data <% if(editable){ %>clickable<% }%>">
|
||||
<span class="level" style="background-color:<%- priority.color %>"></span>
|
||||
<span class="priority-priority"><%- priority.name %></span>
|
||||
<% if(editable){ %><span class="icon icon-arrow-bottom"></span><% }%>
|
||||
<span class="level-name">priority</span>
|
||||
|
||||
<ul class="popover pop-priority">
|
||||
<% _.each(priorityes, function(pr) { %>
|
||||
<li><a href="" class="priority" title="<%- pr.name %>"
|
||||
data-priority-id="<%- pr.id %>"><%- pr.name %></a></li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
</div>
|
||||
""") #TODO: i18n
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
isEditable = ->
|
||||
return $scope.project.my_permissions.indexOf("modify_issue") != -1
|
||||
|
||||
render = (issue) =>
|
||||
priority = $scope.priorityById[issue.priority]
|
||||
|
||||
html = template({
|
||||
priority: priority
|
||||
priorityes: $scope.priorityList
|
||||
editable: isEditable()
|
||||
})
|
||||
$el.html(html)
|
||||
|
||||
$el.on "click", ".priority-data", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return if not isEditable()
|
||||
|
||||
$el.find(".pop-priority").popover().open()
|
||||
|
||||
$el.on "click", ".priority", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return if not isEditable()
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
|
||||
$.fn.popover().closeAll()
|
||||
|
||||
issue = $model.$modelValue.clone()
|
||||
issue.priority = target.data("priority-id")
|
||||
$model.$setViewValue(issue)
|
||||
|
||||
$scope.$apply()
|
||||
|
||||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("history:reload")
|
||||
$loading.finish($el.find(".level-name"))
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
issue.revert()
|
||||
$model.$setViewValue(issue)
|
||||
$loading.finish($el.find(".level-name"))
|
||||
$loading.start($el.find(".level-name"))
|
||||
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (issue) ->
|
||||
render(issue) if issue
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgIssuePriorityButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", IssuePriorityButtonDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
|
@ -386,7 +573,7 @@ module.directive("tgIssueStatus", IssueStatusDirective)
|
|||
|
||||
PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm) ->
|
||||
template = _.template("""
|
||||
<a class="button button-gray clickable" tg-check-permission="add_us">
|
||||
<a class="button button-gray editable" tg-check-permission="add_us">
|
||||
Promote to User Story
|
||||
</a>
|
||||
""") # TODO: i18n
|
||||
|
|
|
@ -74,11 +74,7 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
|||
tgLoader.pageLoaded()
|
||||
|
||||
# On Error
|
||||
promise.then null, (xhr) =>
|
||||
if xhr and xhr.status == 404
|
||||
@location.path(@navUrls.resolve("not-found"))
|
||||
@location.replace()
|
||||
return @q.reject(xhr)
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
@scope.$on "issueform:new:success", =>
|
||||
@analytics.trackEvent("issue", "create", "create issue on issues list", 1)
|
||||
|
@ -323,11 +319,11 @@ paginatorTemplate = """
|
|||
<% } %>
|
||||
|
||||
<% _.each(pages, function(item) { %>
|
||||
<li class="<%= item.classes %>">
|
||||
<li class="<%- item.classes %>">
|
||||
<% if (item.type === "page") { %>
|
||||
<a href="" data-pagenum="<%= item.num %>"><%= item.num %></a>
|
||||
<a href="" data-pagenum="<%- item.num %>"><%- item.num %></a>
|
||||
<% } else if (item.type === "page-active") { %>
|
||||
<span class="active"><%= item.num %></span>
|
||||
<span class="active"><%- item.num %></span>
|
||||
<% } else { %>
|
||||
<span>...</span>
|
||||
<% } %>
|
||||
|
@ -473,8 +469,8 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading) ->
|
|||
<% _.each(filters, function(f) { %>
|
||||
<% if (!f.selected) { %>
|
||||
<a class="single-filter"
|
||||
data-type="<%= f.type %>"
|
||||
data-id="<%= f.id %>">
|
||||
data-type="<%- f.type %>"
|
||||
data-id="<%- f.id %>">
|
||||
<span class="name" <% if (f.color){ %>style="border-left: 3px solid <%- f.color %>;"<% } %>>
|
||||
<%- f.name %>
|
||||
</span>
|
||||
|
@ -495,9 +491,9 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading) ->
|
|||
templateSelected = _.template("""
|
||||
<% _.each(filters, function(f) { %>
|
||||
<a class="single-filter selected"
|
||||
data-type="<%= f.type %>"
|
||||
data-id="<%= f.id %>">
|
||||
<span class="name" <% if (f.color){ %>style="border-left: 3px solid <%= f.color %>;"<% } %>>
|
||||
data-type="<%- f.type %>"
|
||||
data-id="<%- f.id %>">
|
||||
<span class="name" <% if (f.color){ %>style="border-left: 3px solid <%- f.color %>;"<% } %>>
|
||||
<%- f.name %>
|
||||
</span>
|
||||
<span class="icon icon-delete"></span>
|
||||
|
@ -512,14 +508,14 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading) ->
|
|||
|
||||
showFilters = (title, type) ->
|
||||
$el.find(".filters-cats").hide()
|
||||
$el.find(".filter-list").show()
|
||||
$el.find(".filter-list").removeClass("hidden")
|
||||
$el.find("h2.breadcrumb").removeClass("hidden")
|
||||
$el.find("h2 a.subfilter span.title").html(title)
|
||||
$el.find("h2 a.subfilter span.title").prop("data-type", type)
|
||||
|
||||
showCategories = ->
|
||||
$el.find(".filters-cats").show()
|
||||
$el.find(".filter-list").hide()
|
||||
$el.find(".filter-list").addClass("hidden")
|
||||
$el.find("h2.breadcrumb").addClass("hidden")
|
||||
|
||||
initializeSelectedFilters = (filters) ->
|
||||
|
@ -555,9 +551,10 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading) ->
|
|||
initializeSelectedFilters($scope.filters)
|
||||
return null
|
||||
|
||||
|
||||
filters = $scope.filters[type]
|
||||
filter = _.find(filters, {id:id})
|
||||
filterId = if type == 'tags' then taiga.toString(id) else id
|
||||
filter = _.find(filters, {id: filterId})
|
||||
|
||||
filter.selected = (not filter.selected)
|
||||
|
||||
# Convert id to null as string for properly
|
||||
|
@ -642,9 +639,9 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading) ->
|
|||
target = angular.element(event.currentTarget)
|
||||
customFilterName = target.parent().data('id')
|
||||
title = "Delete custom filter" # TODO: i18n
|
||||
subtitle = "the custom filter '#{customFilterName}'" # TODO: i18n
|
||||
message = "the custom filter '#{customFilterName}'" # TODO: i18n
|
||||
|
||||
$confirm.ask(title, subtitle).then (finish) ->
|
||||
$confirm.askOnDelete(title, message).then (finish) ->
|
||||
promise = $ctrl.deleteMyFilter(customFilterName)
|
||||
promise.then ->
|
||||
promise = $ctrl.loadMyFilters()
|
||||
|
@ -791,7 +788,7 @@ module.directive("tgIssueStatusInlineEdition", ["$tgRepo", IssueStatusInlineEdit
|
|||
|
||||
IssueAssignedToInlineEditionDirective = ($repo, $rootscope, popoverService) ->
|
||||
template = _.template("""
|
||||
<img src="<%= imgurl %>" alt="<%- name %>"/>
|
||||
<img src="<%- imgurl %>" alt="<%- name %>"/>
|
||||
<figcaption><%- name %></figcaption>
|
||||
""")
|
||||
|
||||
|
|
|
@ -78,12 +78,7 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
|||
tgLoader.pageLoaded()
|
||||
|
||||
# On Error
|
||||
promise.then null, (xhr) =>
|
||||
if xhr and xhr.status == 404
|
||||
@location.path(@navUrls.resolve("not-found"))
|
||||
@location.replace()
|
||||
return @q.reject(xhr)
|
||||
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
initializeEventHandlers: ->
|
||||
@scope.$on "usform:new:success", =>
|
||||
|
@ -397,7 +392,7 @@ KanbanUserDirective = ($log) ->
|
|||
template = _.template("""
|
||||
<figure class="avatar">
|
||||
<a href="#" title="Assign User Story" <% if (!clickable) {%>class="not-clickable"<% } %>>
|
||||
<img src="<%= imgurl %>" alt="<%- name %>" class="avatar">
|
||||
<img src="<%- imgurl %>" alt="<%- name %>" class="avatar">
|
||||
</a>
|
||||
</figure>
|
||||
""") # TODO: i18n
|
||||
|
|
|
@ -272,7 +272,7 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $
|
|||
<li><a href="" title="Logout" class="logout">Logout</a></li>
|
||||
</ul>
|
||||
<a href="" title="User preferences" class="avatar" id="nav-user-settings">
|
||||
<img src="<%= user.photo %>" alt="<%= user.full_name_display %>" />
|
||||
<img src="<%- user.photo %>" alt="<%- user.full_name_display %>" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -281,41 +281,30 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $
|
|||
|
||||
mainTemplate = _.template("""
|
||||
<div class="logo-container logo">
|
||||
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 134.2 134.3" version="1.1" preserveAspectRatio="xMidYMid meet" shape-rendering="geometricPrecision">
|
||||
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" version="1.1" preserveAspectRatio="xMidYMid meet">
|
||||
<style>
|
||||
svg {
|
||||
transform: scale(.99);
|
||||
}
|
||||
path {
|
||||
fill:#f5f5f5;
|
||||
opacity:0.7;
|
||||
}
|
||||
.moustache {
|
||||
fill:#000;
|
||||
}
|
||||
</style>
|
||||
<g transform="translate(-307.87667,-465.22863)">
|
||||
<g class="bottom">
|
||||
<path transform="matrix(-0.14066483,0.99005727,-0.99005727,0.14066483,0,0)"
|
||||
d="m561.8-506.6 42 0 0 42-42 0z" />
|
||||
<path transform="matrix(0.14066483,-0.99005727,0.99005727,-0.14066483,0,0)"
|
||||
d="m-645.7 422.6 42 0 0 42-42 0z" />
|
||||
<path transform="matrix(0.99005727,0.14066483,0.14066483,0.99005727,0,0)"
|
||||
d="m266.6 451.9 42 0 0 42-42 0z" />
|
||||
<path transform="matrix(-0.99005727,-0.14066483,-0.14066483,-0.99005727,0,0)"
|
||||
d="m-350.6-535.9 42 0 0 42-42 0z" />
|
||||
</g>
|
||||
<g class="top">
|
||||
<path transform="matrix(-0.60061118,-0.79954125,0.60061118,-0.79954125,0,0)"
|
||||
d="m-687.1-62.7 42 0 0 42-42 0z" />
|
||||
<path transform="matrix(-0.79954125,0.60061118,-0.79954125,-0.60061118,0,0)"
|
||||
d="m166.6-719.6 42 0 0 42-42 0z" />
|
||||
<path transform="matrix(0.60061118,0.79954125,-0.60061118,0.79954125,0,0)"
|
||||
d="m603.1-21.3 42 0 0 42-42 0z" />
|
||||
<path transform="matrix(0.79954125,-0.60061118,0.79954125,0.60061118,0,0)"
|
||||
d="m-250.7 635.8 42 0 0 42-42 0z" />
|
||||
<path transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)"
|
||||
d="m630.3 100 22.6 0 0 22.6-22.6 0z" />
|
||||
<g transform="translate(0,-52.362183)">
|
||||
<g transform="matrix(1.1783562,0,0,1.1783562,2450.4425,-1298.9778)">
|
||||
<path transform="matrix(-0.1406648,0.99005728,-0.99005728,0.14066481,0,0)" d="m1114.3 1212.6 263 0 0 263-263 0z" />
|
||||
<path transform="matrix(0.1406648,-0.99005728,0.99005728,-0.14066481,0,0)" d="m-1640.3-1738.9 263 0 0 263-263 0z" />
|
||||
<path transform="matrix(0.99005728,0.1406648,0.14066481,0.99005728,0,0)" d="m-2199.2 1599.4 263 0 0 263-263 0z" />
|
||||
<path transform="matrix(-0.99005728,-0.1406648,-0.14066481,-0.99005728,0,0)" d="m1673.2-2125.1 263 0 0 263-263 0z" />
|
||||
<path transform="matrix(-0.60061118,-0.79954125,0.60061117,-0.79954125,0,0)" d="m132.1-2623.1 263 0 0 263-263 0z" />
|
||||
<path transform="matrix(-0.79954125,0.60061118,-0.79954125,-0.60061117,0,0)" d="m2079.7-535.4 263 0 0 263-263 0z" />
|
||||
<path transform="matrix(0.60061118,0.79954125,-0.60061118,0.79954125,0,0)" d="m-658.4 2097.4 263 0 0 263-263 0z" />
|
||||
<path transform="matrix(0.79954125,-0.60061118,0.79954125,0.60061117,0,0)" d="m-2606.2 10.1 263 0 0 263-263 0z" />
|
||||
<path transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)" d="m-130.2 2210.7 141.4 0 0 141.4-141.4 0z" />
|
||||
</g>
|
||||
</g>
|
||||
<path class="moustache" d="m197.1 669.9c0 0-11.5 83.8 58.5 113.8 60.9 26.1 109.3 13 144.4 0.7 35.1-12.4 98.9-66.3 98.9-66.3 0 0 92.3 74.1 154.1 80.6 61.8 6.5 100.4-9.5 119-28 45.1-44.9 34.5-102.8 34.5-102.8 0 0-43.6 31.9-88.4 11.7-44.9-20.2-63.7-73.6-109.8-90.5-46.2-16.9-78.1 0.8-91.8 13.1-13.7 12.4-15.6 16.9-15.6 16.9 0 0-28-31.2-63.1-33.8-35.1-2.6-59.8 15.6-91 46.2-31.2 30.6-48.1 54-83.9 55.3-35.8 1.3-65.7-16.9-65.7-16.9z" />
|
||||
</svg>
|
||||
<span class="item">taiga<sup>[beta]</sup></span>
|
||||
</div>
|
||||
|
|
|
@ -26,7 +26,7 @@ debounce = @.taiga.debounce
|
|||
|
||||
module = angular.module("taigaProject")
|
||||
|
||||
CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $projectUrl, lightboxService) ->
|
||||
CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $projectUrl, lightboxService, $cacheFactory) ->
|
||||
link = ($scope, $el, attrs) ->
|
||||
$scope.data = {}
|
||||
$scope.templates = []
|
||||
|
@ -34,6 +34,11 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
|
|||
form = $el.find("form").checksley({"onlyOneErrorElement": true})
|
||||
|
||||
onSuccessSubmit = (response) ->
|
||||
# remove all $http cache
|
||||
# This is necessary when a project is created with the same name
|
||||
# than another deleted in the same session
|
||||
$cacheFactory.get('$http').removeAll()
|
||||
|
||||
$rootscope.$broadcast("projects:reload")
|
||||
$confirm.notify("success", "Success") #TODO: i18n
|
||||
$location.url($projectUrl.get(response))
|
||||
|
@ -116,7 +121,7 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
|
|||
return {link:link}
|
||||
|
||||
module.directive("tgLbCreateProject", ["$rootScope", "$tgRepo", "$tgConfirm", "$location", "$tgNavUrls",
|
||||
"$tgResources", "$projectUrl", "lightboxService", CreateProject])
|
||||
"$tgResources", "$projectUrl", "lightboxService", "$cacheFactory", CreateProject])
|
||||
|
||||
|
||||
#############################################################################
|
||||
|
|
|
@ -53,11 +53,7 @@ class ProjectsController extends taiga.Controller
|
|||
@scope.$emit("projects:loaded")
|
||||
@tgLoader.pageLoaded()
|
||||
|
||||
promise.then null, (xhr) =>
|
||||
if xhr and xhr.status == 404
|
||||
@location.path(@navUrls.resolve("not-found"))
|
||||
@location.replace()
|
||||
return @q.reject(xhr)
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
loadInitialData: ->
|
||||
return @rs.projects.list().then (projects) =>
|
||||
|
@ -96,11 +92,7 @@ class ProjectController extends taiga.Controller
|
|||
promise.then () =>
|
||||
@appTitle.set(@scope.project.name)
|
||||
|
||||
promise.then null, (xhr) =>
|
||||
if xhr and xhr.status == 404
|
||||
@location.path(@navUrls.resolve("not-found"))
|
||||
@location.replace()
|
||||
return @q.reject(xhr)
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
loadInitialData: ->
|
||||
# Resolve project slug
|
||||
|
|
|
@ -53,7 +53,7 @@ RelatedTaskRowDirective = ($repo, $compile, $confirm, $rootscope, $loading) ->
|
|||
</a>
|
||||
</div>
|
||||
<div tg-related-task-assigned-to-inline-edition="task" class="assigned-to">
|
||||
<div title="Assigned to" class="task-assignedto">
|
||||
<div title="Assigned to" class="task-assignedto <% if(perms.modify_task) { %>editable<% } %>">
|
||||
<figure class="avatar"></figure>
|
||||
<% if(perms.modify_task) { %>
|
||||
<span class="icon icon-arrow-bottom"></span>
|
||||
|
@ -139,9 +139,9 @@ RelatedTaskRowDirective = ($repo, $compile, $confirm, $rootscope, $loading) ->
|
|||
#TODO: i18n
|
||||
task = $model.$modelValue
|
||||
title = "Delete Task"
|
||||
subtitle = task.subject
|
||||
message = task.subject
|
||||
|
||||
$confirm.ask(title, subtitle).then (finish) ->
|
||||
$confirm.askOnDelete(title, message).then (finish) ->
|
||||
promise = $repo.remove(task)
|
||||
promise.then ->
|
||||
finish()
|
||||
|
@ -166,7 +166,7 @@ RelatedTaskRowDirective = ($repo, $compile, $confirm, $rootscope, $loading) ->
|
|||
|
||||
return {link:link, require:"ngModel"}
|
||||
|
||||
module.directive("tgRelatedTaskRow", ["$tgRepo", "$compile", "$tgConfirm", "$rootScope", "$tgLoading", RelatedTaskRowDirective])
|
||||
module.directive("tgRelatedTaskRow", ["$tgRepo", "$compile", "$tgConfirm", "$rootScope", "$tgLoading", "$tgAnalytics", RelatedTaskRowDirective])
|
||||
|
||||
RelatedTaskCreateFormDirective = ($repo, $compile, $confirm, $tgmodel, $loading, $analytics) ->
|
||||
template = _.template("""
|
||||
|
@ -310,7 +310,7 @@ module.directive("tgRelatedTasks", ["$tgRepo", "$tgResources", "$rootScope", Rel
|
|||
|
||||
RelatedTaskAssignedToInlineEditionDirective = ($repo, $rootscope, popoverService) ->
|
||||
template = _.template("""
|
||||
<img src="<%= imgurl %>" alt="<%- name %>"/>
|
||||
<img src="<%- imgurl %>" alt="<%- name %>"/>
|
||||
<figcaption><%- name %></figcaption>
|
||||
""")
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ taiga = @.taiga
|
|||
sizeFormat = @.taiga.sizeFormat
|
||||
|
||||
|
||||
resourceProvider = ($rootScope, $urls, $model, $repo, $auth, $q) ->
|
||||
resourceProvider = ($rootScope, $config, $urls, $model, $repo, $auth, $q) ->
|
||||
service = {}
|
||||
|
||||
service.list = (urlName, objectId, projectId) ->
|
||||
|
@ -38,6 +38,16 @@ resourceProvider = ($rootScope, $urls, $model, $repo, $auth, $q) ->
|
|||
defered.reject(null)
|
||||
return defered.promise
|
||||
|
||||
maxFileSize = $config.get("maxUploadFileSize", null)
|
||||
if maxFileSize and file.size > maxFileSize
|
||||
response = {
|
||||
status: 413,
|
||||
data: _error_message: "'#{file.name}' (#{sizeFormat(file.size)}) is too heavy for our oompa
|
||||
loompas, try it with a smaller than (#{sizeFormat(maxFileSize)})"
|
||||
}
|
||||
defered.reject(response)
|
||||
return defered.promise
|
||||
|
||||
uploadProgress = (evt) =>
|
||||
$rootScope.$apply =>
|
||||
file.status = "in-progress"
|
||||
|
@ -83,5 +93,5 @@ resourceProvider = ($rootScope, $urls, $model, $repo, $auth, $q) ->
|
|||
|
||||
|
||||
module = angular.module("taigaResources")
|
||||
module.factory("$tgAttachmentsResourcesProvider", ["$rootScope", "$tgUrls", "$tgModel", "$tgRepo", "$tgAuth",
|
||||
"$q", resourceProvider])
|
||||
module.factory("$tgAttachmentsResourcesProvider", ["$rootScope", "$tgConfig", "$tgUrls", "$tgModel", "$tgRepo",
|
||||
"$tgAuth", "$q", resourceProvider])
|
||||
|
|
|
@ -42,9 +42,9 @@ resourceProvider = ($repo, $http, $urls) ->
|
|||
url = $urls.resolve("memberships")
|
||||
return $http.post("#{url}/#{id}/resend_invitation", {})
|
||||
|
||||
service.bulkCreateMemberships = (projectId, data) ->
|
||||
service.bulkCreateMemberships = (projectId, data, invitation_extra_text) ->
|
||||
url = $urls.resolve("bulk-create-memberships")
|
||||
params = {project_id: projectId, bulk_memberships: data}
|
||||
params = {project_id: projectId, bulk_memberships: data, invitation_extra_text: invitation_extra_text}
|
||||
return $http.post(url, params)
|
||||
|
||||
return (instance) ->
|
||||
|
|
|
@ -45,9 +45,6 @@ resourceProvider = ($repo) ->
|
|||
service.stats = (projectId) ->
|
||||
return $repo.queryOneRaw("projects", "#{projectId}/stats")
|
||||
|
||||
service.tags = (projectId) ->
|
||||
return $repo.queryOneRaw("projects", "#{projectId}/tags")
|
||||
|
||||
service.tagsColors = (id) ->
|
||||
return $repo.queryOne("projects", "#{id}/tags_colors")
|
||||
|
||||
|
|
|
@ -21,13 +21,26 @@
|
|||
|
||||
|
||||
taiga = @.taiga
|
||||
sizeFormat = @.taiga.sizeFormat
|
||||
|
||||
resourceProvider = ($repo, $http, $urls) ->
|
||||
|
||||
resourceProvider = ($config, $repo, $http, $urls, $q) ->
|
||||
service = {}
|
||||
|
||||
service.changeAvatar = (attachmentModel) ->
|
||||
service.changeAvatar = (file) ->
|
||||
maxFileSize = $config.get("maxUploadFileSize", null)
|
||||
if maxFileSize and file.size > maxFileSize
|
||||
response = {
|
||||
status: 413,
|
||||
data: _error_message: "'#{file.name}' (#{sizeFormat(file.size)}) is too heavy for our oompa
|
||||
loompas, try it with a smaller than (#{sizeFormat(maxFileSize)})"
|
||||
}
|
||||
defered = $q.defer()
|
||||
defered.reject(response)
|
||||
return defered.promise
|
||||
|
||||
data = new FormData()
|
||||
data.append('avatar', attachmentModel)
|
||||
data.append('avatar', file)
|
||||
options = {
|
||||
transformRequest: angular.identity,
|
||||
headers: {'Content-Type': undefined}
|
||||
|
@ -52,4 +65,5 @@ resourceProvider = ($repo, $http, $urls) ->
|
|||
|
||||
|
||||
module = angular.module("taigaResources")
|
||||
module.factory("$tgUserSettingsResourcesProvider", ["$tgRepo", "$tgHttp", "$tgUrls", resourceProvider])
|
||||
module.factory("$tgUserSettingsResourcesProvider", ["$tgConfig", "$tgRepo", "$tgHttp", "$tgUrls", "$q",
|
||||
resourceProvider])
|
||||
|
|
|
@ -55,11 +55,7 @@ class SearchController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
promise.then () =>
|
||||
@appTitle.set("Search")
|
||||
|
||||
promise.then null, (xhr) =>
|
||||
if xhr and xhr.status == 404
|
||||
@location.path(@navUrls.resolve("not-found"))
|
||||
@location.replace()
|
||||
return @q.reject(xhr)
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
# Search input watcher
|
||||
@scope.searchTerm = ""
|
||||
|
|
|
@ -25,7 +25,7 @@ debounce = @.taiga.debounce
|
|||
|
||||
CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, lightboxService) ->
|
||||
link = ($scope, $el, attrs) ->
|
||||
isNew = true
|
||||
$scope.isNew = true
|
||||
|
||||
$scope.$on "taskform:new", (ctx, sprintId, usId) ->
|
||||
$scope.task = {
|
||||
|
@ -37,7 +37,7 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, lightboxService) ->
|
|||
assigned_to: null
|
||||
tags: []
|
||||
}
|
||||
isNew = true
|
||||
$scope.isNew = true
|
||||
|
||||
# Update texts for creation
|
||||
$el.find(".button-green span").html("Create") #TODO: i18n
|
||||
|
@ -46,7 +46,7 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, lightboxService) ->
|
|||
|
||||
$scope.$on "taskform:edit", (ctx, task) ->
|
||||
$scope.task = task
|
||||
isNew = false
|
||||
$scope.isNew = false
|
||||
|
||||
# Update texts for edition
|
||||
$el.find(".button-green span").html("Save") #TODO: i18n
|
||||
|
@ -60,7 +60,7 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, lightboxService) ->
|
|||
if not form.validate()
|
||||
return
|
||||
|
||||
if isNew
|
||||
if $scope.isNew
|
||||
promise = $repo.create("tasks", $scope.task)
|
||||
broadcastEvent = "taskform:new:success"
|
||||
else
|
||||
|
|
|
@ -66,11 +66,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
tgLoader.pageLoaded()
|
||||
|
||||
# On Error
|
||||
promise.then null, (xhr) =>
|
||||
if xhr and xhr.status == 404
|
||||
@location.path(@navUrls.resolve("not-found"))
|
||||
@location.replace()
|
||||
return @q.reject(xhr)
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
initializeEventHandlers: ->
|
||||
# TODO: Reload entire taskboard after create/edit tasks seems
|
||||
|
@ -320,7 +316,7 @@ TaskboardUserDirective = ($log) ->
|
|||
template = _.template("""
|
||||
<figure class="avatar">
|
||||
<a href="#" title="Assign task" <% if (!clickable) {%>class="not-clickable"<% } %>>
|
||||
<img src="<%= imgurl %>" alt="<%- name %>">
|
||||
<img src="<%- imgurl %>" alt="<%- name %>">
|
||||
</a>
|
||||
</figure>
|
||||
""") # TODO: i18n
|
||||
|
|
|
@ -57,13 +57,10 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
|
||||
promise.then () =>
|
||||
@appTitle.set(@scope.task.subject + " - " + @scope.project.name)
|
||||
@.initializeOnDeleteGoToUrl()
|
||||
tgLoader.pageLoaded()
|
||||
|
||||
promise.then null, (xhr) =>
|
||||
if xhr and xhr.status == 404
|
||||
@location.path(@navUrls.resolve("not-found"))
|
||||
@location.replace()
|
||||
return @q.reject(xhr)
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
initializeEventHandlers: ->
|
||||
@scope.$on "attachment:create", =>
|
||||
|
@ -74,6 +71,21 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
@scope.$on "attachment:delete", =>
|
||||
@rootscope.$broadcast("history:reload")
|
||||
|
||||
initializeOnDeleteGoToUrl: ->
|
||||
ctx = {project: @scope.project.slug}
|
||||
@scope.onDeleteGoToUrl = @navUrls.resolve("project", ctx)
|
||||
if @scope.project.is_backlog_activated
|
||||
if @scope.task.milestone
|
||||
ctx.sprint = @scope.sprint.slug
|
||||
@scope.onDeleteGoToUrl = @navUrls.resolve("project-taskboard", ctx)
|
||||
else if @scope.task.us
|
||||
ctx.ref = @scope.us.ref
|
||||
@scope.onDeleteGoToUrl = @navUrls.resolve("project-userstories-detail", ctx)
|
||||
else if @scope.project.is_kanban_activated
|
||||
if @scope.us
|
||||
ctx.ref = @scope.us.ref
|
||||
@scope.onDeleteGoToUrl = @navUrls.resolve("project-userstories-detail", ctx)
|
||||
|
||||
loadProject: ->
|
||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||
@scope.project = project
|
||||
|
@ -101,14 +113,19 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
ref: @scope.task.neighbors.next.ref
|
||||
}
|
||||
@scope.nextUrl = @navUrls.resolve("project-tasks-detail", ctx)
|
||||
return task
|
||||
|
||||
if task.milestone
|
||||
@rs.sprints.get(task.project, task.milestone).then (sprint) =>
|
||||
loadSprint: ->
|
||||
if @scope.task.milestone
|
||||
return @rs.sprints.get(@scope.task.project, @scope.task.milestone).then (sprint) =>
|
||||
@scope.sprint = sprint
|
||||
return sprint
|
||||
|
||||
if task.user_story
|
||||
@rs.userstories.get(task.project, task.user_story).then (us) =>
|
||||
loadUserStory: ->
|
||||
if @scope.task.user_story
|
||||
return @rs.userstories.get(@scope.task.project, @scope.task.user_story).then (us) =>
|
||||
@scope.us = us
|
||||
return us
|
||||
|
||||
loadInitialData: ->
|
||||
params = {
|
||||
|
@ -123,152 +140,216 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
|
||||
return promise.then(=> @.loadProject())
|
||||
.then(=> @.loadUsersAndRoles())
|
||||
.then(=> @.loadTask())
|
||||
|
||||
block: ->
|
||||
@rootscope.$broadcast("block", @scope.task)
|
||||
|
||||
unblock: ->
|
||||
@rootscope.$broadcast("unblock", @scope.task)
|
||||
|
||||
delete: ->
|
||||
#TODO: i18n
|
||||
title = "Delete Task"
|
||||
subtitle = @scope.task.subject
|
||||
|
||||
@confirm.ask(title, subtitle).then (finish) =>
|
||||
promise = @.repo.remove(@scope.task)
|
||||
promise.then =>
|
||||
finish()
|
||||
@location.path(@navUrls.resolve("project-backlog", {project: @scope.project.slug}))
|
||||
promise.then null, =>
|
||||
finish(false)
|
||||
@confirm.notify("error")
|
||||
.then(=> @.loadTask().then(=> @q.all([@.loadUserStory(),
|
||||
@.loadSprint()])))
|
||||
|
||||
module.controller("TaskDetailController", TaskDetailController)
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Task Main Directive
|
||||
## Task status display directive
|
||||
#############################################################################
|
||||
|
||||
TaskDirective = ($tgrepo, $log, $location, $confirm, $navUrls, $loading) ->
|
||||
linkSidebar = ($scope, $el, $attrs, $ctrl) ->
|
||||
TaskStatusDisplayDirective = ->
|
||||
# Display if a Task is open or closed and its taskboard status.
|
||||
#
|
||||
# Example:
|
||||
# tg-task-status-display(ng-model="task")
|
||||
#
|
||||
# Requirements:
|
||||
# - Task object (ng-model)
|
||||
# - scope.statusById object
|
||||
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$ctrl = $el.controller()
|
||||
linkSidebar($scope, $el, $attrs, $ctrl)
|
||||
|
||||
if $el.is("form")
|
||||
form = $el.checksley()
|
||||
|
||||
$el.on "click", ".save-task", (event) ->
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
onSuccess = ->
|
||||
$loading.finish(target)
|
||||
$confirm.notify("success")
|
||||
ctx = {
|
||||
project: $scope.project.slug
|
||||
ref: $scope.task.ref
|
||||
}
|
||||
$location.path($navUrls.resolve("project-tasks-detail", ctx))
|
||||
|
||||
onError = ->
|
||||
$loading.finish(target)
|
||||
$confirm.notify("error")
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
$loading.start(target)
|
||||
$tgrepo.save($scope.task).then(onSuccess, onError)
|
||||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgTaskDetail", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm", "$tgNavUrls",
|
||||
"$tgLoading", TaskDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Task status directive
|
||||
#############################################################################
|
||||
|
||||
TaskStatusDirective = () ->
|
||||
#TODO: i18n
|
||||
template = _.template("""
|
||||
<h1>
|
||||
<span>
|
||||
<% if (status.is_closed) { %>
|
||||
Closed
|
||||
<% } else { %>
|
||||
Open
|
||||
<% } %>
|
||||
<span class="us-detail-status" style="color:<%= status.color %>"><%= status.name %></span>
|
||||
</h1>
|
||||
<div class="us-created-by">
|
||||
<div class="user-avatar">
|
||||
<img src="<%= owner.photo %>" alt="<%- owner.full_name_display %>" />
|
||||
</div>
|
||||
</span>
|
||||
<span class="us-detail-status" style="color:<%- status.color %>">
|
||||
<%- status.name %>
|
||||
</span>
|
||||
""") # TODO: i18n
|
||||
|
||||
<div class="created-by">
|
||||
<span class="created-title">Created by <%- owner.full_name_display %></span>
|
||||
<span class="created-date"><%- date %></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="issue-data">
|
||||
<div class="status-data <% if (editable) { %>clickable<% } %>">
|
||||
<span class="level" style="background-color:<%= status.color %>"></span>
|
||||
<span class="status-status"><%= status.name %></span>
|
||||
<% if (editable) { %>
|
||||
<span class="icon icon-arrow-bottom"></span>
|
||||
<% } %>
|
||||
<span class="level-name">status</span>
|
||||
</div>
|
||||
</div>
|
||||
""")
|
||||
selectionStatusTemplate = _.template("""
|
||||
<ul class="popover pop-status">
|
||||
<% _.each(statuses, function(status) { %>
|
||||
<li><a href="" class="status" title="<%- status.name %>"
|
||||
data-status-id="<%- status.id %>"><%- status.name %></a></li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
""")
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
editable = $attrs.editable?
|
||||
|
||||
renderTaskstatus = (task) ->
|
||||
owner = $scope.usersById?[task.owner]
|
||||
date = moment(task.created_date).format("DD MMM YYYY HH:mm")
|
||||
status = $scope.statusById[task.status]
|
||||
link = ($scope, $el, $attrs) ->
|
||||
render = (task) ->
|
||||
html = template({
|
||||
owner: owner
|
||||
date: date
|
||||
editable: editable
|
||||
status: status
|
||||
status: $scope.statusById[task.status]
|
||||
})
|
||||
$el.html(html)
|
||||
$el.find(".status-data").append(selectionStatusTemplate({statuses:$scope.statusList}))
|
||||
|
||||
$scope.$watch $attrs.ngModel, (task) ->
|
||||
if task?
|
||||
renderTaskstatus(task)
|
||||
render(task) if task?
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgTaskStatusDisplay", TaskStatusDisplayDirective)
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Task status button directive
|
||||
#############################################################################
|
||||
|
||||
TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
||||
# Display the status of Task and you can edit it.
|
||||
#
|
||||
# Example:
|
||||
# tg-task-status-button(ng-model="task")
|
||||
#
|
||||
# Requirements:
|
||||
# - Task object (ng-model)
|
||||
# - scope.statusById object
|
||||
# - $scope.project.my_permissions
|
||||
|
||||
template = _.template("""
|
||||
<div class="status-data <% if(editable){ %>clickable<% }%>">
|
||||
<span class="level" style="background-color:<%- status.color %>"></span>
|
||||
<span class="status-status"><%- status.name %></span>
|
||||
<% if(editable){ %><span class="icon icon-arrow-bottom"></span><% }%>
|
||||
<span class="level-name">status</span>
|
||||
|
||||
<ul class="popover pop-status">
|
||||
<% _.each(statuses, function(st) { %>
|
||||
<li><a href="" class="status" title="<%- st.name %>"
|
||||
data-status-id="<%- st.id %>"><%- st.name %></a></li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
</div>
|
||||
""") #TODO: i18n
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
isEditable = ->
|
||||
return $scope.project.my_permissions.indexOf("modify_task") != -1
|
||||
|
||||
render = (task) =>
|
||||
status = $scope.statusById[task.status]
|
||||
|
||||
html = template({
|
||||
status: status
|
||||
statuses: $scope.statusList
|
||||
editable: isEditable()
|
||||
})
|
||||
$el.html(html)
|
||||
|
||||
if editable
|
||||
$el.on "click", ".status-data", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return if not isEditable()
|
||||
|
||||
$el.find(".pop-status").popover().open()
|
||||
|
||||
$el.on "click", ".status", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return if not isEditable()
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
$model.$modelValue.status = target.data("status-id")
|
||||
renderTaskstatus($model.$modelValue)
|
||||
$el.find(".popover").popover().close()
|
||||
|
||||
return {link:link, require:"ngModel"}
|
||||
$.fn.popover().closeAll()
|
||||
|
||||
module.directive("tgTaskStatus", TaskStatusDirective)
|
||||
task = $model.$modelValue.clone()
|
||||
task.status = target.data("status-id")
|
||||
$model.$setViewValue(task)
|
||||
|
||||
$scope.$apply()
|
||||
|
||||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("history:reload")
|
||||
$loading.finish($el.find(".level-name"))
|
||||
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
task.revert()
|
||||
$model.$setViewValue(task)
|
||||
$loading.finish($el.find(".level-name"))
|
||||
|
||||
$loading.start($el.find(".level-name"))
|
||||
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (task) ->
|
||||
render(task) if task
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgTaskStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading",
|
||||
TaskStatusButtonDirective])
|
||||
|
||||
|
||||
TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
|
||||
template = _.template("""
|
||||
<fieldset title="Feeling a bit overwhelmed by a task? Make sure others know about it by clicking on Iocaine when editing a task. It's possible to become immune to this (fictional) deadly poison by consuming small amounts over time just as it's possible to get better at what you do by occasionally taking on extra challenges!">
|
||||
<label for="is-iocaine"
|
||||
class="button button-gray is-iocaine <% if(isEditable){ %>editable<% }; %> <% if(isIocaine){ %>active<% }; %>">
|
||||
Iocaine
|
||||
</label>
|
||||
<input type="checkbox" id="is-iocaine" name="is-iocaine"/>
|
||||
</fieldset>
|
||||
""")
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
isEditable = ->
|
||||
return $scope.project.my_permissions.indexOf("modify_task") != -1
|
||||
|
||||
render = (task) ->
|
||||
if not isEditable() and not task.is_iocaine
|
||||
$el.html("")
|
||||
return
|
||||
|
||||
ctx = {
|
||||
isIocaine: task.is_iocaine
|
||||
isEditable: isEditable()
|
||||
}
|
||||
html = template(ctx)
|
||||
$el.html(html)
|
||||
|
||||
$el.on "click", ".is-iocaine", (event) ->
|
||||
return if not isEditable()
|
||||
|
||||
task = $model.$modelValue.clone()
|
||||
task.is_iocaine = not task.is_iocaine
|
||||
$model.$setViewValue(task)
|
||||
$loading.start($el.find('label'))
|
||||
|
||||
promise = $tgrepo.save($model.$modelValue)
|
||||
promise.then ->
|
||||
$confirm.notify("success")
|
||||
$rootscope.$broadcast("history:reload")
|
||||
|
||||
promise.then null, ->
|
||||
task.revert()
|
||||
$model.$setViewValue(task)
|
||||
$confirm.notify("error")
|
||||
|
||||
promise.finally ->
|
||||
$loading.finish($el.find('label'))
|
||||
|
||||
$scope.$watch $attrs.ngModel, (task) ->
|
||||
render(task) if task
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgTaskIsIocaineButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", TaskIsIocaineButtonDirective])
|
||||
|
|
|
@ -51,11 +51,7 @@ class UserChangePasswordController extends mixOf(taiga.Controller, taiga.PageMix
|
|||
|
||||
promise = @.loadInitialData()
|
||||
|
||||
promise.then null, (xhr) =>
|
||||
if xhr and xhr.status == 404
|
||||
@location.path(@navUrls.resolve("not-found"))
|
||||
@location.replace()
|
||||
return @q.reject(xhr)
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
loadProject: ->
|
||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
taiga = @.taiga
|
||||
mixOf = @.taiga.mixOf
|
||||
sizeFormat = @.taiga.sizeFormat
|
||||
module = angular.module("taigaUserSettings")
|
||||
|
||||
|
||||
|
@ -32,6 +33,7 @@ class UserSettingsController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
@.$inject = [
|
||||
"$scope",
|
||||
"$rootScope",
|
||||
"$tgConfig",
|
||||
"$tgRepo",
|
||||
"$tgConfirm",
|
||||
"$tgResources",
|
||||
|
@ -42,18 +44,18 @@ class UserSettingsController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
"$tgAuth"
|
||||
]
|
||||
|
||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @auth) ->
|
||||
constructor: (@scope, @rootscope, @config, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @auth) ->
|
||||
@scope.sectionName = "User Profile" #i18n
|
||||
@scope.project = {}
|
||||
@scope.user = @auth.getUser()
|
||||
|
||||
maxFileSize = @config.get("maxUploadFileSize", null)
|
||||
if maxFileSize
|
||||
@scope.maxFileSizeMsg = "[Max, size: #{sizeFormat(maxFileSize)}" # TODO: i18n
|
||||
|
||||
promise = @.loadInitialData()
|
||||
|
||||
promise.then null, (xhr) =>
|
||||
if xhr and xhr.status == 404
|
||||
@location.path(@navUrls.resolve("not-found"))
|
||||
@location.replace()
|
||||
return @q.reject(xhr)
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
loadProject: ->
|
||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||
|
@ -115,6 +117,9 @@ module.directive("tgUserProfile", ["$tgConfirm", "$tgAuth", "$tgRepo", UserProf
|
|||
|
||||
UserAvatarDirective = ($auth, $model, $rs, $confirm) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
showSizeInfo = ->
|
||||
$el.find(".size-info").removeClass("hidden")
|
||||
|
||||
onSuccess = (response) ->
|
||||
user = $model.make_model("users", response.data)
|
||||
$auth.setUser(user)
|
||||
|
@ -124,6 +129,7 @@ UserAvatarDirective = ($auth, $model, $rs, $confirm) ->
|
|||
$confirm.notify('success')
|
||||
|
||||
onError = (response) ->
|
||||
showSizeInfo() if response.status == 413
|
||||
$el.find('.overlay').hide()
|
||||
$confirm.notify('error', response.data._error_message)
|
||||
|
||||
|
|
|
@ -51,11 +51,7 @@ class UserNotificationsController extends mixOf(taiga.Controller, taiga.PageMixi
|
|||
|
||||
promise = @.loadInitialData()
|
||||
|
||||
promise.then null, (xhr) =>
|
||||
if xhr and xhr.status == 404
|
||||
@location.path(@navUrls.resolve("not-found"))
|
||||
@location.replace()
|
||||
return @q.reject(xhr)
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
loadProject: ->
|
||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||
|
|
|
@ -59,16 +59,17 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
# On Success
|
||||
promise.then =>
|
||||
@appTitle.set(@scope.us.subject + " - " + @scope.project.name)
|
||||
@.initializeOnDeleteGoToUrl()
|
||||
tgLoader.pageLoaded()
|
||||
|
||||
# On Error
|
||||
promise.then null, (xhr) =>
|
||||
if xhr and xhr.status == 404
|
||||
@location.path(@navUrls.resolve("not-found"))
|
||||
@location.replace()
|
||||
return @q.reject(xhr)
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
initializeEventHandlers: ->
|
||||
@scope.$on "related-tasks:update", =>
|
||||
@.loadUs()
|
||||
@scope.tasks = _.clone(@scope.tasks, false)
|
||||
|
||||
@scope.$on "attachment:create", =>
|
||||
@analytics.trackEvent("attachment", "create", "create attachment on userstory", 1)
|
||||
@rootscope.$broadcast("history:reload")
|
||||
|
@ -79,6 +80,18 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
@scope.$on "attachment:delete", =>
|
||||
@rootscope.$broadcast("history:reload")
|
||||
|
||||
initializeOnDeleteGoToUrl: ->
|
||||
ctx = {project: @scope.project.slug}
|
||||
@scope.onDeleteGoToUrl = @navUrls.resolve("project", ctx)
|
||||
if @scope.project.is_backlog_activated
|
||||
if @scope.us.milestone
|
||||
ctx.sprint = @scope.sprint.slug
|
||||
@scope.onDeleteGoToUrl = @navUrls.resolve("project-taskboard", ctx)
|
||||
else
|
||||
@scope.onDeleteGoToUrl = @navUrls.resolve("project-backlog", ctx)
|
||||
else if @scope.project.is_kanban_activated
|
||||
@scope.onDeleteGoToUrl = @navUrls.resolve("project-kanban", ctx)
|
||||
|
||||
loadProject: ->
|
||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||
@scope.project = project
|
||||
|
@ -110,12 +123,14 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
}
|
||||
@scope.nextUrl = @navUrls.resolve("project-userstories-detail", ctx)
|
||||
|
||||
if us.milestone
|
||||
@rs.sprints.get(us.project, us.milestone).then (sprint) =>
|
||||
@scope.sprint = sprint
|
||||
|
||||
return us
|
||||
|
||||
loadSprint: ->
|
||||
if @scope.us.milestone
|
||||
return @rs.sprints.get(@scope.us.project, @scope.us.milestone).then (sprint) =>
|
||||
@scope.sprint = sprint
|
||||
return sprint
|
||||
|
||||
loadTasks: ->
|
||||
return @rs.tasks.list(@scope.projectId, null, @scope.usId).then (tasks) =>
|
||||
@scope.tasks = tasks
|
||||
|
@ -134,257 +149,128 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
|
||||
return promise.then(=> @.loadProject())
|
||||
.then(=> @.loadUsersAndRoles())
|
||||
.then(=> @q.all([@.loadUs(),
|
||||
.then(=> @q.all([@.loadUs().then(=> @.loadSprint()),
|
||||
@.loadTasks()]))
|
||||
|
||||
block: ->
|
||||
@rootscope.$broadcast("block", @scope.us)
|
||||
|
||||
unblock: ->
|
||||
@rootscope.$broadcast("unblock", @scope.us)
|
||||
|
||||
delete: ->
|
||||
#TODO: i18n
|
||||
title = "Delete User Story"
|
||||
subtitle = @scope.us.subject
|
||||
|
||||
@confirm.ask(title, subtitle).then (finish) =>
|
||||
promise = @.repo.remove(@scope.us)
|
||||
promise.then =>
|
||||
finish()
|
||||
@location.path(@navUrls.resolve("project-backlog", {project: @scope.project.slug}))
|
||||
promise.then null, =>
|
||||
finish(false)
|
||||
$confirm.notify("error")
|
||||
|
||||
module.controller("UserStoryDetailController", UserStoryDetailController)
|
||||
|
||||
#############################################################################
|
||||
## User story Main Directive
|
||||
#############################################################################
|
||||
|
||||
UsDirective = ($tgrepo, $log, $location, $confirm, $navUrls, $loading) ->
|
||||
linkSidebar = ($scope, $el, $attrs, $ctrl) ->
|
||||
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$ctrl = $el.controller()
|
||||
linkSidebar($scope, $el, $attrs, $ctrl)
|
||||
|
||||
if $el.is("form")
|
||||
form = $el.checksley()
|
||||
|
||||
$el.on "click", ".save-us", (event) ->
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
onSuccess = ->
|
||||
$loading.finish(target)
|
||||
$confirm.notify("success")
|
||||
ctx = {
|
||||
project: $scope.project.slug
|
||||
ref: $scope.us.ref
|
||||
}
|
||||
$location.path($navUrls.resolve("project-userstories-detail", ctx))
|
||||
|
||||
onError = ->
|
||||
$loading.finish(target)
|
||||
$confirm.notify("error")
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
$loading.start(target)
|
||||
$tgrepo.save($scope.us).then(onSuccess, onError)
|
||||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgUsDetail", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm",
|
||||
"$tgNavUrls", "$tgLoading", UsDirective])
|
||||
|
||||
#############################################################################
|
||||
## User story status directive
|
||||
## User story status display directive
|
||||
#############################################################################
|
||||
|
||||
UsStatusDetailDirective = () ->
|
||||
#TODO: i18n
|
||||
UsStatusDisplayDirective = ->
|
||||
# Display if a US is open or closed and its kanban status.
|
||||
#
|
||||
# Example:
|
||||
# tg-us-status-display(ng-model="us")
|
||||
#
|
||||
# Requirements:
|
||||
# - US object (ng-model)
|
||||
# - scope.statusById object
|
||||
|
||||
template = _.template("""
|
||||
<h1>
|
||||
<span>
|
||||
<% if (is_closed) { %>
|
||||
Closed
|
||||
<% } else { %>
|
||||
Open
|
||||
<% } %>
|
||||
<span class="us-detail-status" style="color:<%= status.color %>"><%= status.name %></span>
|
||||
</h1>
|
||||
</span>
|
||||
<span class="us-detail-status" style="color:<%- status.color %>">
|
||||
<%- status.name %>
|
||||
</span>
|
||||
""") # TODO: i18n
|
||||
|
||||
<div class="us-detail-progress-bar">
|
||||
<div class="current-progress" style="width:<%- usProgress %>%"/>
|
||||
link = ($scope, $el, $attrs) ->
|
||||
render = (us) ->
|
||||
html = template({
|
||||
is_closed: us.is_closed
|
||||
status: $scope.statusById[us.status]
|
||||
})
|
||||
$el.html(html)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (us) ->
|
||||
render(us) if us?
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgUsStatusDisplay", UsStatusDisplayDirective)
|
||||
|
||||
|
||||
#############################################################################
|
||||
## User story related tasts progress splay Directive
|
||||
#############################################################################
|
||||
|
||||
UsTasksProgressDisplayDirective = ->
|
||||
# Display a progress bar with the stats of completed tasks.
|
||||
#
|
||||
# Example:
|
||||
# tg-us-tasks-progress-display(ng-model="tasks")
|
||||
#
|
||||
# Requirements:
|
||||
# - Task object list (ng-model)
|
||||
# - scope.taskStatusById object
|
||||
|
||||
template = _.template("""
|
||||
<div class="current-progress" style="width:<%- progress %>%" />
|
||||
<span clasS="tasks-completed">
|
||||
<%- totalClosedTasks %>/<%- totalTasks %> tasks completed
|
||||
</span>
|
||||
</div>
|
||||
""") # TODO: i18n
|
||||
|
||||
<div class="us-created-by">
|
||||
<div class="user-avatar">
|
||||
<img src="<%= owner.photo %>" alt="<%- owner.full_name_display %>" />
|
||||
</div>
|
||||
link = ($scope, $el, $attrs) ->
|
||||
render = (tasks) ->
|
||||
totalTasks = tasks.length
|
||||
totalClosedTasks = _.filter(tasks, (task) => $scope.taskStatusById[task.status].is_closed).length
|
||||
|
||||
<div class="created-by">
|
||||
<span class="created-title">Created by <%- owner.full_name_display %></span>
|
||||
<span class="created-date"><%- date %></span>
|
||||
</div>
|
||||
</div>
|
||||
progress = if totalTasks > 0 then 100 * totalClosedTasks / totalTasks else 0
|
||||
|
||||
<ul class="points-per-role">
|
||||
<li class="total">
|
||||
<span class="points"><%- totalPoints %></span>
|
||||
<span class="role">total</span>
|
||||
</li>
|
||||
<% _.each(rolePoints, function(rolePoint) { %>
|
||||
<li class="total <% if (editable) { %>clickable<% } %>" data-role-id="<%- rolePoint.id %>">
|
||||
<span class="points"><%- rolePoint.points %></span>
|
||||
<span class="role"><%- rolePoint.name %></span></li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
|
||||
<div class="issue-data">
|
||||
<div class="status-data <% if (editable) { %>clickable<% } %>">
|
||||
<span class="level" style="background-color:<%= status.color %>"></span>
|
||||
<span class="status-status"><%= status.name %></span>
|
||||
<% if (editable) { %>
|
||||
<span class="icon icon-arrow-bottom"></span>
|
||||
<% } %>
|
||||
<span class="level-name">status</span>
|
||||
</div>
|
||||
</div>
|
||||
""")
|
||||
selectionStatusTemplate = _.template("""
|
||||
<ul class="popover pop-status">
|
||||
<% _.each(statuses, function(status) { %>
|
||||
<li><a href="" class="status" title="<%- status.name %>"
|
||||
data-status-id="<%- status.id %>"><%- status.name %></a></li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
""")
|
||||
selectionPointsTemplate = _.template("""
|
||||
<ul class="popover pop-points-open">
|
||||
<% _.each(points, function(point) { %>
|
||||
<li><a href="" class="point" title="<%- point.name %>"
|
||||
data-point-id="<%- point.id %>"><%- point.name %></a>
|
||||
</li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
""")
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
editable = $attrs.editable?
|
||||
updatingSelectedRoleId = null
|
||||
$ctrl = $el.controller()
|
||||
|
||||
showSelectPoints = (target) ->
|
||||
us = $model.$modelValue
|
||||
$el.find(".pop-points-open").remove()
|
||||
$el.find(target).append(selectionPointsTemplate({ "points": $scope.project.points }))
|
||||
target.removeClass('active')
|
||||
$el.find(".pop-points-open a[data-point-id='#{us.points[updatingSelectedRoleId]}']").addClass("active")
|
||||
# If not showing role selection let's move to the left
|
||||
$el.find(".pop-points-open").popover().open()
|
||||
|
||||
calculateTotalPoints = (us)->
|
||||
values = _.map(us.points, (v, k) -> $scope.pointsById[v].value)
|
||||
values = _.filter(values, (num) -> num?)
|
||||
if values.length == 0
|
||||
return "?"
|
||||
|
||||
return _.reduce(values, (acc, num) -> acc + num)
|
||||
|
||||
renderUsstatus = (us) ->
|
||||
owner = $scope.usersById?[us.owner]
|
||||
date = moment(us.created_date).format("DD MMM YYYY HH:mm")
|
||||
status = $scope.statusById[us.status]
|
||||
rolePoints = _.clone(_.filter($scope.project.roles, "computable"), true)
|
||||
_.map rolePoints, (v, k) ->
|
||||
name = $scope.pointsById[us.points[v.id]].name
|
||||
name = "?" if not name?
|
||||
v.points = name
|
||||
|
||||
totalTasks = $scope.tasks.length
|
||||
totalClosedTasks = _.filter($scope.tasks, (task) => $scope.taskStatusById[task.status].is_closed).length
|
||||
usProgress = 0
|
||||
usProgress = 100 * totalClosedTasks / totalTasks if totalTasks > 0
|
||||
html = template({
|
||||
owner: owner
|
||||
date: date
|
||||
editable: editable
|
||||
is_closed: us.is_closed
|
||||
status: status
|
||||
totalPoints: us.total_points
|
||||
rolePoints: rolePoints
|
||||
totalTasks: totalTasks
|
||||
totalClosedTasks: totalClosedTasks
|
||||
usProgress: usProgress
|
||||
progress: progress
|
||||
})
|
||||
$el.html(html)
|
||||
$el.find(".status-data").append(selectionStatusTemplate({statuses:$scope.statusList}))
|
||||
|
||||
bindOnce $scope, "tasks", (tasks) ->
|
||||
$scope.$watch $attrs.ngModel, (us) ->
|
||||
if us?
|
||||
renderUsstatus(us)
|
||||
$scope.$watch $attrs.ngModel, (tasks) ->
|
||||
render(tasks) if tasks?
|
||||
|
||||
$scope.$on "related-tasks:update", ->
|
||||
us = $scope.$eval $attrs.ngModel
|
||||
if us?
|
||||
# Reload the us because the status could have changed
|
||||
$ctrl.loadUs()
|
||||
renderUsstatus(us)
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
if editable
|
||||
$el.on "click", ".status-data", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
$el.find(".pop-status").popover().open()
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
$el.on "click", ".status", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
target = angular.element(event.currentTarget)
|
||||
$model.$modelValue.status = target.data("status-id")
|
||||
renderUsstatus($model.$modelValue)
|
||||
$.fn.popover().closeAll()
|
||||
module.directive("tgUsTasksProgressDisplay", UsTasksProgressDisplayDirective)
|
||||
|
||||
$el.on "click", ".total.clickable", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
target = angular.element(event.currentTarget)
|
||||
updatingSelectedRoleId = target.data("role-id")
|
||||
target.siblings().removeClass('active')
|
||||
target.addClass('active')
|
||||
showSelectPoints(target)
|
||||
|
||||
$el.on "click", ".point", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
$.fn.popover().closeAll()
|
||||
|
||||
$scope.$apply () ->
|
||||
us = $model.$modelValue
|
||||
usPoints = _.clone(us.points, true)
|
||||
usPoints[updatingSelectedRoleId] = target.data("point-id")
|
||||
us.points = usPoints
|
||||
us.total_points = calculateTotalPoints(us)
|
||||
renderUsstatus(us)
|
||||
|
||||
return {link:link, require:"ngModel"}
|
||||
|
||||
module.directive("tgUsStatusDetail", UsStatusDetailDirective)
|
||||
|
||||
#############################################################################
|
||||
## User story estimation directive
|
||||
#############################################################################
|
||||
|
||||
UsEstimationDirective = ($log) ->
|
||||
UsEstimationDirective = ($rootScope, $repo, $confirm) ->
|
||||
# Display the points of a US and you can edit it.
|
||||
#
|
||||
# Example:
|
||||
# tg-us-estimation-progress-bar(ng-model="us")
|
||||
#
|
||||
# Requirements:
|
||||
# - Us object (ng-model)
|
||||
# - scope.project object
|
||||
# Optionals:
|
||||
# - save-after-modify (boolean): save object after modify
|
||||
|
||||
mainTemplate = _.template("""
|
||||
<ul class="points-per-role">
|
||||
<li class="total">
|
||||
|
@ -392,7 +278,7 @@ UsEstimationDirective = ($log) ->
|
|||
<span class="role">total</span>
|
||||
</li>
|
||||
<% _.each(roles, function(role) { %>
|
||||
<li class="total clickable" data-role-id="<%- role.id %>">
|
||||
<li class="total <% if(editable){ %>clickable<% } %>" data-role-id="<%- role.id %>">
|
||||
<span class="points"><%- role.points %></span>
|
||||
<span class="role"><%- role.name %></span></li>
|
||||
<% }); %>
|
||||
|
@ -415,7 +301,14 @@ UsEstimationDirective = ($log) ->
|
|||
</ul>
|
||||
""")
|
||||
|
||||
link = ($scope, $el, $attrs) ->
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
saveAfterModify = $attrs.saveAfterModify or false
|
||||
|
||||
isEditable = ->
|
||||
if $model.$modelValue.id
|
||||
return $scope.project.my_permissions.indexOf("modify_us") != -1
|
||||
return $scope.project.my_permissions.indexOf("add_us") != -1
|
||||
|
||||
render = (us) ->
|
||||
totalPoints = us.total_points or 0
|
||||
computableRoles = _.filter($scope.project.roles, "computable")
|
||||
|
@ -428,7 +321,12 @@ UsEstimationDirective = ($log) ->
|
|||
role.points = if pointObj? and pointObj.name? then pointObj.name else "?"
|
||||
return role
|
||||
|
||||
html = mainTemplate({totalPoints: totalPoints, roles: roles})
|
||||
ctx = {
|
||||
totalPoints: totalPoints
|
||||
roles: roles
|
||||
editable: isEditable()
|
||||
}
|
||||
html = mainTemplate(ctx)
|
||||
$el.html(html)
|
||||
|
||||
renderPoints = (target, us, roleId) ->
|
||||
|
@ -461,19 +359,15 @@ UsEstimationDirective = ($log) ->
|
|||
return "0"
|
||||
return _.reduce(values, (acc, num) -> acc + num)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (us) ->
|
||||
render(us) if us
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
$el.on "click", ".total.clickable", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return if not isEditable()
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
roleId = target.data("role-id")
|
||||
|
||||
us = $scope.$eval($attrs.ngModel)
|
||||
us = $model.$modelValue
|
||||
renderPoints(target, us, roleId)
|
||||
|
||||
target.siblings().removeClass('active')
|
||||
|
@ -482,8 +376,7 @@ UsEstimationDirective = ($log) ->
|
|||
$el.on "click", ".point", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
us = $scope.$eval($attrs.ngModel)
|
||||
return if not isEditable()
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
roleId = target.data("role-id")
|
||||
|
@ -491,17 +384,262 @@ UsEstimationDirective = ($log) ->
|
|||
|
||||
$el.find(".popover").popover().close()
|
||||
|
||||
points = _.clone(us.points, true)
|
||||
# NOTE: This block of code is strange and, sometimes, repetitive
|
||||
# but is the only solution I find to update the object
|
||||
# corectly
|
||||
us = angular.copy($model.$modelValue)
|
||||
points = _.clone($model.$modelValue.points, true)
|
||||
points[roleId] = pointId
|
||||
|
||||
$scope.$apply ->
|
||||
us.setAttr('points', points) if us.setAttr?
|
||||
us.points = points
|
||||
us.total_points = calculateTotalPoints(us)
|
||||
render(us)
|
||||
$model.$setViewValue(us)
|
||||
|
||||
if saveAfterModify
|
||||
# Edit in the detail page
|
||||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("history:reload")
|
||||
onError = ->
|
||||
us.revert()
|
||||
$model.$setViewValue(us)
|
||||
$confirm.notify("error")
|
||||
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||
else
|
||||
# Create or eedit in the lightbox
|
||||
render($model.$modelValue)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (us) ->
|
||||
render(us) if us
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgUsEstimation", UsEstimationDirective)
|
||||
module.directive("tgUsEstimation", ["$rootScope", "$tgRepo", "$tgConfirm", UsEstimationDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## User story status button directive
|
||||
#############################################################################
|
||||
|
||||
UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
||||
# Display the status of a US and you can edit it.
|
||||
#
|
||||
# Example:
|
||||
# tg-us-status-button(ng-model="us")
|
||||
#
|
||||
# Requirements:
|
||||
# - Us object (ng-model)
|
||||
# - scope.statusById object
|
||||
# - $scope.project.my_permissions
|
||||
|
||||
template = _.template("""
|
||||
<div class="status-data <% if(editable){ %>clickable<% }%>">
|
||||
<span class="level" style="background-color:<%- status.color %>"></span>
|
||||
<span class="status-status"><%- status.name %></span>
|
||||
<% if(editable){ %><span class="icon icon-arrow-bottom"></span><% }%>
|
||||
<span class="level-name">status</span>
|
||||
|
||||
<ul class="popover pop-status">
|
||||
<% _.each(statuses, function(st) { %>
|
||||
<li><a href="" class="status" title="<%- st.name %>"
|
||||
data-status-id="<%- st.id %>"><%- st.name %></a></li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
</div>
|
||||
""") #TODO: i18n
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
isEditable = ->
|
||||
return $scope.project.my_permissions.indexOf("modify_us") != -1
|
||||
|
||||
render = (us) =>
|
||||
status = $scope.statusById[us.status]
|
||||
|
||||
html = template({
|
||||
status: status
|
||||
statuses: $scope.statusList
|
||||
editable: isEditable()
|
||||
})
|
||||
$el.html(html)
|
||||
|
||||
$el.on "click", ".status-data", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return if not isEditable()
|
||||
|
||||
$el.find(".pop-status").popover().open()
|
||||
|
||||
$el.on "click", ".status", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return if not isEditable()
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
|
||||
$.fn.popover().closeAll()
|
||||
|
||||
us = $model.$modelValue.clone()
|
||||
us.status = target.data("status-id")
|
||||
$model.$setViewValue(us)
|
||||
|
||||
$scope.$apply()
|
||||
|
||||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("history:reload")
|
||||
$loading.finish($el.find(".level-name"))
|
||||
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
us.revert()
|
||||
$model.$setViewValue(us)
|
||||
$loading.finish($el.find(".level-name"))
|
||||
|
||||
$loading.start($el.find(".level-name"))
|
||||
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (us) ->
|
||||
render(us) if us
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgUsStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading",
|
||||
UsStatusButtonDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## User story team requirements button directive
|
||||
#############################################################################
|
||||
|
||||
UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
|
||||
template = _.template("""
|
||||
<label for="team-requirement"
|
||||
class="button button-gray team-requirement <% if(canEdit){ %>editable<% }; %> <% if(isRequired){ %>active<% }; %>">
|
||||
Team requirement
|
||||
</label>
|
||||
<input type="checkbox" id="team-requirement" name="team-requirement"/>
|
||||
""") #TODO: i18n
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
canEdit = ->
|
||||
return $scope.project.my_permissions.indexOf("modify_us") != -1
|
||||
|
||||
render = (us) ->
|
||||
if not canEdit() and not us.team_requirement
|
||||
$el.html("")
|
||||
return
|
||||
|
||||
ctx = {
|
||||
canEdit: canEdit()
|
||||
isRequired: us.team_requirement
|
||||
}
|
||||
html = template(ctx)
|
||||
$el.html(html)
|
||||
|
||||
$el.on "click", ".team-requirement", (event) ->
|
||||
return if not canEdit()
|
||||
|
||||
us = $model.$modelValue.clone()
|
||||
us.team_requirement = not us.team_requirement
|
||||
$model.$setViewValue(us)
|
||||
|
||||
$loading.start($el.find("label"))
|
||||
promise = $tgrepo.save($model.$modelValue)
|
||||
promise.then =>
|
||||
$loading.finish($el.find("label"))
|
||||
$rootscope.$broadcast("history:reload")
|
||||
promise.then null, ->
|
||||
$loading.finish($el.find("label"))
|
||||
$confirm.notify("error")
|
||||
us.revert()
|
||||
$model.$setViewValue(us)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (us) ->
|
||||
render(us) if us
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgUsTeamRequirementButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", UsTeamRequirementButtonDirective])
|
||||
|
||||
#############################################################################
|
||||
## User story client requirements button directive
|
||||
#############################################################################
|
||||
|
||||
UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
|
||||
template = _.template("""
|
||||
<label for="client-requirement"
|
||||
class="button button-gray client-requirement <% if(canEdit){ %>editable<% }; %> <% if(isRequired){ %>active<% }; %>">
|
||||
Client requirement
|
||||
</label>
|
||||
<input type="checkbox" id="client-requirement" name="client-requirement"/>
|
||||
""") #TODO: i18n
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
canEdit = ->
|
||||
return $scope.project.my_permissions.indexOf("modify_us") != -1
|
||||
|
||||
render = (us) ->
|
||||
if not canEdit() and not us.client_requirement
|
||||
$el.html("")
|
||||
return
|
||||
|
||||
ctx = {
|
||||
canEdit: canEdit()
|
||||
isRequired: us.client_requirement
|
||||
}
|
||||
html = template(ctx)
|
||||
$el.html(html)
|
||||
|
||||
$el.on "click", ".client-requirement", (event) ->
|
||||
return if not canEdit()
|
||||
|
||||
us = $model.$modelValue.clone()
|
||||
us.client_requirement = not us.client_requirement
|
||||
$model.$setViewValue(us)
|
||||
|
||||
$loading.start($el.find("label"))
|
||||
promise = $tgrepo.save($model.$modelValue)
|
||||
promise.then =>
|
||||
$loading.finish($el.find("label"))
|
||||
$rootscope.$broadcast("history:reload")
|
||||
promise.then null, ->
|
||||
$loading.finish($el.find("label"))
|
||||
$confirm.notify("error")
|
||||
us.revert()
|
||||
$model.$setViewValue(us)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (us) ->
|
||||
render(us) if us
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgUsClientRequirementButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading",
|
||||
UsClientRequirementButtonDirective])
|
||||
|
|
|
@ -38,6 +38,7 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
"$scope",
|
||||
"$rootScope",
|
||||
"$tgRepo",
|
||||
"$tgModel",
|
||||
"$tgConfirm",
|
||||
"$tgResources",
|
||||
"$routeParams",
|
||||
|
@ -51,7 +52,7 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
"tgLoader"
|
||||
]
|
||||
|
||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
|
||||
constructor: (@scope, @rootscope, @repo, @model, @confirm, @rs, @params, @q, @location,
|
||||
@filter, @log, @appTitle, @navUrls, @analytics, tgLoader) ->
|
||||
@scope.projectSlug = @params.pslug
|
||||
@scope.wikiSlug = @params.slug
|
||||
|
@ -65,11 +66,7 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
tgLoader.pageLoaded()
|
||||
|
||||
# On Error
|
||||
promise.then null, (xhr) =>
|
||||
if xhr and xhr.status == 404
|
||||
@location.path(@navUrls.resolve("not-found"))
|
||||
@location.replace()
|
||||
return @q.reject(xhr)
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
loadProject: ->
|
||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||
|
@ -84,7 +81,15 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
@scope.wiki = wiki
|
||||
return wiki
|
||||
|
||||
@scope.wiki = {content: ""}
|
||||
if @scope.project.my_permissions.indexOf("add_wiki_page") == -1
|
||||
return null
|
||||
|
||||
data = {
|
||||
project: @scope.projectId
|
||||
slug: @scope.wikiSlug
|
||||
content: ""
|
||||
}
|
||||
@scope.wiki = @model.make_model("wiki", data)
|
||||
return @scope.wiki
|
||||
|
||||
loadWikiLinks: ->
|
||||
|
@ -113,34 +118,19 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
@scope.wikiId = data.wikipage
|
||||
|
||||
return prom.then null, (xhr) =>
|
||||
ctx = {project: @params.pslug, slug: @params.slug}
|
||||
@location.path(@navUrls.resolve("project-wiki-page-edit", ctx))
|
||||
@scope.wikiId = null
|
||||
|
||||
return promise.then(=> @.loadProject())
|
||||
.then(=> @.loadUsersAndRoles())
|
||||
.then(=> @q.all([@.loadWikiLinks(),
|
||||
@.loadWiki()]))
|
||||
|
||||
edit: ->
|
||||
ctx = {
|
||||
project: @scope.projectSlug
|
||||
slug: @scope.wikiSlug
|
||||
}
|
||||
@location.path(@navUrls.resolve("project-wiki-page-edit", ctx))
|
||||
|
||||
cancel: ->
|
||||
ctx = {
|
||||
project: @scope.projectSlug
|
||||
slug: @scope.wikiSlug
|
||||
}
|
||||
@location.path(@navUrls.resolve("project-wiki-page", ctx))
|
||||
|
||||
delete: ->
|
||||
# TODO: i18n
|
||||
title = "Delete Wiki Page"
|
||||
subtitle = unslugify(@scope.wiki.slug)
|
||||
message = unslugify(@scope.wiki.slug)
|
||||
|
||||
@confirm.ask(title, subtitle).then (finish) =>
|
||||
@confirm.askOnDelete(title, message).then (finish) =>
|
||||
onSuccess = =>
|
||||
finish()
|
||||
ctx = {project: @scope.projectSlug}
|
||||
|
@ -155,95 +145,181 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
|
||||
module.controller("WikiDetailController", WikiDetailController)
|
||||
|
||||
#############################################################################
|
||||
## Wiki Edit Controller
|
||||
#############################################################################
|
||||
|
||||
class WikiEditController extends WikiDetailController
|
||||
save: debounce 2000, ->
|
||||
onSuccess = =>
|
||||
ctx = {
|
||||
project: @scope.projectSlug
|
||||
slug: @scope.wiki.slug
|
||||
}
|
||||
@location.path(@navUrls.resolve("project-wiki-page", ctx))
|
||||
@confirm.notify("success")
|
||||
|
||||
onError = =>
|
||||
@confirm.notify("error")
|
||||
|
||||
if @scope.wiki.id
|
||||
@repo.save(@scope.wiki).then onSuccess, onError
|
||||
else
|
||||
@analytics.trackEvent("wikipage", "create", "create wiki page", 1)
|
||||
@scope.wiki.project = @scope.projectId
|
||||
@scope.wiki.slug = @scope.wikiSlug
|
||||
@repo.create("wiki", @scope.wiki).then onSuccess, onError
|
||||
|
||||
module.controller("WikiEditController", WikiEditController)
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Wiki Main Directive
|
||||
## Wiki Summary Directive
|
||||
#############################################################################
|
||||
|
||||
WikiDirective = ($tgrepo, $log, $location, $confirm) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$ctrl = $el.controller()
|
||||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgWikiDetail", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm", WikiDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Wiki Edit Main Directive
|
||||
#############################################################################
|
||||
|
||||
WikiEditDirective = ($tgrepo, $log, $location, $confirm) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$ctrl = $el.controller()
|
||||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgWikiEdit", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm", WikiEditDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Wiki User Info Directive
|
||||
#############################################################################
|
||||
|
||||
WikiUserInfoDirective = ($log) ->
|
||||
WikiSummaryDirective = ($log) ->
|
||||
template = _.template("""
|
||||
<ul>
|
||||
<li>
|
||||
<span class="number"><%- totalEditions %></span>
|
||||
<span class="description">times <br />edited</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="number"><%- lastModifiedDate %></span>
|
||||
<span class="description"> last <br />edit</span>
|
||||
</li>
|
||||
<li class="username-edition">
|
||||
<figure class="avatar">
|
||||
<img src="<%= imgurl %>" alt="<%- name %>">
|
||||
<img src="<%- user.imgUrl %>" alt="<%- user.name %>">
|
||||
</figure>
|
||||
<span class="description">last modification</span>
|
||||
<span class="username"><%- name %></span>
|
||||
<span class="username"><%- user.name %></span>
|
||||
</li>
|
||||
</ul>
|
||||
""")
|
||||
|
||||
link = ($scope, $el, $attrs) ->
|
||||
if not $attrs.ngModel?
|
||||
return $log.error "WikiUserDirective: no ng-model attr is defined"
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
render = (wiki) ->
|
||||
if not $scope.usersById?
|
||||
$log.error "WikiUserDirective requires userById set in scope."
|
||||
$log.error "WikiSummaryDirective requires userById set in scope."
|
||||
else
|
||||
user = $scope.usersById[wiki.last_modifier]
|
||||
if user is undefined
|
||||
ctx = {name: "unknown", imgurl: "/images/unnamed.png"}
|
||||
else
|
||||
ctx = {name: user.full_name_display, imgurl: user.photo}
|
||||
|
||||
if user is undefined
|
||||
user = {name: "unknown", imgUrl: "/images/unnamed.png"}
|
||||
else
|
||||
user = {name: user.full_name_display, imgUrl: user.photo}
|
||||
|
||||
ctx = {
|
||||
totalEditions: wiki.editions
|
||||
lastModifiedDate: moment(wiki.modified_date).format("DD MMM YYYY HH:mm")
|
||||
user: user
|
||||
}
|
||||
html = template(ctx)
|
||||
$el.html(html)
|
||||
|
||||
bindOnce($scope, $attrs.ngModel, render)
|
||||
$scope.$watch $attrs.ngModel, (wikiPage) ->
|
||||
return if not wikiPage
|
||||
render(wikiPage)
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "AE"
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgWikiUserInfo", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm", WikiUserInfoDirective])
|
||||
module.directive("tgWikiSummary", ["$log", WikiSummaryDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Editable Wiki Content Directive
|
||||
#############################################################################
|
||||
|
||||
EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $location, $navUrls,
|
||||
$analytics) ->
|
||||
template = """
|
||||
<div class="view-wiki-content">
|
||||
<section class="wysiwyg"
|
||||
tg-bind-html="wiki.html"></section>
|
||||
<span class="edit icon icon-edit" title="Edit"></span>
|
||||
</div>
|
||||
<div class="edit-wiki-content" style="display: none;">
|
||||
<textarea placeholder="Write your wiki page here"
|
||||
ng-model="wiki.content"
|
||||
tg-markitup="tg-markitup"></textarea>
|
||||
<span class="action-container">
|
||||
<a class="save icon icon-floppy" href="" title="Save" />
|
||||
<a class="cancel icon icon-delete" href="" title="Cancel" />
|
||||
</span>
|
||||
</div>
|
||||
""" # TODO: i18n
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
isEditable = ->
|
||||
return $scope.project.my_permissions.indexOf("modify_wiki_page") != -1
|
||||
|
||||
switchToEditMode = ->
|
||||
$el.find('.edit-wiki-content').show()
|
||||
$el.find('.view-wiki-content').hide()
|
||||
$el.find('textarea').focus()
|
||||
|
||||
switchToReadMode = ->
|
||||
$el.find('.edit-wiki-content').hide()
|
||||
$el.find('.view-wiki-content').show()
|
||||
|
||||
disableEdition = ->
|
||||
$el.find(".view-wiki-content .edit").remove()
|
||||
$el.find(".edit-wiki-content").remove()
|
||||
|
||||
cancelEdition = ->
|
||||
if $scope.wiki.id
|
||||
$scope.wiki.revert()
|
||||
switchToReadMode()
|
||||
else
|
||||
ctx = {project: $scope.projectSlug}
|
||||
$location.path($navUrls.resolve("project-wiki", ctx))
|
||||
|
||||
getSelectedText = ->
|
||||
if $window.getSelection
|
||||
return $window.getSelection().toString()
|
||||
else if $document.selection
|
||||
return $document.selection.createRange().text
|
||||
return null
|
||||
|
||||
$el.on "mouseup", ".view-wiki-content", (event) ->
|
||||
# We want to dettect the a inside the div so we use the target and
|
||||
# not the currentTarget
|
||||
target = angular.element(event.target)
|
||||
return if not isEditable()
|
||||
return if target.is('a')
|
||||
return if getSelectedText()
|
||||
switchToEditMode()
|
||||
|
||||
$el.on "click", ".save", debounce 2000, ->
|
||||
onSuccess = (wikiPage) ->
|
||||
if not $scope.wiki.id?
|
||||
$analytics.trackEvent("wikipage", "create", "create wiki page", 1)
|
||||
|
||||
$scope.wiki = wikiPage
|
||||
$model.setModelValue = $scope.wiki
|
||||
$confirm.notify("success")
|
||||
switchToReadMode()
|
||||
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
|
||||
$loading.start($el.find('.save-container'))
|
||||
if $scope.wiki.id?
|
||||
promise = $repo.save($scope.wiki).then(onSuccess, onError)
|
||||
else
|
||||
promise = $repo.create("wiki", $scope.wiki).then(onSuccess, onError)
|
||||
promise.finally ->
|
||||
$loading.finish($el.find('.save-container'))
|
||||
|
||||
$el.on "click", ".cancel", ->
|
||||
cancelEdition()
|
||||
|
||||
$el.on "keyup", "textarea", ->
|
||||
if event.keyCode == 27
|
||||
cancelEdition()
|
||||
|
||||
$scope.$watch $attrs.ngModel, (wikiPage) ->
|
||||
return if not wikiPage
|
||||
$scope.wiki = wikiPage
|
||||
|
||||
if isEditable()
|
||||
$el.addClass('editable')
|
||||
if not wikiPage.id?
|
||||
switchToEditMode()
|
||||
else
|
||||
disableEdition()
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
template: template
|
||||
}
|
||||
|
||||
module.directive("tgEditableWikiContent", ["$window", "$document", "$tgRepo", "$tgConfirm", "$tgLoading",
|
||||
"$tgLocation", "$tgNavUrls", "$tgAnalytics",
|
||||
EditableWikiContentDirective])
|
||||
|
|
|
@ -107,9 +107,9 @@ WikiNavDirective = ($tgrepo, $log, $location, $confirm, $navUrls, $analytics, $l
|
|||
|
||||
# TODO: i18n
|
||||
title = "Delete Wiki Link"
|
||||
subtitle = $scope.wikiLinks[linkId].title
|
||||
message = $scope.wikiLinks[linkId].title
|
||||
|
||||
$confirm.ask(title, subtitle).then (finish) =>
|
||||
$confirm.askOnDelete(title, message).then (finish) =>
|
||||
promise = $tgrepo.remove($scope.wikiLinks[linkId])
|
||||
promise.then ->
|
||||
promise = $ctrl.loadWikiLinks()
|
||||
|
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 1.0 MiB |
|
@ -45,4 +45,5 @@
|
|||
<glyph unicode="J" d="M184 54l24 145c1 3 0 5-2 7 0 0 0 0 0 0-2 2-5 3-7 3l-145-25c-3 0-5-2-6-5-1-3 0-7 2-9l28-28-76-76c-3-3-3-8 0-11l53-53c3-3 8-3 11 0l76 76 28-28c3-2 6-3 9-2 3 1 5 3 5 6z m144 404l-24-145c-1-3 0-5 2-7 0 0 0 0 0 0 2-2 5-3 7-3l145 25c3 0 5 2 6 5 1 3 0 7-2 9l-28 28 76 76c3 3 3 8 0 11l-53 53c-3 3-8 3-11 0l-76-76-28 28c-3 2-6 3-9 2-3-1-5-3-5-6z m130-274l-145 24c-3 1-5 0-7-2 0 0 0 0 0 0-2-2-3-5-3-7l25-145c0-3 2-5 5-6 3-1 7 0 9 2l28 28 76-76c3-3 8-3 11 0l53 53c3 3 3 8 0 11l-76 76 28 28c2 3 3 6 2 9-1 3-3 5-6 5z m-404 144l145-24c3-1 5 0 7 2 0 0 0 0 0 0 2 2 3 5 3 7l-25 145c0 3-2 5-5 6-3 1-6 0-9-2l-28-28-76 76c-3 3-8 3-11 0l-53-53c-3-3-3-8 0-11l76-76-28-28c-2-3-3-6-2-9 1-3 3-5 6-5z"/>
|
||||
<glyph unicode="K" d="M24 154l-24-144c0-3 1-6 2-8 0 0 0 0 0 0 2-1 5-2 8-2l144 25c3 0 6 2 6 5 1 3 1 6-2 8l-28 29 76 75c3 4 3 9 0 12l-52 52c-3 4-9 4-12 0l-76-75-28 28c-2 2-5 3-8 2-3-1-5-4-6-7z m464 204l24 144c0 3-1 6-2 8 0 0 0 0 0 0-2 1-5 2-8 2l-144-25c-3 0-6-2-6-5-1-3-1-6 2-8l28-29-76-75c-3-4-3-9 0-12l52-52c3-4 9-4 12 0l76 75 28-28c2-2 5-3 8-2 3 1 5 4 6 7z m-130-334l144-24c3 0 6 1 8 2 0 0 0 0 0 0 1 2 2 5 2 8l-25 144c0 3-2 6-5 6-3 1-6 1-8-2l-29-28-75 76c-4 3-9 3-12 0l-52-52c-4-3-4-9 0-12l75-76-28-28c-2-2-3-5-2-8 1-3 4-5 7-6z m-204 464l-144 24c-3 0-6-1-8-2 0 0 0 0 0 0-1-2-2-5-2-8l25-144c0-3 2-6 5-6 3-1 6-1 8 2l29 28 75-76c4-3 9-3 12 0l52 52c4 3 4 9 0 12l-75 76 28 28c2 2 3 5 2 8-1 3-4 5-7 6z"/>
|
||||
<glyph unicode="L" d="M435 486c8 0 14-2 19-7 4-5 7-11 7-18 0 0 0-435 0-435 0 0-103 0-103 0 0 0 0 435 0 435 0 17 7 25 21 25 0 0 56 0 56 0m-153-153c7 0 13-3 18-8 5-5 7-11 7-18 0 0 0-281 0-281 0 0-102 0-102 0 0 0 0 281 0 281 0 17 7 26 20 26 0 0 57 0 57 0m-154-154c8 0 14-2 18-7 5-6 8-12 8-18 0 0 0-128 0-128 0 0-103 0-103 0 0 0 0 128 0 128 0 17 7 25 21 25 0 0 56 0 56 0"/>
|
||||
<glyph unicode="M" d="M384 64l-320 0 0 288 320 0 0-96 32 0 0 160c0 17-14 32-32 32l-96 0c0 35-28 64-64 64-35 0-64-29-64-64l-96 0c-17 0-32-15-32-32l0-352c0-18 15-32 32-32l320 0c18 0 32 14 32 32l0 64-32 0z m-256 352c15 0 15 0 32 0 18 0 32 14 32 32 0 17 15 32 32 32 18 0 32-15 32-32 0-18 16-32 32-32 16 0 17 0 32 0 16 0 32-15 32-32l-256 0c0 19 14 32 32 32z m-32-256l64 0 0 32-64 0z m224 64l0 64-128-96 128-96 0 64 160 0 0 64z m-224-128l96 0 0 32-96 0z m160 224l-160 0 0-32 160 0z m-96-64l-64 0 0-32 64 0z"/>
|
||||
</font></defs></svg>
|
||||
|
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 323 B |
Before Width: | Height: | Size: 859 B After Width: | Height: | Size: 370 B |
Before Width: | Height: | Size: 223 B After Width: | Height: | Size: 272 B |
Before Width: | Height: | Size: 343 B After Width: | Height: | Size: 365 B |
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 255 B |
Before Width: | Height: | Size: 357 B After Width: | Height: | Size: 321 B |
Before Width: | Height: | Size: 606 B After Width: | Height: | Size: 397 B |
Before Width: | Height: | Size: 537 B After Width: | Height: | Size: 338 B |
Before Width: | Height: | Size: 743 B After Width: | Height: | Size: 375 B |
Before Width: | Height: | Size: 269 B After Width: | Height: | Size: 343 B |
|
@ -21,13 +21,13 @@ html(lang="en", ng-app="taiga")
|
|||
|
||||
div.master(ng-view)
|
||||
|
||||
div.hidden.lightbox.lightbox-confirm-delete
|
||||
include partials/views/modules/lightbox-confirm-delete
|
||||
div.hidden.lightbox.lightbox-ask-choice
|
||||
div.lightbox.lightbox-generic-ask
|
||||
include partials/views/modules/lightbox-generic-ask
|
||||
div.lightbox.lightbox-ask-choice
|
||||
include partials/views/modules/lightbox-ask-choice
|
||||
div.hidden.lightbox.lightbox-generic-success
|
||||
div.lightbox.lightbox-generic-success
|
||||
include partials/views/modules/lightbox-generic-success
|
||||
div.hidden.lightbox.lightbox-generic-error
|
||||
div.lightbox.lightbox-generic-error
|
||||
include partials/views/modules/lightbox-generic-error
|
||||
div.lightbox.lightbox-search(tg-search-box)
|
||||
include partials/views/modules/lightbox-search
|
||||
|
|
|
@ -22,5 +22,5 @@ block content
|
|||
|
||||
div.paginator.memberships-paginator
|
||||
|
||||
div.lightbox.lightbox-add-member.hidden(tg-lb-create-members)
|
||||
div.lightbox.lightbox-add-member(tg-lb-create-members)
|
||||
include views/modules/lightbox-add-member
|
||||
|
|
|
@ -12,7 +12,7 @@ block content
|
|||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="default-values")
|
||||
include views/modules/admin-submenu-project-profile
|
||||
|
||||
section.main.admin-roles
|
||||
section.main.admin-common
|
||||
header
|
||||
include views/components/mainTitle
|
||||
|
||||
|
|
|
@ -58,5 +58,5 @@ block content
|
|||
a.button.button-green(href="") Save
|
||||
a.delete-project(href="", title="Delete this project", ng-click="ctrl.openDeleteLightbox()") Delete this project
|
||||
|
||||
div.lightbox.lightbox-delete-project.hidden(tg-lb-delete-project)
|
||||
div.lightbox.lightbox-delete-project(tg-lb-delete-project)
|
||||
include views/modules/lightbox-delete-project
|
||||
|
|
|
@ -13,7 +13,7 @@ block content
|
|||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-priorities")
|
||||
include views/modules/admin-submenu-project-values
|
||||
|
||||
section.main.admin-roles
|
||||
section.main.admin-common
|
||||
include views/components/mainTitle
|
||||
p.admin-subtitle Specify the priority levels users can assign to issues
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ block content
|
|||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-severities")
|
||||
include views/modules/admin-submenu-project-values
|
||||
|
||||
section.main.admin-roles
|
||||
section.main.admin-common
|
||||
include views/components/mainTitle
|
||||
p.admin-subtitle Specify the severity level users can select to classify issues
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ block content
|
|||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-issue-status")
|
||||
include views/modules/admin-submenu-project-values
|
||||
|
||||
section.main.admin-roles
|
||||
section.main.admin-common
|
||||
include views/components/mainTitle
|
||||
p.admin-subtitle Specify the column headers that you will use to classify Issues
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ block content
|
|||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-issue-types")
|
||||
include views/modules/admin-submenu-project-values
|
||||
|
||||
section.main.admin-roles
|
||||
section.main.admin-common
|
||||
include views/components/mainTitle
|
||||
p.admin-subtitle Specify the categories users can select to classify issues
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ block content
|
|||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-task-status")
|
||||
include views/modules/admin-submenu-project-values
|
||||
|
||||
section.main.admin-roles
|
||||
section.main.admin-common
|
||||
include views/components/mainTitle
|
||||
p.admin-subtitle Specify the column headers that you will use to classify Tasks related to each User Stories
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ block content
|
|||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-us-points")
|
||||
include views/modules/admin-submenu-project-values
|
||||
|
||||
section.main.admin-roles
|
||||
section.main.admin-common
|
||||
include views/components/mainTitle
|
||||
p.admin-subtitle Specify the numerical system you will use to indicate the level of difficulty for each User Story
|
||||
|
||||
|
@ -26,5 +26,5 @@ block content
|
|||
|
||||
include views/modules/admin/project-points
|
||||
|
||||
div.hidden.lightbox.lightbox-generic-notion.notion-admin-project-values-us-points(id="notion-admin-project-values-us-points", tg-lb-notion)
|
||||
div.lightbox.lightbox-generic-notion.notion-admin-project-values-us-points(id="notion-admin-project-values-us-points", tg-lb-notion)
|
||||
include views/modules/help-notions/lightbox-notion-admin-project-values-us-points
|
||||
|
|
|
@ -13,7 +13,7 @@ block content
|
|||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-us-status")
|
||||
include views/modules/admin-submenu-project-values
|
||||
|
||||
section.main.admin-roles
|
||||
section.main.admin-common
|
||||
include views/components/mainTitle
|
||||
p.admin-subtitle Specify the column headers that you will use to classify User Stories
|
||||
|
||||
|
|
|
@ -11,15 +11,21 @@ block content
|
|||
sidebar.menu-tertiary.sidebar
|
||||
include views/modules/admin-submenu-roles
|
||||
|
||||
section.main.admin-roles
|
||||
section.main.admin-roles.admin-common
|
||||
.header-with-actions
|
||||
include views/components/mainTitle
|
||||
.action-buttons
|
||||
a.button.button-red.delete-role(href="", title="Delete", ng-click="ctrl.delete()") Delete
|
||||
|
||||
|
||||
div(tg-edit-role)
|
||||
.edit-role
|
||||
input(type="text", value="{{ role.name }}")
|
||||
a.save.icon.icon-floppy(href="", title="Save")
|
||||
|
||||
p.total
|
||||
| {{ role.name }}
|
||||
span ({{ role.members_count }} members with this role)
|
||||
span.role-name(title="{{ role.members_count }} members with this role") {{ role.name }}
|
||||
a.edit-value.icon.icon-edit
|
||||
|
||||
div.any-computable-role(ng-hide="anyComputableRole") Be careful, no role in your project will be able to estimate the point value for user stories
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ block content
|
|||
div.wrapper
|
||||
div.login-main
|
||||
div.login-container
|
||||
img.logo-svg(src="/svg/logo.svg", alt="TAIGA")
|
||||
img.logo-svg(src="/svg/logo.svg", alt="TAIGA loves Movember!")
|
||||
h1.logo Taiga
|
||||
h2.tagline LOVE YOUR PROJECT
|
||||
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
extends dummy-layout
|
||||
|
||||
block head
|
||||
title Taiga Your agile, free, and open source project management tool
|
||||
|
||||
block content
|
||||
form.wrapper(tg-issue-detail, ng-controller="IssueDetailController as ctrl",
|
||||
ng-init="section='issues'")
|
||||
div.main.us-detail
|
||||
div.us-detail-header.header-with-actions
|
||||
include views/components/mainTitle
|
||||
.action-buttons
|
||||
a.button.button-green.save-issue(href="", title="Save") Save
|
||||
a.button.button-red.cancel(tg-nav="project-issues-detail:project=project.slug, ref=issue.ref", href="", title="Cancel") Cancel
|
||||
|
||||
section.us-story-main-data
|
||||
div.us-title(ng-class="{blocked: issue.is_blocked}")
|
||||
div.us-edit-name-inner
|
||||
span.us-number(tg-bo-ref="issue.ref")
|
||||
input(type="text", ng-model="issue.subject", data-required="true", data-maxlength="500")
|
||||
p.block-desc-container(ng-show="issue.is_blocked")
|
||||
span.block-description-title Blocked
|
||||
span.block-description(tg-bind-html="issue.blocked_note || 'This issue is blocked'")
|
||||
a.unblock(ng-click="ctrl.unblock()", href="", title="Unblock issue") Unblock
|
||||
|
||||
div(tg-tag-line, editable="true", ng-model="issue.tags")
|
||||
|
||||
section.us-content
|
||||
textarea(placeholder="Write a description of your issue", ng-model="issue.description", tg-markitup)
|
||||
|
||||
tg-attachments(ng-model="issue", type="issue")
|
||||
tg-history(ng-model="issue", type="issue", mode="edit")
|
||||
|
||||
sidebar.menu-secondary.sidebar
|
||||
section.us-status(tg-issue-status, ng-model="issue", editable="true")
|
||||
section.us-assigned-to(tg-assigned-to, ng-model="issue", editable="true")
|
||||
section.watchers(tg-watchers, ng-model="issue", editable="true")
|
||||
|
||||
section.us-detail-settings
|
||||
a.button.button-gray.clickable(title="Click to block the issue", ng-show="!issue.is_blocked", ng-click="ctrl.block()") Block
|
||||
a.button.button-red(title="Click to delete the issue", tg-check-permission="delete_issue", ng-click="ctrl.delete()", href="") Delete
|
||||
|
||||
div.lightbox.lightbox-block.hidden(tg-lb-block, title="Blocking issue", ng-model="issue")
|
||||
|
||||
div.lightbox.lightbox-select-user(tg-lb-assignedto)
|
||||
|
||||
div.lightbox.lightbox-select-user(tg-lb-watchers)
|
|
@ -4,19 +4,17 @@ block head
|
|||
title Taiga Your agile, free, and open source project management tool
|
||||
|
||||
block content
|
||||
div.wrapper(tg-issue-detail, ng-controller="IssueDetailController as ctrl",
|
||||
div.wrapper(ng-controller="IssueDetailController as ctrl",
|
||||
ng-init="section='issues'")
|
||||
div.main.us-detail
|
||||
div.us-detail-header.header-with-actions
|
||||
include views/components/mainTitle
|
||||
.action-buttons
|
||||
a.button.button-green(tg-check-permission="modify_issue", href="", title="Edit", tg-nav="project-issues-detail-edit:project=project.slug,ref=issue.ref") Edit
|
||||
|
||||
section.us-story-main-data
|
||||
div.us-title(ng-class="{blocked: issue.is_blocked}")
|
||||
h2.us-title-text
|
||||
span.us-number(tg-bo-ref="issue.ref")
|
||||
span.us-name(ng-bind="issue.subject")
|
||||
span.us-name(tg-editable-subject, ng-model="issue", required-perm="modify_issue")
|
||||
|
||||
p.us-related-task(ng-if="issue.generated_user_stories") This issue has been promoted to US:
|
||||
a(ng-repeat="us in issue.generated_user_stories",
|
||||
|
@ -27,23 +25,43 @@ block content
|
|||
|
||||
p.block-desc-container(ng-show="issue.is_blocked")
|
||||
span.block-description-title Blocked
|
||||
span.block-description(tg-bind-html="issue.blocked_note || 'This issue is blocked'")
|
||||
span.block-description(ng-bind="issue.blocked_note || 'This issue is blocked'")
|
||||
|
||||
div.issue-nav
|
||||
a.icon.icon-arrow-left(ng-show="previousUrl",href="{{ previousUrl }}", title="previous issue")
|
||||
a.icon.icon-arrow-right(ng-show="nextUrl", href="{{ nextUrl }}", title="next issue")
|
||||
a.icon.icon-arrow-left(ng-show="previousUrl", tg-bo-href="previousUrl",
|
||||
title="previous issue")
|
||||
a.icon.icon-arrow-right(ng-show="nextUrl", tg-bo-href="nextUrl",
|
||||
title="next issue")
|
||||
|
||||
div(tg-tag-line, ng-model="issue.tags", ng-show="issue.tags")
|
||||
div.tags-block(tg-tag-line, ng-model="issue", required-perm="modify_issue")
|
||||
|
||||
section.us-content.wysiwyg(tg-bind-html="issue.description_html")
|
||||
section.duty-content.wysiwyg(tg-editable-description, ng-model="issue", required-perm="modify_issue")
|
||||
|
||||
tg-attachments(ng-model="issue", type="issue")
|
||||
tg-history(ng-model="issue", type="issue")
|
||||
|
||||
sidebar.menu-secondary.sidebar
|
||||
section.us-status(tg-issue-status, ng-model="issue")
|
||||
section.us-assigned-to(tg-assigned-to, ng-model="issue")
|
||||
section.watchers(tg-watchers, ng-model="issue")
|
||||
section.us-status
|
||||
h1(tg-issue-status-display, ng-model="issue")
|
||||
tg-created-by-display.us-created-by(ng-model="issue")
|
||||
div.duty-data-container
|
||||
div.duty-data(tg-issue-type-button, ng-model="issue")
|
||||
div.duty-data(tg-issue-severity-button, ng-model="issue")
|
||||
div.duty-data(tg-issue-priority-button, ng-model="issue")
|
||||
div.duty-data(tg-issue-status-button, ng-model="issue")
|
||||
|
||||
section.duty-assigned-to(tg-assigned-to, ng-model="issue", required-perm="modify_issue")
|
||||
|
||||
section.watchers(tg-watchers, ng-model="issue", required-perm="modify_issue")
|
||||
|
||||
section.us-detail-settings
|
||||
tg-promote-issue-to-us-button(ng-model="issue")
|
||||
tg-promote-issue-to-us-button(tg-check-permission="add_us", ng-model="issue")
|
||||
tg-block-button(tg-check-permission="modify_issue", ng-model="issue")
|
||||
tg-delete-button(tg-check-permission="delete_issue",
|
||||
on-delete-title="'Delete issue'",
|
||||
on-delete-go-to-url="onDeleteGoToUrl",
|
||||
ng-model="issue")
|
||||
|
||||
div.lightbox.lightbox-block(tg-lb-block, title="Blocking issue", ng-model="issue")
|
||||
div.lightbox.lightbox-select-user(tg-lb-assignedto)
|
||||
div.lightbox.lightbox-select-user(tg-lb-watchers)
|
||||
|
|
|
@ -26,4 +26,4 @@ block content
|
|||
div.lightbox.lightbox-generic-bulk(tg-lb-create-bulk-userstories)
|
||||
include views/modules/lightbox-us-bulk
|
||||
|
||||
div.lightbox.lightbox-select-user.hidden(tg-lb-assignedto)
|
||||
div.lightbox.lightbox-select-user(tg-lb-assignedto)
|
||||
|
|
|
@ -8,7 +8,7 @@ block content
|
|||
div.wrapper
|
||||
div.login-main
|
||||
div.login-container
|
||||
img.logo-svg(src="/svg/logo.svg", alt="TAIGA")
|
||||
img.logo-svg(src="/svg/logo.svg", alt="TAIGA loves Movember!")
|
||||
h1.logo Taiga
|
||||
h2.tagline LOVE YOUR PROJECT
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ block content
|
|||
sidebar.menu-secondary.sidebar(tg-user-settings-navigation="mail-notifications")
|
||||
include views/modules/user-settings-menu
|
||||
|
||||
section.main.admin-roles
|
||||
section.main.admin-common
|
||||
header
|
||||
h1
|
||||
span.green(tg-bo-html="sectionName")
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
div.error-main
|
||||
div.error-container
|
||||
object.logo-svg(type="image/svg+xml", data="/svg/logo.svg")
|
||||
img(src="/images/logo.png", alt="TAIGA")
|
||||
h1.logo Permission denied
|
||||
p.error-text Error 403.
|
||||
a(href="/", title="") Take me home
|
|
@ -8,8 +8,7 @@ block content
|
|||
div.wrapper
|
||||
div.login-main
|
||||
div.login-container
|
||||
object.logo-svg(type="image/svg+xml", data="/svg/logo.svg")
|
||||
img(src="/images/logo.png", alt="TAIGA")
|
||||
img.logo-svg(src="/svg/logo.svg", alt="TAIGA loves Movember!")
|
||||
h1.logo Taiga
|
||||
h2.tagline LOVE YOUR PROJECT
|
||||
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
extends dummy-layout
|
||||
|
||||
block head
|
||||
title Taiga Your agile, free, and open source project management tool
|
||||
|
||||
block content
|
||||
form.wrapper(tg-task-detail, ng-controller="TaskDetailController as ctrl",
|
||||
ng-init="section='backlog'")
|
||||
div.main.us-detail
|
||||
div.us-detail-header.header-with-actions
|
||||
include views/components/mainTitle
|
||||
.action-buttons
|
||||
a.button.button-green.save-task(href="", title="Save") Save
|
||||
a.button.button-red.cancel(tg-nav="project-tasks-detail:project=project.slug,ref=task.ref", href="", title="Cancel") Cancel
|
||||
|
||||
section.us-story-main-data
|
||||
div.us-title(ng-class="{blocked: task.is_blocked}")
|
||||
div.us-edit-name-inner
|
||||
span.us-number(tg-bo-ref="task.ref")
|
||||
input(type="text", ng-model="task.subject", data-required="true", data-maxlength="500")
|
||||
p.block-desc-container(ng-show="task.is_blocked")
|
||||
span.block-description-title Blocked
|
||||
span.block-description(tg-bind-html="task.blocked_note || 'This task is blocked'")
|
||||
a.unblock(ng-click="ctrl.unblock()", href="", title="Unblock task") Unblock
|
||||
|
||||
div(tg-tag-line, editable="true", ng-model="task.tags")
|
||||
|
||||
section.us-content
|
||||
textarea(placeholder="Write a description of your task", ng-model="task.description", tg-markitup)
|
||||
|
||||
tg-attachments(ng-model="task", type="task")
|
||||
tg-history(ng-model="task", type="task", mode="edit")
|
||||
|
||||
sidebar.menu-secondary.sidebar
|
||||
section.us-status(tg-task-status, ng-model="task", editable="true")
|
||||
section.us-assigned-to(tg-assigned-to, ng-model="task", editable="true")
|
||||
section.watchers(tg-watchers, ng-model="task", editable="true")
|
||||
|
||||
section.us-detail-settings
|
||||
fieldset(title="Feeling a bit overwhelmed by a task? Make sure others know about it by clicking on Iocaine when editing a task. It's possible to become immune to this (fictional) deadly poison by consuming small amounts over time just as it's possible to get better at what you do by occasionally taking on extra challenges!")
|
||||
label.clickable.button.button-gray(for="is-iocaine", ng-class="{'active': task.is_iocaine}") Iocaine
|
||||
input(ng-model="task.is_iocaine", type="checkbox", id="is-iocaine", name="is-iocaine")
|
||||
|
||||
a.button.button-gray.clickable(ng-show="!task.is_blocked", ng-click="ctrl.block()") Block
|
||||
a.button.button-red(tg-check-permission="delete_task", ng-click="ctrl.delete()", href="") Delete
|
||||
|
||||
div.lightbox.lightbox-block.hidden(tg-lb-block, title="Blocking task", ng-model="task")
|
||||
div.lightbox.lightbox-select-user.hidden(tg-lb-assignedto)
|
||||
div.lightbox.lightbox-select-user.hidden(tg-lb-watchers)
|
|
@ -4,7 +4,7 @@ block head
|
|||
title Taiga Your agile, free, and open source project management tool
|
||||
|
||||
block content
|
||||
div.wrapper(tg-task-detail, ng-controller="TaskDetailController as ctrl",
|
||||
div.wrapper(ng-controller="TaskDetailController as ctrl",
|
||||
ng-init="section='backlog'")
|
||||
div.main.us-detail
|
||||
div.us-detail-header.header-with-actions
|
||||
|
@ -15,16 +15,12 @@ block content
|
|||
href="", title="Go to taskboard",
|
||||
tg-nav="project-taskboard:project=project.slug,sprint=sprint.slug",
|
||||
ng-if="sprint && project.is_backlog_activated") Taskboard
|
||||
a.button.button-green(
|
||||
tg-check-permission="modify_task", href="",
|
||||
title="Edit",
|
||||
tg-nav="project-tasks-detail-edit:project=project.slug,ref=task.ref") Edit
|
||||
|
||||
section.us-story-main-data
|
||||
div.us-title(ng-class="{blocked: task.is_blocked}")
|
||||
h2.us-title-text
|
||||
span.us-number(tg-bo-ref="task.ref")
|
||||
span.us-name(ng-bind="task.subject")
|
||||
span.us-name(tg-editable-subject, ng-model="task", required-perm="modify_task")
|
||||
h3.us-related-task This task belongs to
|
||||
a(tg-check-permission="view_us", href="", title="Go to user story",
|
||||
tg-nav="project-userstories-detail:project=project.slug, ref=us.ref",
|
||||
|
@ -33,22 +29,39 @@ block content
|
|||
span(tg-bo-bind="us.subject")
|
||||
p.block-desc-container(ng-show="task.is_blocked")
|
||||
span.block-description-title Blocked
|
||||
span.block-description(tg-bind-html="task.blocked_note || 'This task is blocked'")
|
||||
span.block-description(ng-bind="task.blocked_note || 'This task is blocked'")
|
||||
div.issue-nav
|
||||
a.icon.icon-arrow-left(ng-show="previousUrl",href="{{ previousUrl }}", title="previous task")
|
||||
a.icon.icon-arrow-right(ng-show="nextUrl", href="{{ nextUrl }}", title="next task")
|
||||
a.icon.icon-arrow-left(ng-show="previousUrl", tg-bo-href="previousUrl",
|
||||
title="previous task")
|
||||
a.icon.icon-arrow-right(ng-show="nextUrl", tg-bo-href="nextUrl",
|
||||
title="next task")
|
||||
|
||||
div(tg-tag-line, ng-model="task.tags", ng-show="task.tags")
|
||||
div.tags-block(tg-tag-line, ng-model="task", required-perm="modify_task")
|
||||
|
||||
section.us-content.wysiwyg(tg-bind-html="task.description_html")
|
||||
section.duty-content.wysiwyg(tg-editable-description, ng-model="task", required-perm="modify_task")
|
||||
|
||||
tg-attachments(ng-model="task", type="task")
|
||||
tg-history(ng-model="task", type="task")
|
||||
|
||||
sidebar.menu-secondary.sidebar
|
||||
section.us-status(tg-task-status, ng-model="task")
|
||||
section.us-assigned-to(tg-assigned-to, ng-model="task")
|
||||
section.watchers(tg-watchers, ng-model="task")
|
||||
section.us-status
|
||||
h1(tg-task-status-display, ng-model="task")
|
||||
div.us-created-by(tg-created-by-display, ng-model="task")
|
||||
div.duty-data-container
|
||||
div.duty-data(tg-task-status-button, ng-model="task")
|
||||
|
||||
section.duty-assigned-to(tg-assigned-to, ng-model="task", required-perm="modify_task")
|
||||
|
||||
section.watchers(tg-watchers, ng-model="task", required-perm="modify_task")
|
||||
|
||||
section.us-detail-settings
|
||||
span.button.button-gray(href="", ng-class="{'active': task.is_iocaine }", title="Feeling a bit overwhelmed by a task? Make sure others know about it by clicking on Iocaine when editing a task. It's possible to become immune to this (fictional) deadly poison by consuming small amounts over time just as it's possible to get better at what you do by occasionally taking on extra challenges!") Iocaine
|
||||
tg-task-is-iocaine-button(ng-model="task")
|
||||
tg-block-button(tg-check-permission="modify_task", ng-model="task")
|
||||
tg-delete-button(tg-check-permission="delete_task",
|
||||
on-delete-title="'Delete Task'",
|
||||
on-delete-go-to-url="onDeleteGoToUrl",
|
||||
ng-model="task")
|
||||
|
||||
div.lightbox.lightbox-block(tg-lb-block, title="Blocking task", ng-model="task")
|
||||
div.lightbox.lightbox-select-user(tg-lb-assignedto)
|
||||
div.lightbox.lightbox-select-user(tg-lb-watchers)
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
extends dummy-layout
|
||||
|
||||
block head
|
||||
title Taiga Your agile, free, and open source project management tool
|
||||
|
||||
block content
|
||||
form.wrapper(tg-us-detail, ng-controller="UserStoryDetailController as ctrl",
|
||||
ng-init="section='backlog'")
|
||||
div.main.us-detail
|
||||
div.us-detail-header.header-with-actions
|
||||
include views/components/mainTitle
|
||||
.action-buttons
|
||||
a.button.button-green.save-us(href="", title="Save") Save
|
||||
a.button.button-red.cancel(tg-nav="project-userstories-detail:project=project.slug,ref=us.ref", href="", title="Cancel") Cancel
|
||||
|
||||
section.us-story-main-data
|
||||
div.us-title(ng-class="{blocked: us.is_blocked}")
|
||||
div.us-edit-name-inner
|
||||
span.us-number(tg-bo-ref="us.ref")
|
||||
input(type="text", ng-model="us.subject", data-required="true", data-maxlength="500")
|
||||
p.block-desc-container(ng-show="us.is_blocked")
|
||||
span.block-description-title Blocked
|
||||
span.block-description(tg-bind-html="us.blocked_note || 'This US is blocked'")
|
||||
a.unblock(ng-click="ctrl.unblock()", href="", title="Unblock US") Unblock
|
||||
|
||||
div(tg-tag-line, editable="true", ng-model="us.tags")
|
||||
|
||||
section.us-content
|
||||
textarea(placeholder="Write a description of your user story", ng-model="us.description", tg-markitup)
|
||||
|
||||
tg-attachments(ng-model="us", type="us")
|
||||
tg-history(ng-model="us", type="us", mode="edit")
|
||||
|
||||
sidebar.menu-secondary.sidebar
|
||||
section.us-status(tg-us-status-detail, ng-model="us", editable="true")
|
||||
section.us-assigned-to(tg-assigned-to, ng-model="us", editable="true")
|
||||
section.watchers(tg-watchers, ng-model="us", editable="true")
|
||||
|
||||
section.us-detail-settings
|
||||
fieldset
|
||||
label.clickable.button.button-gray(for="client-requirement", ng-class="{'active': us.client_requirement}") Client requirement
|
||||
input(ng-model="us.client_requirement", type="checkbox", id="client-requirement", name="client-requirement")
|
||||
fieldset
|
||||
label.clickable.button.button-gray(for="team-requirement", ng-class="{'active': us.team_requirement}") Team requirement
|
||||
input(ng-model="us.team_requirement", type="checkbox", id="team-requirement", name="team-requirement")
|
||||
|
||||
a.button.button-gray.clickable(ng-show="!us.is_blocked", ng-click="ctrl.block()") Block
|
||||
a.button.button-red(tg-check-permission="delete_us", ng-click="ctrl.delete()", href="") Delete
|
||||
|
||||
div.lightbox.lightbox-block.hidden(tg-lb-block, title="Blocking issue", ng-model="us")
|
||||
div.lightbox.lightbox-select-user.hidden(tg-lb-assignedto)
|
||||
div.lightbox.lightbox-select-user.hidden(tg-lb-watchers)
|
|
@ -4,7 +4,7 @@ block head
|
|||
title Taiga Your agile, free, and open source project management tool
|
||||
|
||||
block content
|
||||
div.wrapper(tg-us-detail, ng-controller="UserStoryDetailController as ctrl",
|
||||
div.wrapper(ng-controller="UserStoryDetailController as ctrl",
|
||||
ng-init="section='backlog'")
|
||||
div.main.us-detail
|
||||
div.us-detail-header.header-with-actions
|
||||
|
@ -15,16 +15,12 @@ block content
|
|||
href="", title="Go to taskboard",
|
||||
tg-nav="project-taskboard:project=project.slug,sprint=sprint.slug",
|
||||
ng-if="sprint && project.is_backlog_activated") Taskboard
|
||||
a.button.button-green(
|
||||
tg-check-permission="modify_us", href="",
|
||||
title="Edit",
|
||||
tg-nav="project-userstories-detail-edit:project=project.slug,ref=us.ref") Edit
|
||||
|
||||
section.us-story-main-data
|
||||
div.us-title(ng-class="{blocked: us.is_blocked}")
|
||||
h2.us-title-text
|
||||
span.us-number(tg-bo-ref="us.ref")
|
||||
span.us-name(ng-bind="us.subject")
|
||||
span.us-name(tg-editable-subject, ng-model="us", required-perm="modify_us")
|
||||
|
||||
p.us-related-task(ng-if="us.origin_issue") This US has been promoted from Issue
|
||||
a(tg-check-permission="view_us", href="", title="Go to issue",
|
||||
|
@ -34,15 +30,16 @@ block content
|
|||
|
||||
p.block-desc-container(ng-show="us.is_blocked")
|
||||
span.block-description-title Blocked
|
||||
span.block-description(tg-bind-html="us.blocked_note || 'This user story is blocked'")
|
||||
span.block-description(ng-bind="us.blocked_note || 'This user story is blocked'")
|
||||
div.issue-nav
|
||||
a.icon.icon-arrow-left(ng-show="previousUrl",href="{{ previousUrl }}",
|
||||
a.icon.icon-arrow-left(ng-show="previousUrl", tg-bo-href="previousUrl",
|
||||
title="previous user story")
|
||||
a.icon.icon-arrow-right(ng-show="nextUrl", href="{{ nextUrl }}", title="next user story")
|
||||
a.icon.icon-arrow-right(ng-show="nextUrl", tg-bo-href="nextUrl",
|
||||
title="next user story")
|
||||
|
||||
div(tg-tag-line, ng-model="us.tags", ng-show="us.tags")
|
||||
div.tags-block(tg-tag-line, ng-model="us", required-perm="modify_us")
|
||||
|
||||
section.us-content.wysiwyg(tg-bind-html="us.description_html")
|
||||
section.duty-content.wysiwyg(tg-editable-description, ng-model="us", required-perm="modify_us")
|
||||
|
||||
include views/modules/related-tasks
|
||||
|
||||
|
@ -50,15 +47,27 @@ block content
|
|||
tg-history(ng-model="us", type="us")
|
||||
|
||||
sidebar.menu-secondary.sidebar
|
||||
section.us-status(tg-us-status-detail, ng-model="us")
|
||||
section.us-assigned-to(tg-assigned-to, ng-model="us")
|
||||
section.us-created-by(tg-created-by, ng-model="us")
|
||||
section.watchers(tg-watchers, ng-model="us")
|
||||
section.us-status
|
||||
h1(tg-us-status-display, ng-model="us")
|
||||
div.us-detail-progress-bar(tg-us-tasks-progress-display, ng-model="tasks")
|
||||
tg-created-by-display.us-created-by(ng-model="us")
|
||||
tg-us-estimation(ng-model="us", save-after-modify="true")
|
||||
div.duty-data-container
|
||||
div.duty-data(tg-us-status-button, ng-model="us")
|
||||
|
||||
section.duty-assigned-to(tg-assigned-to, ng-model="us", required-perm="modify_us")
|
||||
|
||||
section.watchers(tg-watchers, ng-model="us", required-perm="modify_us")
|
||||
|
||||
section.us-detail-settings
|
||||
span.button.button-gray(href="", title="Client requirement",
|
||||
ng-class="{'active': us.client_requirement}") Client requirement
|
||||
span.button.button-gray(href="", title="Team requirement",
|
||||
ng-class="{'active': us.team_requirement}") Team requirement
|
||||
tg-us-team-requirement-button(ng-model="us")
|
||||
tg-us-client-requirement-button(ng-model="us")
|
||||
tg-block-button(tg-check-permission="modify_us", ng-model="us")
|
||||
tg-delete-button(tg-check-permission="delete_us",
|
||||
on-delete-title="'Delete User Story'",
|
||||
on-delete-go-to-url="onDeleteGoToUrl",
|
||||
ng-model="us")
|
||||
|
||||
div.lightbox.lightbox-select-user.hidden(tg-lb-assignedto)
|
||||
div.lightbox.lightbox-block(tg-lb-block, title="Blocking us", ng-model="us")
|
||||
div.lightbox.lightbox-select-user(tg-lb-assignedto)
|
||||
div.lightbox.lightbox-select-user(tg-lb-watchers)
|
||||
|
|
|
@ -27,6 +27,3 @@ block content
|
|||
fieldset
|
||||
input(type="submit", class="hidden")
|
||||
a.button.button-green(href="", ng-click="ctrl.save()") Save
|
||||
|
||||
div.lightbox.lightbox-delete-account.hidden
|
||||
include views/modules/lightbox-delete-account
|
||||
|
|
|
@ -24,9 +24,9 @@ block content
|
|||
span.icon.icon-spinner
|
||||
input(type="file", id="avatar-field", class="hidden",
|
||||
tg-avatar-model="avatarAttachment")
|
||||
|
||||
p The image will be cropped to 80x80 size.
|
||||
a.button.button-green.change Change
|
||||
p The image will be cropped to 80x80px.<br>
|
||||
span.size-info.hidden(tg-bo-html="maxFileSizeMsg")
|
||||
a.button.button-green.change(tg-bo-title="'Change photo. ' + maxFileSizeMsg") Change
|
||||
a.use-gravatar Use gravatar image
|
||||
|
||||
div.data
|
||||
|
@ -60,7 +60,4 @@ block content
|
|||
a.delete-account(href="", title="Delete Taiga account",
|
||||
ng-click="ctrl.openDeleteLightbox()") Delete Taiga account
|
||||
|
||||
div.lightbox.lightbox-delete-account.hidden(tg-lb-delete-user)
|
||||
|
||||
div.lightbox.lightbox-confirm-use-gravatar.hidden
|
||||
include views/modules/lightbox-use-gravatar
|
||||
div.lightbox.lightbox-delete-account(tg-lb-delete-user)
|
||||
|
|
|
@ -4,10 +4,10 @@ div.kanban-task-inner
|
|||
div.task-text
|
||||
a.task-assigned(href="", title="Assign User Story")
|
||||
span.task-num(tg-bo-ref="us.ref")
|
||||
a.task-name(href="", tg-bo-title="us.subject", tg-bind-html="us.subject",
|
||||
a.task-name(href="", tg-bo-title="us.subject", ng-bind="us.subject",
|
||||
tg-nav="project-userstories-detail:project=project.slug,ref=us.ref")
|
||||
a.task-points(href="", title="Total Us points")
|
||||
span(tg-bind-html="us.total_points") --
|
||||
span(ng-bind="us.total_points") --
|
||||
span points
|
||||
a.icon.icon-edit(tg-check-permission="modify_us", href="", title="Edit")
|
||||
a.icon.icon-drag-h(tg-check-permission="modify_us", href="", title="Drag&Drop")
|
||||
|
|
|
@ -2,7 +2,7 @@ div.summary.large-summary
|
|||
div
|
||||
div.summary-progress-bar(tg-progress-bar="stats.completedPercentage")
|
||||
div.data
|
||||
span.number(tg-bind-html="stats.completedPercentage + '%'")
|
||||
span.number(ng-bind="stats.completedPercentage + '%'")
|
||||
|
||||
ul
|
||||
li
|
||||
|
|
|
@ -2,17 +2,17 @@ div.summary
|
|||
div.summary-progress-bar(tg-backlog-progress-bar="stats")
|
||||
|
||||
div.data
|
||||
span.number(tg-bind-html="stats.completedPercentage + '%'")
|
||||
span.number(ng-bind="stats.completedPercentage + '%'")
|
||||
ul
|
||||
li
|
||||
span.number(tg-bind-html="stats.total_points") --
|
||||
span.number(ng-bind="stats.total_points") --
|
||||
span.description project<br />points
|
||||
li
|
||||
span.number(tg-bind-html="stats.defined_points") --
|
||||
span.number(ng-bind="stats.defined_points") --
|
||||
span.description defined<br />points
|
||||
li
|
||||
span.number(tg-bind-html="stats.closed_points") --
|
||||
span.number(ng-bind="stats.closed_points") --
|
||||
span.description closed<br />points
|
||||
li
|
||||
span.number(tg-bind-html="stats.speed | number:0") --
|
||||
span.number(ng-bind="stats.speed | number:0") --
|
||||
span.description points /<br />sprint
|
||||
|
|
|
@ -33,8 +33,8 @@ section.project-values-table
|
|||
data-type="number")
|
||||
|
||||
div.project-values-settings
|
||||
a.save.icon.icon-floppy(href="", title="Add")
|
||||
a.cancel.icon.icon-delete(href="", title="Delete")
|
||||
a.save.icon.icon-floppy(href="", title="Save changes")
|
||||
a.cancel.icon.icon-delete(href="", title="Cancel")
|
||||
|
||||
form
|
||||
div.project-values-row.new-value.hidden
|
||||
|
@ -48,4 +48,4 @@ section.project-values-table
|
|||
|
||||
div.project-values-settings
|
||||
a.add-new.icon.icon-floppy(href="", title="Add")
|
||||
a.delete-new.icon.icon-delete(href="", title="Delete")
|
||||
a.delete-new.icon.icon-delete(href="", title="Cancel")
|
||||
|
|
|
@ -38,8 +38,8 @@ section.colors-table
|
|||
ng-options="e.id as e.name for e in [{'id':true, 'name':'Yes'},{'id':false, 'name': 'No'}]")
|
||||
|
||||
div.options-column
|
||||
a.save.icon.icon-floppy(href="", title="Add")
|
||||
a.cancel.icon.icon-delete(href="", title="Delete")
|
||||
a.save.icon.icon-floppy(href="", title="Save changes")
|
||||
a.cancel.icon.icon-delete(href="", title="Cancel")
|
||||
|
||||
form
|
||||
div.row.table-main.new-value.hidden
|
||||
|
@ -57,4 +57,4 @@ section.colors-table
|
|||
|
||||
div.options-column
|
||||
a.add-new.icon.icon-floppy(href="", title="Add")
|
||||
a.delete-new.icon.icon-delete(href="", title="Delete")
|
||||
a.delete-new.icon.icon-delete(href="", title="Cancel")
|
||||
|
|
|
@ -31,8 +31,8 @@ section.colors-table
|
|||
ng-model="value.name", data-required="true", data-maxlength="255")
|
||||
|
||||
div.options-column
|
||||
a.save.icon.icon-floppy(href="", title="Add")
|
||||
a.cancel.icon.icon-delete(href="", title="Delete")
|
||||
a.save.icon.icon-floppy(href="", title="Save changes")
|
||||
a.cancel.icon.icon-delete(href="", title="Cancel")
|
||||
|
||||
form
|
||||
div.row.table-main.new-value.hidden
|
||||
|
@ -46,4 +46,4 @@ section.colors-table
|
|||
|
||||
div.options-column
|
||||
a.add-new.icon.icon-floppy(href="", title="Add")
|
||||
a.delete-new.icon.icon-delete(href="", title="Delete")
|
||||
a.delete-new.icon.icon-delete(href="", title="Cancel")
|
||||
|
|
|
@ -46,8 +46,8 @@ section.colors-table
|
|||
ng-model="value.wip_limit", data-type="digits")
|
||||
|
||||
div.options-column
|
||||
a.save.icon.icon-floppy(href="", title="Add")
|
||||
a.cancel.icon.icon-delete(href="", title="Delete")
|
||||
a.save.icon.icon-floppy(href="", title="Save changes")
|
||||
a.cancel.icon.icon-delete(href="", title="Cancel")
|
||||
|
||||
form
|
||||
div.row.table-main.new-value.hidden
|
||||
|
@ -69,4 +69,4 @@ section.colors-table
|
|||
|
||||
div.options-column
|
||||
a.add-new.icon.icon-floppy(href="", title="Add")
|
||||
a.delete-new.icon.icon-delete(href="", title="Delete")
|
||||
a.delete-new.icon.icon-delete(href="", title="Cancel")
|
||||
|
|
|
@ -7,6 +7,6 @@ section
|
|||
|
||||
block content
|
||||
|
||||
div.delete-options
|
||||
div.options
|
||||
a.button.button-green(href="", title="Accept")
|
||||
span Accept
|
||||
|
|
|
@ -4,6 +4,7 @@ form
|
|||
h2.title New Member
|
||||
|
||||
//- Form is set in a directive
|
||||
.add-member-forms
|
||||
|
||||
a.button.button-green(href="", title="Save")
|
||||
span Create
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
a.close(href="", title="close")
|
||||
span.icon.icon-delete
|
||||
form
|
||||
h2.title Delete User Story
|
||||
p
|
||||
span.delete-question Are you sure you want to delete?
|
||||
span.subtitle #125 Crear el perfil de usuario senior en el admin
|
||||
span.replacement What value do you want to use as replacement?
|
||||
h2.title
|
||||
p.question
|
||||
p.subtitle
|
||||
p.replacement
|
||||
select.choices
|
||||
div.delete-options
|
||||
p.warning
|
||||
|
||||
div.options
|
||||
a.button.button-green(href="", title="Accept")
|
||||
span Accept
|
||||
a.button.button-red(href="", title="Delete")
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
a.close(href="", title="close")
|
||||
span.icon.icon-delete
|
||||
form
|
||||
h2.title Delete User Story
|
||||
p
|
||||
span.delete-question Are you sure you want to delete?
|
||||
span.subtitle #125 Crear el perfil de usuario senior en el admin
|
||||
div.delete-options
|
||||
a.button.button-green(href="", title="Accept")
|
||||
span Accept
|
||||
a.button.button-red(href="", title="Delete")
|
||||
span Cancel
|
|
@ -14,7 +14,7 @@ form
|
|||
select.severity(ng-model="issue.severity", ng-options="s.id as s.name for s in severityList")
|
||||
|
||||
fieldset
|
||||
div(tg-tag-line, editable="true", ng-model="issue.tags")
|
||||
div.tags-block(tg-lb-tag-line, ng-model="issue.tags")
|
||||
|
||||
fieldset
|
||||
textarea.description(placeholder="Description", ng-model="issue.description")
|
||||
|
|
|
@ -3,9 +3,9 @@ a.close(href="", title="close")
|
|||
form
|
||||
h2.title Delete Taiga Account
|
||||
p
|
||||
span.delete-question Are you sure you want to delete your Taiga account?
|
||||
span.question Are you sure you want to delete your Taiga account?
|
||||
span.subtitle We're going to miss you! :-(
|
||||
div.delete-options
|
||||
div.options
|
||||
a.button.button-green(href="", title="Accept")
|
||||
span Accept
|
||||
a.button.button-red(href="", title="Cancel")
|
||||
|
|