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