commit
3be01527da
14
AUTHORS.rst
14
AUTHORS.rst
|
@ -7,14 +7,22 @@ The PRIMARY AUTHORS are:
|
||||||
- Alejandro Alonso <alejandro.alonso@kaleidos.net>
|
- Alejandro Alonso <alejandro.alonso@kaleidos.net>
|
||||||
- Anler Hernández <hello@anler.me>
|
- Anler Hernández <hello@anler.me>
|
||||||
- Juan Francisco Alcántara <juanfran.alcantara@kaleidos.net>
|
- Juan Francisco Alcántara <juanfran.alcantara@kaleidos.net>
|
||||||
|
- Esther Moreno Riesco <esther.moreno@kaleidos.net>
|
||||||
|
|
||||||
Special thanks to Kaleidos Open Source S.L. for provide time for taiga
|
Special thanks to Kaleidos Open Source S.L. for provide time for Taiga
|
||||||
development.
|
development.
|
||||||
|
|
||||||
And here is an inevitably incomplete list of MUCH-APPRECIATED CONTRIBUTORS --
|
And here is an inevitably incomplete list of MUCH-APPRECIATED CONTRIBUTORS --
|
||||||
people who have submitted patches, reported bugs, added translations, helped
|
people who have submitted patches, reported bugs, added translations, helped
|
||||||
answer newbie questions, and generally made taiga that much better:
|
answer newbie questions, and generally made Taiga that much better:
|
||||||
|
|
||||||
- Pilar Esteban <pilar.esteban@gmail.com>
|
- Pilar Esteban <pilar.esteban@gmail.com>
|
||||||
- Guilhem Got <guilhem.got@gmail.com>
|
- Guilhem Got <guilhem.got@gmail.com>
|
||||||
...
|
- Ramiro Sánchez <ramiro.sanzhez@kaleidos.net>
|
||||||
|
- Miguel de la Cruz <miguel.delacruz@kaleidos.net>
|
||||||
|
- Andrea Stagi <stagi.andrea@gmail.com>
|
||||||
|
- Jordan Rinke
|
||||||
|
- Wil Wade
|
||||||
|
- Daniel Koch
|
||||||
|
- Florian Bezagu
|
||||||
|
- Ryan Swanstrom
|
||||||
|
|
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -1,5 +1,19 @@
|
||||||
# Changelog #
|
# Changelog #
|
||||||
|
|
||||||
|
|
||||||
|
## 1.6.0 Abies Bifolia (2015-03-17)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- Added custom fields per project for user stories, tasks and issues.
|
||||||
|
- Add to the Admin Panel the export to CSV sections.
|
||||||
|
- Reorganized the Admin Panel.
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
- New contrib plugin for hipchat (by Δndrea Stagi)
|
||||||
|
- Plugin based authentication.
|
||||||
|
- Added Taiga Style Guide in support Pages to enhance open source design.
|
||||||
|
- Lots of small and not so small bugfixes.
|
||||||
|
|
||||||
## 1.5.0 Betula Pendula - FOSDEM 2015 (2015-01-29)
|
## 1.5.0 Betula Pendula - FOSDEM 2015 (2015-01-29)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# Taiga Front #
|
# Taiga Front #
|
||||||
|
|
||||||

|

|
||||||
|
[](https://taiga.io "Managed with Taiga")
|
||||||
|
|
||||||
## Get the compiled version ##
|
## Get the compiled version ##
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ window.taigaConfig = {
|
||||||
"privacyPolicyUrl": null,
|
"privacyPolicyUrl": null,
|
||||||
"termsOfServiceUrl": null,
|
"termsOfServiceUrl": null,
|
||||||
"maxUploadFileSize": null,
|
"maxUploadFileSize": null,
|
||||||
"gitHubClientId": null,
|
|
||||||
"contribPlugins": []
|
"contribPlugins": []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,20 +36,26 @@ taiga.generateUniqueSessionIdentifier = ->
|
||||||
taiga.sessionId = taiga.generateUniqueSessionIdentifier()
|
taiga.sessionId = taiga.generateUniqueSessionIdentifier()
|
||||||
|
|
||||||
|
|
||||||
configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEventsProvider, tgLoaderProvider) ->
|
configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEventsProvider, tgLoaderProvider, $compileProvider) ->
|
||||||
$routeProvider.when("/",
|
$routeProvider.when("/",
|
||||||
{templateUrl: "project/projects.html", resolve: {loader: tgLoaderProvider.add()}})
|
{templateUrl: "project/projects.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||||
|
|
||||||
$routeProvider.when("/project/:pslug/",
|
$routeProvider.when("/project/:pslug/",
|
||||||
{templateUrl: "project/project.html"})
|
{templateUrl: "project/project.html"})
|
||||||
$routeProvider.when("/project/:pslug/backlog",
|
|
||||||
{templateUrl: "backlog/backlog.html", resolve: {loader: tgLoaderProvider.add()}})
|
|
||||||
$routeProvider.when("/project/:pslug/taskboard/:sslug",
|
|
||||||
{templateUrl: "taskboard/taskboard.html", resolve: {loader: tgLoaderProvider.add()}})
|
|
||||||
$routeProvider.when("/project/:pslug/search",
|
$routeProvider.when("/project/:pslug/search",
|
||||||
{templateUrl: "search/search.html", reloadOnSearch: false})
|
{templateUrl: "search/search.html", reloadOnSearch: false})
|
||||||
|
|
||||||
|
$routeProvider.when("/project/:pslug/backlog",
|
||||||
|
{templateUrl: "backlog/backlog.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||||
|
|
||||||
$routeProvider.when("/project/:pslug/kanban",
|
$routeProvider.when("/project/:pslug/kanban",
|
||||||
{templateUrl: "kanban/kanban.html", resolve: {loader: tgLoaderProvider.add()}})
|
{templateUrl: "kanban/kanban.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||||
|
|
||||||
|
# Milestone
|
||||||
|
$routeProvider.when("/project/:pslug/taskboard/:sslug",
|
||||||
|
{templateUrl: "taskboard/taskboard.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||||
|
|
||||||
# User stories
|
# User stories
|
||||||
$routeProvider.when("/project/:pslug/us/:usref",
|
$routeProvider.when("/project/:pslug/us/:usref",
|
||||||
{templateUrl: "us/us-detail.html", resolve: {loader: tgLoaderProvider.add()}})
|
{templateUrl: "us/us-detail.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||||
|
@ -74,7 +80,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
||||||
$routeProvider.when("/project/:pslug/issue/:issueref",
|
$routeProvider.when("/project/:pslug/issue/:issueref",
|
||||||
{templateUrl: "issue/issues-detail.html", resolve: {loader: tgLoaderProvider.add()}})
|
{templateUrl: "issue/issues-detail.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||||
|
|
||||||
# Admin
|
# Admin - Project Profile
|
||||||
$routeProvider.when("/project/:pslug/admin/project-profile/details",
|
$routeProvider.when("/project/:pslug/admin/project-profile/details",
|
||||||
{templateUrl: "admin/admin-project-profile.html"})
|
{templateUrl: "admin/admin-project-profile.html"})
|
||||||
$routeProvider.when("/project/:pslug/admin/project-profile/default-values",
|
$routeProvider.when("/project/:pslug/admin/project-profile/default-values",
|
||||||
|
@ -83,24 +89,28 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
||||||
{templateUrl: "admin/admin-project-modules.html"})
|
{templateUrl: "admin/admin-project-modules.html"})
|
||||||
$routeProvider.when("/project/:pslug/admin/project-profile/export",
|
$routeProvider.when("/project/:pslug/admin/project-profile/export",
|
||||||
{templateUrl: "admin/admin-project-export.html"})
|
{templateUrl: "admin/admin-project-export.html"})
|
||||||
$routeProvider.when("/project/:pslug/admin/project-values/us-status",
|
$routeProvider.when("/project/:pslug/admin/project-profile/reports",
|
||||||
{templateUrl: "admin/admin-project-values-us-status.html"})
|
{templateUrl: "admin/admin-project-reports.html"})
|
||||||
$routeProvider.when("/project/:pslug/admin/project-values/us-points",
|
|
||||||
{templateUrl: "admin/admin-project-values-us-points.html"})
|
$routeProvider.when("/project/:pslug/admin/project-values/status",
|
||||||
$routeProvider.when("/project/:pslug/admin/project-values/task-status",
|
{templateUrl: "admin/admin-project-values-status.html"})
|
||||||
{templateUrl: "admin/admin-project-values-task-status.html"})
|
$routeProvider.when("/project/:pslug/admin/project-values/points",
|
||||||
$routeProvider.when("/project/:pslug/admin/project-values/issue-status",
|
{templateUrl: "admin/admin-project-values-points.html"})
|
||||||
{templateUrl: "admin/admin-project-values-issue-status.html"})
|
$routeProvider.when("/project/:pslug/admin/project-values/priorities",
|
||||||
$routeProvider.when("/project/:pslug/admin/project-values/issue-types",
|
{templateUrl: "admin/admin-project-values-priorities.html"})
|
||||||
{templateUrl: "admin/admin-project-values-issue-types.html"})
|
$routeProvider.when("/project/:pslug/admin/project-values/severities",
|
||||||
$routeProvider.when("/project/:pslug/admin/project-values/issue-priorities",
|
{templateUrl: "admin/admin-project-values-severities.html"})
|
||||||
{templateUrl: "admin/admin-project-values-issue-priorities.html"})
|
$routeProvider.when("/project/:pslug/admin/project-values/types",
|
||||||
$routeProvider.when("/project/:pslug/admin/project-values/issue-severities",
|
{templateUrl: "admin/admin-project-values-types.html"})
|
||||||
{templateUrl: "admin/admin-project-values-issue-severities.html"})
|
$routeProvider.when("/project/:pslug/admin/project-values/custom-fields",
|
||||||
|
{templateUrl: "admin/admin-project-values-custom-fields.html"})
|
||||||
|
|
||||||
$routeProvider.when("/project/:pslug/admin/memberships",
|
$routeProvider.when("/project/:pslug/admin/memberships",
|
||||||
{templateUrl: "admin/admin-memberships.html"})
|
{templateUrl: "admin/admin-memberships.html"})
|
||||||
|
# Admin - Roles
|
||||||
$routeProvider.when("/project/:pslug/admin/roles",
|
$routeProvider.when("/project/:pslug/admin/roles",
|
||||||
{templateUrl: "admin/admin-roles.html"})
|
{templateUrl: "admin/admin-roles.html"})
|
||||||
|
# Admin - Third Parties
|
||||||
$routeProvider.when("/project/:pslug/admin/third-parties/webhooks",
|
$routeProvider.when("/project/:pslug/admin/third-parties/webhooks",
|
||||||
{templateUrl: "admin/admin-third-parties-webhooks.html"})
|
{templateUrl: "admin/admin-third-parties-webhooks.html"})
|
||||||
$routeProvider.when("/project/:pslug/admin/third-parties/github",
|
$routeProvider.when("/project/:pslug/admin/third-parties/github",
|
||||||
|
@ -109,6 +119,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
||||||
{templateUrl: "admin/admin-third-parties-gitlab.html"})
|
{templateUrl: "admin/admin-third-parties-gitlab.html"})
|
||||||
$routeProvider.when("/project/:pslug/admin/third-parties/bitbucket",
|
$routeProvider.when("/project/:pslug/admin/third-parties/bitbucket",
|
||||||
{templateUrl: "admin/admin-third-parties-bitbucket.html"})
|
{templateUrl: "admin/admin-third-parties-bitbucket.html"})
|
||||||
|
# Admin - Contrib Plugins
|
||||||
$routeProvider.when("/project/:pslug/admin/contrib/:plugin",
|
$routeProvider.when("/project/:pslug/admin/contrib/:plugin",
|
||||||
{templateUrl: "contrib/main.html"})
|
{templateUrl: "contrib/main.html"})
|
||||||
|
|
||||||
|
@ -223,6 +234,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
||||||
linewidth: "The subject must have a maximum size of %s"
|
linewidth: "The subject must have a maximum size of %s"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
$compileProvider.debugInfoEnabled(window.taigaConfig.debugInfo || false)
|
||||||
|
|
||||||
init = ($log, $i18n, $config, $rootscope, $auth, $events, $analytics) ->
|
init = ($log, $i18n, $config, $rootscope, $auth, $events, $analytics) ->
|
||||||
$i18n.initialize($config.get("defaultLanguage"))
|
$i18n.initialize($config.get("defaultLanguage"))
|
||||||
$log.debug("Initialize application")
|
$log.debug("Initialize application")
|
||||||
|
@ -280,6 +293,7 @@ module.config([
|
||||||
"$provide",
|
"$provide",
|
||||||
"$tgEventsProvider",
|
"$tgEventsProvider",
|
||||||
"tgLoaderProvider",
|
"tgLoaderProvider",
|
||||||
|
"$compileProvider",
|
||||||
configure
|
configure
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,9 @@ MAX_MEMBERSHIP_FIELDSETS = 4
|
||||||
CreateMembersDirective = ($rs, $rootScope, $confirm, $loading ,lightboxService) ->
|
CreateMembersDirective = ($rs, $rootScope, $confirm, $loading ,lightboxService) ->
|
||||||
extraTextTemplate = """
|
extraTextTemplate = """
|
||||||
<fieldset class="extra-text">
|
<fieldset class="extra-text">
|
||||||
<textarea placeholder="(Optional) Add a personalized text to the invitation. Tell something lovely to your new members ;-)"></textarea>
|
<textarea placeholder="(Optional) Add a personalized text to the invitation. Tell something lovely to your new members ;-)"
|
||||||
|
maxlength="255">
|
||||||
|
</textarea>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -150,7 +152,6 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, $loading ,lightboxService)
|
||||||
submitButton = $el.find(".submit-button")
|
submitButton = $el.find(".submit-button")
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
return {link: link}
|
return {link: link}
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,9 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai
|
||||||
|
|
||||||
loadProject: ->
|
loadProject: ->
|
||||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||||
|
if not project.i_am_owner
|
||||||
|
@location.path(@navUrls.resolve("permission-denied"))
|
||||||
|
|
||||||
@scope.project = project
|
@scope.project = project
|
||||||
@scope.$emit('project:loaded', project)
|
@scope.$emit('project:loaded', project)
|
||||||
return project
|
return project
|
||||||
|
@ -307,7 +310,7 @@ MembershipsRowRoleSelectorDirective = ($log, $repo, $confirm) ->
|
||||||
member = $scope.$eval($attrs.tgMembershipsRowRoleSelector)
|
member = $scope.$eval($attrs.tgMembershipsRowRoleSelector)
|
||||||
html = render(member)
|
html = render(member)
|
||||||
|
|
||||||
$el.on "click", "select", (event) =>
|
$el.on "change", "select", (event) =>
|
||||||
onSuccess = ->
|
onSuccess = ->
|
||||||
$confirm.notify("success")
|
$confirm.notify("success")
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,9 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
|
|
||||||
loadProject: ->
|
loadProject: ->
|
||||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||||
|
if not project.i_am_owner
|
||||||
|
@location.path(@navUrls.resolve("permission-denied"))
|
||||||
|
|
||||||
@scope.project = project
|
@scope.project = project
|
||||||
@scope.pointsList = _.sortBy(project.points, "order")
|
@scope.pointsList = _.sortBy(project.points, "order")
|
||||||
@scope.usStatusList = _.sortBy(project.us_statuses, "order")
|
@scope.usStatusList = _.sortBy(project.us_statuses, "order")
|
||||||
|
@ -120,7 +123,6 @@ ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location) ->
|
||||||
submitButton = $el.find(".submit-button")
|
submitButton = $el.find(".submit-button")
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
return {link:link}
|
return {link:link}
|
||||||
|
|
||||||
|
@ -154,7 +156,6 @@ ProjectDefaultValuesDirective = ($repo, $confirm, $loading) ->
|
||||||
submitButton = $el.find(".submit-button")
|
submitButton = $el.find(".submit-button")
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
$el.off()
|
$el.off()
|
||||||
|
@ -233,7 +234,7 @@ ProjectExportDirective = ($window, $rs, $confirm) ->
|
||||||
resultTitleEl = $el.find(".result-title")
|
resultTitleEl = $el.find(".result-title")
|
||||||
setLoadingTitle = -> resultTitleEl.html("We are generating your dump file") # TODO: i18n
|
setLoadingTitle = -> resultTitleEl.html("We are generating your dump file") # TODO: i18n
|
||||||
setAsyncTitle = -> resultTitleEl.html("We are generating your dump file") # TODO: i18n
|
setAsyncTitle = -> resultTitleEl.html("We are generating your dump file") # TODO: i18n
|
||||||
setSyncTitle = -> resultTitleEl.html("Your dump file ir ready!") # TODO: i18n
|
setSyncTitle = -> resultTitleEl.html("Your dump file is ready!") # TODO: i18n
|
||||||
|
|
||||||
resultMessageEl = $el.find(".result-message ")
|
resultMessageEl = $el.find(".result-message ")
|
||||||
setLoadingMessage = -> resultMessageEl.html("Please don't close this page.") # TODO: i18n
|
setLoadingMessage = -> resultMessageEl.html("Please don't close this page.") # TODO: i18n
|
||||||
|
@ -296,3 +297,67 @@ ProjectExportDirective = ($window, $rs, $confirm) ->
|
||||||
return {link:link}
|
return {link:link}
|
||||||
|
|
||||||
module.directive("tgProjectExport", ["$window", "$tgResources", "$tgConfirm", ProjectExportDirective])
|
module.directive("tgProjectExport", ["$window", "$tgResources", "$tgConfirm", ProjectExportDirective])
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## CSV Export Controllers
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
class CsvExporterController extends taiga.Controller
|
||||||
|
@.$inject = [
|
||||||
|
"$scope",
|
||||||
|
"$rootScope",
|
||||||
|
"$tgUrls",
|
||||||
|
"$tgConfirm",
|
||||||
|
"$tgResources",
|
||||||
|
]
|
||||||
|
|
||||||
|
constructor: (@scope, @rootscope, @urls, @confirm, @rs) ->
|
||||||
|
@rootscope.$on("project:loaded", @.setCsvUuid)
|
||||||
|
@scope.$watch "csvUuid", (value) =>
|
||||||
|
if value
|
||||||
|
@scope.csvUrl = @urls.resolveAbsolute("#{@.type}-csv", value)
|
||||||
|
else
|
||||||
|
@scope.csvUrl = ""
|
||||||
|
|
||||||
|
setCsvUuid: =>
|
||||||
|
@scope.csvUuid = @scope.project["#{@.type}_csv_uuid"]
|
||||||
|
|
||||||
|
_generateUuid: (finish) =>
|
||||||
|
promise = @rs.projects["regenerate_#{@.type}_csv_uuid"](@scope.projectId)
|
||||||
|
|
||||||
|
promise.then (data) =>
|
||||||
|
@scope.csvUuid = data.data?.uuid
|
||||||
|
|
||||||
|
promise.then null, =>
|
||||||
|
@confirm.notify("error")
|
||||||
|
|
||||||
|
promise.finally ->
|
||||||
|
finish()
|
||||||
|
return promise
|
||||||
|
|
||||||
|
regenerateUuid: ->
|
||||||
|
#TODO: i18n
|
||||||
|
if @scope.csvUuid
|
||||||
|
title = "Change URL"
|
||||||
|
subtitle = "You going to change the CSV data access url. The previous url will be disabled. Are you sure?"
|
||||||
|
@confirm.ask(title, subtitle).then @._generateUuid
|
||||||
|
else
|
||||||
|
@._generateUuid(_.identity)
|
||||||
|
|
||||||
|
|
||||||
|
class CsvExporterUserstoriesController extends CsvExporterController
|
||||||
|
type: "userstories"
|
||||||
|
|
||||||
|
|
||||||
|
class CsvExporterTasksController extends CsvExporterController
|
||||||
|
type: "tasks"
|
||||||
|
|
||||||
|
|
||||||
|
class CsvExporterIssuesController extends CsvExporterController
|
||||||
|
type: "issues"
|
||||||
|
|
||||||
|
|
||||||
|
module.controller("CsvExporterUserstoriesController", CsvExporterUserstoriesController)
|
||||||
|
module.controller("CsvExporterTasksController", CsvExporterTasksController)
|
||||||
|
module.controller("CsvExporterIssuesController", CsvExporterIssuesController)
|
||||||
|
|
|
@ -32,10 +32,10 @@ debounce = @.taiga.debounce
|
||||||
module = angular.module("taigaAdmin")
|
module = angular.module("taigaAdmin")
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Project values Controller
|
## Project values section Controller
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
class ProjectValuesController extends mixOf(taiga.Controller, taiga.PageMixin)
|
class ProjectValuesSectionController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
@.$inject = [
|
@.$inject = [
|
||||||
"$scope",
|
"$scope",
|
||||||
"$rootScope",
|
"$rootScope",
|
||||||
|
@ -59,29 +59,47 @@ class ProjectValuesController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
|
|
||||||
promise.then null, @.onInitialDataError.bind(@)
|
promise.then null, @.onInitialDataError.bind(@)
|
||||||
|
|
||||||
@scope.$on("admin:project-values:move", @.moveValue)
|
|
||||||
|
|
||||||
loadProject: ->
|
loadProject: ->
|
||||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||||
|
if not project.i_am_owner
|
||||||
|
@location.path(@navUrls.resolve("permission-denied"))
|
||||||
|
|
||||||
@scope.project = project
|
@scope.project = project
|
||||||
@scope.$emit('project:loaded', project)
|
@scope.$emit('project:loaded', project)
|
||||||
return project
|
return project
|
||||||
|
|
||||||
loadValues: ->
|
|
||||||
return @rs[@scope.resource].listValues(@scope.projectId, @scope.type).then (values) =>
|
|
||||||
@scope.values = values
|
|
||||||
@scope.maxValueOrder = _.max(values, "order").order
|
|
||||||
return values
|
|
||||||
|
|
||||||
loadInitialData: ->
|
loadInitialData: ->
|
||||||
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
|
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
|
||||||
@scope.projectId = data.project
|
@scope.projectId = data.project
|
||||||
return data
|
return data
|
||||||
|
|
||||||
return promise.then( => @q.all([
|
return promise.then => @.loadProject()
|
||||||
@.loadProject(),
|
|
||||||
@.loadValues(),
|
|
||||||
]))
|
module.controller("ProjectValuesSectionController", ProjectValuesSectionController)
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## Project values Controller
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
class ProjectValuesController extends taiga.Controller
|
||||||
|
@.$inject = [
|
||||||
|
"$scope",
|
||||||
|
"$rootScope",
|
||||||
|
"$tgRepo",
|
||||||
|
"$tgConfirm",
|
||||||
|
"$tgResources",
|
||||||
|
]
|
||||||
|
|
||||||
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs) ->
|
||||||
|
@scope.$on("admin:project-values:move", @.moveValue)
|
||||||
|
@rootscope.$on("project:loaded", @.loadValues)
|
||||||
|
|
||||||
|
loadValues: =>
|
||||||
|
return @rs[@scope.resource].listValues(@scope.projectId, @scope.type).then (values) =>
|
||||||
|
@scope.values = values
|
||||||
|
@scope.maxValueOrder = _.max(values, "order").order
|
||||||
|
return values
|
||||||
|
|
||||||
moveValue: (ctx, itemValue, itemIndex) =>
|
moveValue: (ctx, itemValue, itemIndex) =>
|
||||||
values = @scope.values
|
values = @scope.values
|
||||||
|
@ -147,21 +165,14 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame) ->
|
||||||
$(document.body).scrollTop(table.offset().top + table.height())
|
$(document.body).scrollTop(table.offset().top + table.height())
|
||||||
|
|
||||||
if focus
|
if focus
|
||||||
$(".new-value input").focus()
|
$el.find(".new-value input:visible").first().focus()
|
||||||
|
|
||||||
submit = debounce 2000, =>
|
saveValue = (target) ->
|
||||||
promise = $repo.save($scope.project)
|
formEl = target.parents("form")
|
||||||
promise.then ->
|
form = formEl.checksley()
|
||||||
$confirm.notify("success")
|
|
||||||
|
|
||||||
promise.then null, (data) ->
|
|
||||||
$confirm.notify("error", data._error_message)
|
|
||||||
|
|
||||||
saveValue = debounce 2000, (target) ->
|
|
||||||
form = target.parents("form").checksley()
|
|
||||||
return if not form.validate()
|
return if not form.validate()
|
||||||
|
|
||||||
value = target.scope().value
|
value = formEl.scope().value
|
||||||
promise = $repo.save(value)
|
promise = $repo.save(value)
|
||||||
promise.then =>
|
promise.then =>
|
||||||
row = target.parents(".row.table-main")
|
row = target.parents(".row.table-main")
|
||||||
|
@ -169,25 +180,37 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame) ->
|
||||||
row.siblings(".visualization").removeClass('hidden')
|
row.siblings(".visualization").removeClass('hidden')
|
||||||
|
|
||||||
promise.then null, (data) ->
|
promise.then null, (data) ->
|
||||||
$confirm.notify("error")
|
form.setErrors(data)
|
||||||
|
|
||||||
|
saveNewValue = (target) ->
|
||||||
|
formEl = target.parents("form")
|
||||||
|
form = formEl.checksley()
|
||||||
|
return if not form.validate()
|
||||||
|
|
||||||
|
$scope.newValue.project = $scope.project.id
|
||||||
|
|
||||||
|
$scope.newValue.order = if $scope.maxValueOrder then $scope.maxValueOrder + 1 else 1
|
||||||
|
|
||||||
|
promise = $repo.create(valueType, $scope.newValue)
|
||||||
|
promise.then (data) =>
|
||||||
|
target.addClass("hidden")
|
||||||
|
|
||||||
|
$scope.values.push(data)
|
||||||
|
$scope.maxValueOrder = data.order
|
||||||
|
initializeNewValue()
|
||||||
|
|
||||||
|
promise.then null, (data) ->
|
||||||
form.setErrors(data)
|
form.setErrors(data)
|
||||||
|
|
||||||
cancel = (target) ->
|
cancel = (target) ->
|
||||||
row = target.parents(".row.table-main")
|
row = target.parents(".row.table-main")
|
||||||
value = target.scope().value
|
formEl = target.parents("form")
|
||||||
|
value = formEl.scope().value
|
||||||
$scope.$apply ->
|
$scope.$apply ->
|
||||||
row.addClass("hidden")
|
row.addClass("hidden")
|
||||||
value.revert()
|
value.revert()
|
||||||
row.siblings(".visualization").removeClass('hidden')
|
row.siblings(".visualization").removeClass('hidden')
|
||||||
|
|
||||||
$el.on "submit", "form", (event) ->
|
|
||||||
event.preventDefault()
|
|
||||||
submit()
|
|
||||||
|
|
||||||
$el.on "click", "form a.button-green", (event) ->
|
|
||||||
event.preventDefault()
|
|
||||||
submit()
|
|
||||||
|
|
||||||
$el.on "click", ".show-add-new", (event) ->
|
$el.on "click", ".show-add-new", (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
$el.find(".new-value").removeClass('hidden')
|
$el.find(".new-value").removeClass('hidden')
|
||||||
|
@ -196,29 +219,12 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame) ->
|
||||||
|
|
||||||
$el.on "click", ".add-new", debounce 2000, (event) ->
|
$el.on "click", ".add-new", debounce 2000, (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
form = $el.find(".new-value").parents("form").checksley()
|
target = $el.find(".new-value")
|
||||||
return if not form.validate()
|
saveNewValue(target)
|
||||||
|
|
||||||
$scope.newValue.project = $scope.project.id
|
|
||||||
|
|
||||||
$scope.newValue.order = if $scope.maxValueOrder then $scope.maxValueOrder + 1 else 1
|
|
||||||
|
|
||||||
promise = $repo.create(valueType, $scope.newValue)
|
|
||||||
promise.then =>
|
|
||||||
$ctrl.loadValues().then ->
|
|
||||||
animationFrame.add () ->
|
|
||||||
goToBottomList()
|
|
||||||
|
|
||||||
$el.find(".new-value").addClass("hidden")
|
|
||||||
initializeNewValue()
|
|
||||||
|
|
||||||
promise.then null, (data) ->
|
|
||||||
$confirm.notify("error")
|
|
||||||
form.setErrors(data)
|
|
||||||
|
|
||||||
$el.on "click", ".delete-new", (event) ->
|
$el.on "click", ".delete-new", (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
$el.find(".new-value").hide()
|
$el.find(".new-value").addClass("hidden")
|
||||||
initializeNewValue()
|
initializeNewValue()
|
||||||
|
|
||||||
$el.on "click", ".edit-value", (event) ->
|
$el.on "click", ".edit-value", (event) ->
|
||||||
|
@ -240,6 +246,14 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame) ->
|
||||||
target = angular.element(event.currentTarget)
|
target = angular.element(event.currentTarget)
|
||||||
cancel(target)
|
cancel(target)
|
||||||
|
|
||||||
|
$el.on "keyup", ".new-value input", (event) ->
|
||||||
|
if event.keyCode == 13
|
||||||
|
target = $el.find(".new-value")
|
||||||
|
saveNewValue(target)
|
||||||
|
else if event.keyCode == 27
|
||||||
|
$el.find(".new-value").addClass("hidden")
|
||||||
|
initializeNewValue()
|
||||||
|
|
||||||
$el.on "click", ".save", (event) ->
|
$el.on "click", ".save", (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
target = angular.element(event.currentTarget)
|
target = angular.element(event.currentTarget)
|
||||||
|
@ -253,7 +267,9 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame) ->
|
||||||
$el.on "click", ".delete-value", (event) ->
|
$el.on "click", ".delete-value", (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
target = angular.element(event.currentTarget)
|
target = angular.element(event.currentTarget)
|
||||||
value = target.scope().value
|
formEl = target.parents("form")
|
||||||
|
value = formEl.scope().value
|
||||||
|
|
||||||
choices = {}
|
choices = {}
|
||||||
_.each $scope.values, (option) ->
|
_.each $scope.values, (option) ->
|
||||||
if value.id != option.id
|
if value.id != option.id
|
||||||
|
@ -337,3 +353,276 @@ ColorSelectionDirective = () ->
|
||||||
}
|
}
|
||||||
|
|
||||||
module.directive("tgColorSelection", ColorSelectionDirective)
|
module.directive("tgColorSelection", ColorSelectionDirective)
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## Custom Attributes Controller
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
class ProjectCustomAttributesController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
|
@.$inject = [
|
||||||
|
"$scope",
|
||||||
|
"$rootScope",
|
||||||
|
"$tgRepo",
|
||||||
|
"$tgResources",
|
||||||
|
"$routeParams",
|
||||||
|
"$q",
|
||||||
|
"$tgLocation",
|
||||||
|
"$tgNavUrls",
|
||||||
|
"$appTitle",
|
||||||
|
]
|
||||||
|
|
||||||
|
constructor: (@scope, @rootscope, @repo, @rs, @params, @q, @location, @navUrls, @appTitle) ->
|
||||||
|
@scope.project = {}
|
||||||
|
|
||||||
|
@rootscope.$on "project:loaded", =>
|
||||||
|
@.loadCustomAttributes()
|
||||||
|
@appTitle.set("Project Custom Attributes - " + @scope.sectionName + " - " + @scope.project.name)
|
||||||
|
|
||||||
|
#########################
|
||||||
|
# Custom Attribute
|
||||||
|
#########################
|
||||||
|
|
||||||
|
loadCustomAttributes: =>
|
||||||
|
return @rs.customAttributes[@scope.type].list(@scope.projectId).then (customAttributes) =>
|
||||||
|
@scope.customAttributes = customAttributes
|
||||||
|
@scope.maxOrder = _.max(customAttributes, "order").order
|
||||||
|
return customAttributes
|
||||||
|
|
||||||
|
createCustomAttribute: (attrValues) =>
|
||||||
|
return @repo.create("custom-attributes/#{@scope.type}", attrValues)
|
||||||
|
|
||||||
|
saveCustomAttribute: (attrModel) =>
|
||||||
|
return @repo.save(attrModel)
|
||||||
|
|
||||||
|
deleteCustomAttribute: (attrModel) =>
|
||||||
|
return @repo.remove(attrModel)
|
||||||
|
|
||||||
|
moveCustomAttributes: (attrModel, newIndex) =>
|
||||||
|
customAttributes = @scope.customAttributes
|
||||||
|
r = customAttributes.indexOf(attrModel)
|
||||||
|
customAttributes.splice(r, 1)
|
||||||
|
customAttributes.splice(newIndex, 0, attrModel)
|
||||||
|
|
||||||
|
_.each customAttributes, (val, idx) ->
|
||||||
|
val.order = idx
|
||||||
|
|
||||||
|
@repo.saveAll(customAttributes)
|
||||||
|
|
||||||
|
|
||||||
|
module.controller("ProjectCustomAttributesController", ProjectCustomAttributesController)
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## Custom Attributes Directive
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
ProjectCustomAttributesDirective = ($log, $confirm, animationFrame) ->
|
||||||
|
link = ($scope, $el, $attrs) ->
|
||||||
|
$ctrl = $el.controller()
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
##################################
|
||||||
|
# Drag & Drop
|
||||||
|
##################################
|
||||||
|
sortableEl = $el.find(".js-sortable")
|
||||||
|
|
||||||
|
sortableEl.sortable({
|
||||||
|
handle: ".js-view-custom-field",
|
||||||
|
dropOnEmpty: true
|
||||||
|
revert: 400
|
||||||
|
axis: "y"
|
||||||
|
})
|
||||||
|
|
||||||
|
sortableEl.on "sortstop", (event, ui) ->
|
||||||
|
itemEl = ui.item
|
||||||
|
itemAttr = itemEl.scope().attr
|
||||||
|
itemIndex = itemEl.index()
|
||||||
|
$ctrl.moveCustomAttributes(itemAttr, itemIndex)
|
||||||
|
|
||||||
|
##################################
|
||||||
|
# New custom attribute
|
||||||
|
##################################
|
||||||
|
|
||||||
|
showCreateForm = ->
|
||||||
|
$el.find(".js-new-custom-field").removeClass("hidden")
|
||||||
|
$el.find(".js-new-custom-field input:visible").first().focus()
|
||||||
|
|
||||||
|
hideCreateForm = ->
|
||||||
|
$el.find(".js-new-custom-field").addClass("hidden")
|
||||||
|
|
||||||
|
showAddButton = ->
|
||||||
|
$el.find(".js-add-custom-field-button").removeClass("hidden")
|
||||||
|
|
||||||
|
hideAddButton = ->
|
||||||
|
$el.find(".js-add-custom-field-button").addClass("hidden")
|
||||||
|
|
||||||
|
showCancelButton = ->
|
||||||
|
$el.find(".js-cancel-new-custom-field-button").removeClass("hidden")
|
||||||
|
|
||||||
|
hideCancelButton = ->
|
||||||
|
$el.find(".js-cancel-new-custom-field-button").addClass("hidden")
|
||||||
|
|
||||||
|
resetNewAttr = ->
|
||||||
|
$scope.newAttr = {}
|
||||||
|
|
||||||
|
create = (formEl) ->
|
||||||
|
form = formEl.checksley()
|
||||||
|
return if not form.validate()
|
||||||
|
|
||||||
|
onSucces = =>
|
||||||
|
$ctrl.loadCustomAttributes()
|
||||||
|
hideCreateForm()
|
||||||
|
resetNewAttr()
|
||||||
|
$confirm.notify("success")
|
||||||
|
|
||||||
|
onError = (data) =>
|
||||||
|
form.setErrors(data)
|
||||||
|
|
||||||
|
attr = $scope.newAttr
|
||||||
|
attr.project = $scope.projectId
|
||||||
|
attr.order = if $scope.maxOrder then $scope.maxOrder + 1 else 1
|
||||||
|
|
||||||
|
$ctrl.createCustomAttribute(attr).then(onSucces, onError)
|
||||||
|
|
||||||
|
cancelCreate = ->
|
||||||
|
hideCreateForm()
|
||||||
|
resetNewAttr()
|
||||||
|
|
||||||
|
$scope.$watch "customAttributes", (customAttributes) ->
|
||||||
|
return if not customAttributes
|
||||||
|
|
||||||
|
if customAttributes.length == 0
|
||||||
|
hideCancelButton()
|
||||||
|
hideAddButton()
|
||||||
|
showCreateForm()
|
||||||
|
else
|
||||||
|
hideCreateForm()
|
||||||
|
showAddButton()
|
||||||
|
showCancelButton()
|
||||||
|
|
||||||
|
$el.on "click", ".js-add-custom-field-button", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
showCreateForm()
|
||||||
|
|
||||||
|
$el.on "click", ".js-create-custom-field-button", debounce 2000, (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
formEl = target.closest("form")
|
||||||
|
|
||||||
|
create(formEl)
|
||||||
|
|
||||||
|
$el.on "click", ".js-cancel-new-custom-field-button", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
cancelCreate()
|
||||||
|
|
||||||
|
$el.on "keyup", ".js-new-custom-field input", (event) ->
|
||||||
|
if event.keyCode == 13 # Enter
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
formEl = target.closest("form")
|
||||||
|
create(formEl)
|
||||||
|
else if event.keyCode == 27 # Esc
|
||||||
|
cancelCreate()
|
||||||
|
|
||||||
|
##################################
|
||||||
|
# Edit custom attribute
|
||||||
|
##################################
|
||||||
|
|
||||||
|
showEditForm = (formEl) ->
|
||||||
|
formEl.find(".js-view-custom-field").addClass("hidden")
|
||||||
|
formEl.find(".js-edit-custom-field").removeClass("hidden")
|
||||||
|
formEl.find(".js-edit-custom-field input:visible").first().focus().select()
|
||||||
|
|
||||||
|
hideEditForm = (formEl) ->
|
||||||
|
formEl.find(".js-edit-custom-field").addClass("hidden")
|
||||||
|
formEl.find(".js-view-custom-field").removeClass("hidden")
|
||||||
|
|
||||||
|
revertChangesInCustomAttribute = (formEl) ->
|
||||||
|
$scope.$apply ->
|
||||||
|
formEl.scope().attr.revert()
|
||||||
|
|
||||||
|
update = (formEl) ->
|
||||||
|
form = formEl.checksley()
|
||||||
|
return if not form.validate()
|
||||||
|
|
||||||
|
onSucces = =>
|
||||||
|
$ctrl.loadCustomAttributes()
|
||||||
|
hideEditForm(formEl)
|
||||||
|
$confirm.notify("success")
|
||||||
|
|
||||||
|
onError = (data) =>
|
||||||
|
form.setErrors(data)
|
||||||
|
|
||||||
|
attr = formEl.scope().attr
|
||||||
|
$ctrl.saveCustomAttribute(attr).then(onSucces, onError)
|
||||||
|
|
||||||
|
cancelUpdate = (formEl) ->
|
||||||
|
hideEditForm(formEl)
|
||||||
|
revertChangesInCustomAttribute(formEl)
|
||||||
|
|
||||||
|
$el.on "click", ".js-edit-custom-field-button", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
formEl = target.closest("form")
|
||||||
|
|
||||||
|
showEditForm(formEl)
|
||||||
|
|
||||||
|
$el.on "click", ".js-update-custom-field-button", debounce 2000, (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
formEl = target.closest("form")
|
||||||
|
|
||||||
|
update(formEl)
|
||||||
|
|
||||||
|
$el.on "click", ".js-cancel-edit-custom-field-button", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
formEl = target.closest("form")
|
||||||
|
|
||||||
|
cancelUpdate(formEl)
|
||||||
|
|
||||||
|
$el.on "keyup", ".js-edit-custom-field input", (event) ->
|
||||||
|
if event.keyCode == 13 # Enter
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
formEl = target.closest("form")
|
||||||
|
update(formEl)
|
||||||
|
else if event.keyCode == 27 # Esc
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
formEl = target.closest("form")
|
||||||
|
cancelUpdate(formEl)
|
||||||
|
|
||||||
|
##################################
|
||||||
|
# Delete custom attribute
|
||||||
|
##################################
|
||||||
|
|
||||||
|
deleteCustomAttribute = (formEl) ->
|
||||||
|
attr = formEl.scope().attr
|
||||||
|
|
||||||
|
title = "Delete custom attribute" # i18n
|
||||||
|
subtitle = "Remeber that all values in this custom field will be deleted.</br> Are you sure you want to continue?"
|
||||||
|
message = attr.name
|
||||||
|
$confirm.ask(title, subtitle, message).then (finish) ->
|
||||||
|
onSucces = ->
|
||||||
|
$ctrl.loadCustomAttributes().finally ->
|
||||||
|
finish()
|
||||||
|
|
||||||
|
onError = ->
|
||||||
|
finish(false)
|
||||||
|
$confirm.notify("error", null, "We have not been able to delete '#{message}'.")
|
||||||
|
|
||||||
|
$ctrl.deleteCustomAttribute(attr).then(onSucces, onError)
|
||||||
|
|
||||||
|
$el.on "click", ".js-delete-custom-field-button", debounce 2000, (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
formEl = target.closest("form")
|
||||||
|
|
||||||
|
deleteCustomAttribute(formEl)
|
||||||
|
|
||||||
|
return {link: link}
|
||||||
|
|
||||||
|
module.directive("tgProjectCustomAttributes", ["$log", "$tgConfirm", "animationFrame", ProjectCustomAttributesDirective])
|
||||||
|
|
|
@ -63,17 +63,40 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil
|
||||||
|
|
||||||
loadProject: ->
|
loadProject: ->
|
||||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||||
|
if not project.i_am_owner
|
||||||
|
@location.path(@navUrls.resolve("permission-denied"))
|
||||||
|
|
||||||
@scope.project = project
|
@scope.project = project
|
||||||
|
|
||||||
@scope.$emit('project:loaded', project)
|
@scope.$emit('project:loaded', project)
|
||||||
@scope.anyComputableRole = _.some(_.map(project.roles, (point) -> point.computable))
|
@scope.anyComputableRole = _.some(_.map(project.roles, (point) -> point.computable))
|
||||||
|
|
||||||
return project
|
return project
|
||||||
|
|
||||||
|
loadExternalUserRole: (roles) ->
|
||||||
|
roles = roles.map (role) ->
|
||||||
|
role.external_user = false
|
||||||
|
|
||||||
|
return role
|
||||||
|
|
||||||
|
public_permission = {
|
||||||
|
"name": "External User",
|
||||||
|
"permissions": @scope.project.public_permissions,
|
||||||
|
"external_user": true
|
||||||
|
}
|
||||||
|
|
||||||
|
roles.push(public_permission)
|
||||||
|
|
||||||
|
return roles
|
||||||
|
|
||||||
loadRoles: ->
|
loadRoles: ->
|
||||||
return @rs.roles.list(@scope.projectId).then (data) =>
|
return @rs.roles.list(@scope.projectId)
|
||||||
@scope.roles = data
|
.then @loadExternalUserRole
|
||||||
|
.then (roles) =>
|
||||||
|
@scope.roles = roles
|
||||||
@scope.role = @scope.roles[0]
|
@scope.role = @scope.roles[0]
|
||||||
return data
|
|
||||||
|
return roles
|
||||||
|
|
||||||
loadInitialData: ->
|
loadInitialData: ->
|
||||||
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
|
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
|
||||||
|
@ -256,7 +279,7 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm) ->
|
||||||
<div class="category-item" data-id="<%- permission.key %>">
|
<div class="category-item" data-id="<%- permission.key %>">
|
||||||
<span><%- permission.description %></span>
|
<span><%- permission.description %></span>
|
||||||
<div class="check">
|
<div class="check">
|
||||||
<input type="checkbox" <% if(permission.active) { %>checked="checked"<% } %>/>
|
<input type="checkbox" <% if(!permission.editable) { %>disabled="disabled"<% } %> <% if(permission.active) { %>checked="checked"<% } %>/>
|
||||||
<div></div>
|
<div></div>
|
||||||
<span class="check-text check-yes">Yes</span>
|
<span class="check-text check-yes">Yes</span>
|
||||||
<span class="check-text check-no">No</span>
|
<span class="check-text check-no">No</span>
|
||||||
|
@ -279,10 +302,23 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm) ->
|
||||||
setActivePermissions = (permissions) ->
|
setActivePermissions = (permissions) ->
|
||||||
return _.map(permissions, (x) -> _.extend({}, x, {active: x["key"] in role.permissions}))
|
return _.map(permissions, (x) -> _.extend({}, x, {active: x["key"] in role.permissions}))
|
||||||
|
|
||||||
|
isPermissionEditable = (permission, role, project) ->
|
||||||
|
if role.external_user &&
|
||||||
|
!project.is_private &&
|
||||||
|
permission.key.indexOf("view_") == 0
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
return true
|
||||||
|
|
||||||
setActivePermissionsPerCategory = (category) ->
|
setActivePermissionsPerCategory = (category) ->
|
||||||
return _.map(category, (x) ->
|
return _.map(category, (cat) ->
|
||||||
_.extend({}, x, {
|
cat.permissions = cat.permissions.map (permission) ->
|
||||||
activePermissions: _.filter(x["permissions"], "active").length
|
permission.editable = isPermissionEditable(permission, role, $scope.project)
|
||||||
|
|
||||||
|
return permission
|
||||||
|
|
||||||
|
_.extend({}, cat, {
|
||||||
|
activePermissions: _.filter(cat["permissions"], "active").length
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -366,10 +402,11 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm) ->
|
||||||
return activePermissions
|
return activePermissions
|
||||||
|
|
||||||
target = angular.element(event.currentTarget)
|
target = angular.element(event.currentTarget)
|
||||||
|
|
||||||
$scope.role.permissions = getActivePermissions()
|
$scope.role.permissions = getActivePermissions()
|
||||||
|
|
||||||
onSuccess = (role) ->
|
onSuccess = () ->
|
||||||
categories = generateCategoriesFromRole(role)
|
categories = generateCategoriesFromRole($scope.role)
|
||||||
categoryId = target.parents(".category-config").data("id")
|
categoryId = target.parents(".category-config").data("id")
|
||||||
renderResume(target.parents(".category-config"), categories[categoryId])
|
renderResume(target.parents(".category-config"), categories[categoryId])
|
||||||
$rootscope.$broadcast("projects:reload")
|
$rootscope.$broadcast("projects:reload")
|
||||||
|
@ -381,6 +418,13 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm) ->
|
||||||
target.prop "checked", !target.prop("checked")
|
target.prop "checked", !target.prop("checked")
|
||||||
$scope.role.permissions = getActivePermissions()
|
$scope.role.permissions = getActivePermissions()
|
||||||
|
|
||||||
|
if $scope.role.external_user
|
||||||
|
$scope.project.public_permissions = $scope.role.permissions
|
||||||
|
$scope.project.anon_permissions = $scope.role.permissions.filter (permission) ->
|
||||||
|
return permission.indexOf("view_") == 0
|
||||||
|
|
||||||
|
$repo.save($scope.project).then onSuccess, onError
|
||||||
|
else
|
||||||
$repo.save($scope.role).then onSuccess, onError
|
$repo.save($scope.role).then onSuccess, onError
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
|
|
|
@ -38,10 +38,12 @@ class WebhooksController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.
|
||||||
"$tgRepo",
|
"$tgRepo",
|
||||||
"$tgResources",
|
"$tgResources",
|
||||||
"$routeParams",
|
"$routeParams",
|
||||||
|
"$tgLocation",
|
||||||
|
"$tgNavUrls",
|
||||||
"$appTitle"
|
"$appTitle"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @repo, @rs, @params, @appTitle) ->
|
constructor: (@scope, @repo, @rs, @params, @location, @navUrls, @appTitle) ->
|
||||||
bindMethods(@)
|
bindMethods(@)
|
||||||
|
|
||||||
@scope.sectionName = "Webhooks" #i18n
|
@scope.sectionName = "Webhooks" #i18n
|
||||||
|
@ -62,6 +64,9 @@ class WebhooksController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.
|
||||||
|
|
||||||
loadProject: ->
|
loadProject: ->
|
||||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||||
|
if not project.i_am_owner
|
||||||
|
@location.path(@navUrls.resolve("permission-denied"))
|
||||||
|
|
||||||
@scope.project = project
|
@scope.project = project
|
||||||
@scope.$emit('project:loaded', project)
|
@scope.$emit('project:loaded', project)
|
||||||
return project
|
return project
|
||||||
|
@ -89,7 +94,7 @@ WebhookDirective = ($rs, $repo, $confirm, $loading) ->
|
||||||
for log in webhooklogs
|
for log in webhooklogs
|
||||||
log.validStatus = 200 <= log.status < 300
|
log.validStatus = 200 <= log.status < 300
|
||||||
log.prettySentHeaders = _.map(_.pairs(log.request_headers), ([header, value]) -> "#{header}: #{value}").join("\n")
|
log.prettySentHeaders = _.map(_.pairs(log.request_headers), ([header, value]) -> "#{header}: #{value}").join("\n")
|
||||||
log.prettySentData = JSON.stringify(log.request_data.data, undefined, 2)
|
log.prettySentData = JSON.stringify(log.request_data)
|
||||||
log.prettyDate = moment(log.created).format("DD MMM YYYY [at] hh:mm:ss") # TODO: i18n
|
log.prettyDate = moment(log.created).format("DD MMM YYYY [at] hh:mm:ss") # TODO: i18n
|
||||||
|
|
||||||
webhook.logs_counter = webhooklogs.length
|
webhook.logs_counter = webhooklogs.length
|
||||||
|
@ -123,8 +128,6 @@ WebhookDirective = ($rs, $repo, $confirm, $loading) ->
|
||||||
save = debounce 2000, (target) ->
|
save = debounce 2000, (target) ->
|
||||||
form = target.parents("form").checksley()
|
form = target.parents("form").checksley()
|
||||||
return if not form.validate()
|
return if not form.validate()
|
||||||
|
|
||||||
value = target.scope().value
|
|
||||||
promise = $repo.save(webhook)
|
promise = $repo.save(webhook)
|
||||||
promise.then =>
|
promise.then =>
|
||||||
showVisualizationMode()
|
showVisualizationMode()
|
||||||
|
@ -152,7 +155,7 @@ WebhookDirective = ($rs, $repo, $confirm, $loading) ->
|
||||||
$el.on "keyup", ".edition-mode input", (event) ->
|
$el.on "keyup", ".edition-mode input", (event) ->
|
||||||
if event.keyCode == 13
|
if event.keyCode == 13
|
||||||
target = angular.element(event.currentTarget)
|
target = angular.element(event.currentTarget)
|
||||||
saveWebhook(target)
|
save(target)
|
||||||
else if event.keyCode == 27
|
else if event.keyCode == 27
|
||||||
target = angular.element(event.currentTarget)
|
target = angular.element(event.currentTarget)
|
||||||
cancel(target)
|
cancel(target)
|
||||||
|
@ -231,8 +234,7 @@ NewWebhookDirective = ($rs, $repo, $confirm, $loading) ->
|
||||||
formDOMNode.addClass("hidden")
|
formDOMNode.addClass("hidden")
|
||||||
addWebhookDOMNode.removeClass("hidden")
|
addWebhookDOMNode.removeClass("hidden")
|
||||||
|
|
||||||
formDOMNode.on "click", ".add-new", debounce 2000, (event) ->
|
save = debounce 2000, () ->
|
||||||
event.preventDefault()
|
|
||||||
form = formDOMNode.checksley()
|
form = formDOMNode.checksley()
|
||||||
return if not form.validate()
|
return if not form.validate()
|
||||||
|
|
||||||
|
@ -246,6 +248,14 @@ NewWebhookDirective = ($rs, $repo, $confirm, $loading) ->
|
||||||
$confirm.notify("error")
|
$confirm.notify("error")
|
||||||
form.setErrors(data)
|
form.setErrors(data)
|
||||||
|
|
||||||
|
formDOMNode.on "click", ".add-new", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
save()
|
||||||
|
|
||||||
|
formDOMNode.on "keyup", "input", (event) ->
|
||||||
|
if event.keyCode == 13
|
||||||
|
save()
|
||||||
|
|
||||||
formDOMNode.on "click", ".cancel-new", (event) ->
|
formDOMNode.on "click", ".cancel-new", (event) ->
|
||||||
$scope.$apply ->
|
$scope.$apply ->
|
||||||
initializeNewValue()
|
initializeNewValue()
|
||||||
|
@ -445,7 +455,6 @@ GithubWebhooksDirective = ($repo, $confirm, $loading) ->
|
||||||
submitButton = $el.find(".submit-button")
|
submitButton = $el.find(".submit-button")
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
return {link:link}
|
return {link:link}
|
||||||
|
|
||||||
|
@ -481,7 +490,6 @@ GitlabWebhooksDirective = ($repo, $confirm, $loading) ->
|
||||||
submitButton = $el.find(".submit-button")
|
submitButton = $el.find(".submit-button")
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
return {link:link}
|
return {link:link}
|
||||||
|
|
||||||
|
@ -517,7 +525,6 @@ BitbucketWebhooksDirective = ($repo, $confirm, $loading) ->
|
||||||
submitButton = $el.find(".submit-button")
|
submitButton = $el.find(".submit-button")
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
return {link:link}
|
return {link:link}
|
||||||
|
|
||||||
|
|
|
@ -197,11 +197,12 @@ LoginDirective = ($auth, $confirm, $location, $config, $routeParams, $navUrls, $
|
||||||
"password": $el.find("form.login-form input[name=password]").val()
|
"password": $el.find("form.login-form input[name=password]").val()
|
||||||
}
|
}
|
||||||
|
|
||||||
promise = $auth.login(data)
|
loginFormType = $config.get("loginFormType", "normal")
|
||||||
|
|
||||||
|
promise = $auth.login(data, loginFormType)
|
||||||
return promise.then(onSuccess, onError)
|
return promise.then(onSuccess, onError)
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
return {link:link}
|
return {link:link}
|
||||||
|
|
||||||
|
@ -242,7 +243,6 @@ RegisterDirective = ($auth, $confirm, $location, $navUrls, $config, $analytics)
|
||||||
promise.then(onSuccessSubmit, onErrorSubmit)
|
promise.then(onSuccessSubmit, onErrorSubmit)
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
return {link:link}
|
return {link:link}
|
||||||
|
|
||||||
|
@ -279,7 +279,6 @@ ForgotPasswordDirective = ($auth, $confirm, $location, $navUrls) ->
|
||||||
promise.then(onSuccessSubmit, onErrorSubmit)
|
promise.then(onSuccessSubmit, onErrorSubmit)
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
return {link:link}
|
return {link:link}
|
||||||
|
|
||||||
|
@ -321,7 +320,6 @@ ChangePasswordFromRecoveryDirective = ($auth, $confirm, $location, $params, $nav
|
||||||
promise.then(onSuccessSubmit, onErrorSubmit)
|
promise.then(onSuccessSubmit, onErrorSubmit)
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
return {link:link}
|
return {link:link}
|
||||||
|
|
||||||
|
@ -471,7 +469,6 @@ CancelAccountDirective = ($repo, $model, $auth, $confirm, $location, $params, $n
|
||||||
promise.then(onSuccessSubmit, onErrorSubmit)
|
promise.then(onSuccessSubmit, onErrorSubmit)
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
return {link:link}
|
return {link:link}
|
||||||
|
|
||||||
|
|
|
@ -89,12 +89,10 @@ BacklogFiltersDirective = ($log, $location, $templates) ->
|
||||||
selectedFilters.push(filter)
|
selectedFilters.push(filter)
|
||||||
$scope.$apply ->
|
$scope.$apply ->
|
||||||
$ctrl.selectFilter(type, id)
|
$ctrl.selectFilter(type, id)
|
||||||
$ctrl.filterVisibleUserstories()
|
|
||||||
else
|
else
|
||||||
selectedFilters = _.reject(selectedFilters, filter)
|
selectedFilters = _.reject(selectedFilters, filter)
|
||||||
$scope.$apply ->
|
$scope.$apply ->
|
||||||
$ctrl.unselectFilter(type, id)
|
$ctrl.unselectFilter(type, id)
|
||||||
$ctrl.filterVisibleUserstories()
|
|
||||||
|
|
||||||
renderSelectedFilters(selectedFilters)
|
renderSelectedFilters(selectedFilters)
|
||||||
|
|
||||||
|
|
|
@ -103,6 +103,9 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading)
|
||||||
$repo.remove($scope.sprint).then(onSuccess, onError)
|
$repo.remove($scope.sprint).then(onSuccess, onError)
|
||||||
|
|
||||||
$scope.$on "sprintform:create", (event, projectId) ->
|
$scope.$on "sprintform:create", (event, projectId) ->
|
||||||
|
form = $el.find("form").checksley()
|
||||||
|
form.reset()
|
||||||
|
|
||||||
createSprint = true
|
createSprint = true
|
||||||
$scope.sprint.project = projectId
|
$scope.sprint.project = projectId
|
||||||
$scope.sprint.name = null
|
$scope.sprint.name = null
|
||||||
|
@ -158,7 +161,6 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading)
|
||||||
submitButton = $el.find(".submit-button")
|
submitButton = $el.find(".submit-button")
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
$el.on "click", ".delete-sprint .icon-delete", (event) ->
|
$el.on "click", ".delete-sprint .icon-delete", (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
|
@ -59,7 +59,6 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
@scope.sectionName = "Backlog"
|
@scope.sectionName = "Backlog"
|
||||||
@showTags = false
|
@showTags = false
|
||||||
@activeFilters = false
|
@activeFilters = false
|
||||||
@excludeClosedSprints = true
|
|
||||||
|
|
||||||
@.initializeEventHandlers()
|
@.initializeEventHandlers()
|
||||||
|
|
||||||
|
@ -111,7 +110,8 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
@scope.$on("sprint:us:moved", @.loadSprints)
|
@scope.$on("sprint:us:moved", @.loadSprints)
|
||||||
@scope.$on("sprint:us:moved", @.loadProjectStats)
|
@scope.$on("sprint:us:moved", @.loadProjectStats)
|
||||||
|
|
||||||
@scope.$on("backlog:toggle-closed-sprints-visualization", @.toggleClosedSprintsVisualization)
|
@scope.$on("backlog:load-closed-sprints", @.loadClosedSprints)
|
||||||
|
@scope.$on("backlog:unload-closed-sprints", @.unloadClosedSprints)
|
||||||
|
|
||||||
initializeSubscription: ->
|
initializeSubscription: ->
|
||||||
routingKey1 = "changes.project.#{@scope.projectId}.userstories"
|
routingKey1 = "changes.project.#{@scope.projectId}.userstories"
|
||||||
|
@ -146,11 +146,23 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
return @rs.projects.tagsColors(@scope.projectId).then (tags_colors) =>
|
return @rs.projects.tagsColors(@scope.projectId).then (tags_colors) =>
|
||||||
@scope.project.tags_colors = tags_colors
|
@scope.project.tags_colors = tags_colors
|
||||||
|
|
||||||
loadSprints: ->
|
unloadClosedSprints: ->
|
||||||
params = {}
|
@scope.$apply =>
|
||||||
if @excludeClosedSprints
|
@scope.closedSprints = []
|
||||||
params["closed"] = false
|
@rootscope.$broadcast("closed-sprints:reloaded", [])
|
||||||
|
|
||||||
|
loadClosedSprints: ->
|
||||||
|
params = {closed: true}
|
||||||
|
return @rs.sprints.list(@scope.projectId, params).then (sprints) =>
|
||||||
|
# NOTE: Fix order of USs because the filter orderBy does not work propertly in partials files
|
||||||
|
for sprint in sprints
|
||||||
|
sprint.user_stories = _.sortBy(sprint.user_stories, "sprint_order")
|
||||||
|
@scope.closedSprints = sprints
|
||||||
|
@rootscope.$broadcast("closed-sprints:reloaded", sprints)
|
||||||
|
return sprints
|
||||||
|
|
||||||
|
loadSprints: ->
|
||||||
|
params = {closed: false}
|
||||||
return @rs.sprints.list(@scope.projectId, params).then (sprints) =>
|
return @rs.sprints.list(@scope.projectId, params).then (sprints) =>
|
||||||
# NOTE: Fix order of USs because the filter orderBy does not work propertly in partials files
|
# NOTE: Fix order of USs because the filter orderBy does not work propertly in partials files
|
||||||
for sprint in sprints
|
for sprint in sprints
|
||||||
|
@ -158,9 +170,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
|
|
||||||
@scope.sprints = sprints
|
@scope.sprints = sprints
|
||||||
@scope.openSprints = _.filter(sprints, (sprint) => not sprint.closed).reverse()
|
@scope.openSprints = _.filter(sprints, (sprint) => not sprint.closed).reverse()
|
||||||
@scope.closedSprints = _.filter(sprints, (sprint) => sprint.closed)
|
@scope.closedSprints = [] if !@scope.closedSprints
|
||||||
if not @excludeClosedSprints
|
|
||||||
@scope.totalClosedMilestones = @scope.closedSprints.length
|
|
||||||
|
|
||||||
@scope.sprintsCounter = sprints.length
|
@scope.sprintsCounter = sprints.length
|
||||||
@scope.sprintsById = groupBy(sprints, (x) -> x.id)
|
@scope.sprintsById = groupBy(sprints, (x) -> x.id)
|
||||||
|
@ -194,8 +204,9 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
# NOTE: Fix order of USs because the filter orderBy does not work propertly in the partials files
|
# NOTE: Fix order of USs because the filter orderBy does not work propertly in the partials files
|
||||||
@scope.userstories = _.sortBy(userstories, "backlog_order")
|
@scope.userstories = _.sortBy(userstories, "backlog_order")
|
||||||
|
|
||||||
@.generateFilters()
|
@.setSearchDataFilters()
|
||||||
@.filterVisibleUserstories()
|
@.filterVisibleUserstories()
|
||||||
|
@.generateFilters()
|
||||||
|
|
||||||
@rootscope.$broadcast("filters:loaded", @scope.filters)
|
@rootscope.$broadcast("filters:loaded", @scope.filters)
|
||||||
# The broadcast must be executed when the DOM has been fully reloaded.
|
# The broadcast must be executed when the DOM has been fully reloaded.
|
||||||
|
@ -214,6 +225,9 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
|
|
||||||
loadProject: ->
|
loadProject: ->
|
||||||
return @rs.projects.getBySlug(@params.pslug).then (project) =>
|
return @rs.projects.getBySlug(@params.pslug).then (project) =>
|
||||||
|
if not project.is_backlog_activated
|
||||||
|
@location.path(@navUrls.resolve("permission-denied"))
|
||||||
|
|
||||||
@scope.projectId = project.id
|
@scope.projectId = project.id
|
||||||
@scope.project = project
|
@scope.project = project
|
||||||
@scope.totalClosedMilestones = project.total_closed_milestones
|
@scope.totalClosedMilestones = project.total_closed_milestones
|
||||||
|
@ -232,40 +246,20 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
|
|
||||||
return promise.then(=> @.loadBacklog())
|
return promise.then(=> @.loadBacklog())
|
||||||
|
|
||||||
toggleClosedSprintsVisualization: ->
|
|
||||||
@excludeClosedSprints = not @excludeClosedSprints
|
|
||||||
@.loadSprints()
|
|
||||||
|
|
||||||
filterVisibleUserstories: ->
|
filterVisibleUserstories: ->
|
||||||
@scope.visibleUserstories = []
|
@scope.visibleUserstories = []
|
||||||
|
|
||||||
# Filter by tags
|
# Filter by tags
|
||||||
selectedTags = _.filter(@scope.filters.tags, "selected")
|
|
||||||
selectedTags = _.map(selectedTags, "name")
|
|
||||||
|
|
||||||
if selectedTags.length == 0
|
|
||||||
@scope.visibleUserstories = _.clone(@scope.userstories, false)
|
|
||||||
else
|
|
||||||
@scope.visibleUserstories = _.reject @scope.userstories, (us) =>
|
@scope.visibleUserstories = _.reject @scope.userstories, (us) =>
|
||||||
if _.intersection(selectedTags, us.tags).length == 0
|
return _.some us.tags, (tag) =>
|
||||||
return true
|
return @isFilterSelected("tag", tag)
|
||||||
return false
|
|
||||||
|
|
||||||
# Filter by status
|
# Filter by status
|
||||||
selectedStatuses = _.filter(@scope.filters.statuses, "selected")
|
@scope.visibleUserstories = _.filter @scope.visibleUserstories, (us) =>
|
||||||
selectedStatuses = _.map(selectedStatuses, "id")
|
if @searchdata["statuses"] && Object.keys(@searchdata["statuses"]).length
|
||||||
|
return @isFilterSelected("statuses", taiga.toString(us.status))
|
||||||
|
|
||||||
if selectedStatuses.length > 0
|
return true
|
||||||
@scope.visibleUserstories = _.reject @scope.visibleUserstories, (us) =>
|
|
||||||
res = _.find(selectedStatuses, (x) -> x == taiga.toString(us.status))
|
|
||||||
return not res
|
|
||||||
|
|
||||||
@rs.userstories.storeQueryParams(@scope.projectId, {
|
|
||||||
"status": selectedStatuses,
|
|
||||||
"tags": selectedTags,
|
|
||||||
"project": @scope.projectId
|
|
||||||
"milestone": null
|
|
||||||
})
|
|
||||||
|
|
||||||
prepareBulkUpdateData: (uses, field="backlog_order") ->
|
prepareBulkUpdateData: (uses, field="backlog_order") ->
|
||||||
return _.map(uses, (x) -> {"us_id": x.id, "order": x[field]})
|
return _.map(uses, (x) -> {"us_id": x.id, "order": x[field]})
|
||||||
|
@ -422,31 +416,33 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
|
|
||||||
return promise
|
return promise
|
||||||
|
|
||||||
getUrlFilters: ->
|
isFilterSelected: (type, id) ->
|
||||||
return _.pick(@location.search(), "statuses", "tags", "q")
|
if @searchdata[type]? and @searchdata[type][id]
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
generateFilters: ->
|
setSearchDataFilters: () ->
|
||||||
urlfilters = @.getUrlFilters()
|
urlfilters = @.getUrlFilters()
|
||||||
|
|
||||||
if urlfilters.q
|
if urlfilters.q
|
||||||
@scope.filtersQ = @scope.filtersQ or urlfilters.q
|
@scope.filtersQ = @scope.filtersQ or urlfilters.q
|
||||||
|
|
||||||
searchdata = {}
|
@searchdata = {}
|
||||||
for name, value of urlfilters
|
for name, value of urlfilters
|
||||||
if not searchdata[name]?
|
if not @searchdata[name]?
|
||||||
searchdata[name] = {}
|
@searchdata[name] = {}
|
||||||
|
|
||||||
for val in taiga.toString(value).split(",")
|
for val in taiga.toString(value).split(",")
|
||||||
searchdata[name][val] = true
|
@searchdata[name][val] = true
|
||||||
|
|
||||||
isSelected = (type, id) ->
|
getUrlFilters: ->
|
||||||
if searchdata[type]? and searchdata[type][id]
|
return _.pick(@location.search(), "statuses", "tags", "q")
|
||||||
return true
|
|
||||||
return false
|
|
||||||
|
|
||||||
|
generateFilters: ->
|
||||||
@scope.filters = {}
|
@scope.filters = {}
|
||||||
|
|
||||||
plainTags = _.flatten(_.filter(_.map(@scope.userstories, "tags")))
|
#tags
|
||||||
|
plainTags = _.flatten(_.filter(_.map(@scope.visibleUserstories, "tags")))
|
||||||
plainTags.sort()
|
plainTags.sort()
|
||||||
|
|
||||||
@scope.filters.tags = _.map _.countBy(plainTags), (v, k) =>
|
@scope.filters.tags = _.map _.countBy(plainTags), (v, k) =>
|
||||||
|
@ -457,10 +453,14 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
color: @scope.project.tags_colors[k],
|
color: @scope.project.tags_colors[k],
|
||||||
count: v
|
count: v
|
||||||
}
|
}
|
||||||
obj.selected = true if isSelected("tags", obj.id)
|
obj.selected = true if @isFilterSelected("tags", obj.id)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
plainStatuses = _.map(@scope.userstories, "status")
|
selectedTags = _.filter(@scope.filters.tags, "selected")
|
||||||
|
selectedTags = _.map(selectedTags, "name")
|
||||||
|
|
||||||
|
#status
|
||||||
|
plainStatuses = _.map(@scope.visibleUserstories, "status")
|
||||||
|
|
||||||
plainStatuses = _.filter plainStatuses, (status) =>
|
plainStatuses = _.filter plainStatuses, (status) =>
|
||||||
if status
|
if status
|
||||||
|
@ -474,11 +474,20 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
color: @scope.usStatusById[k].color,
|
color: @scope.usStatusById[k].color,
|
||||||
count:v
|
count:v
|
||||||
}
|
}
|
||||||
obj.selected = true if isSelected("statuses", obj.id)
|
obj.selected = true if @isFilterSelected("statuses", obj.id)
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
return @scope.filters
|
selectedStatuses = _.filter(@scope.filters.statuses, "selected")
|
||||||
|
selectedStatuses = _.map(selectedStatuses, "id")
|
||||||
|
|
||||||
|
#store query params
|
||||||
|
@rs.userstories.storeQueryParams(@scope.projectId, {
|
||||||
|
"status": selectedStatuses,
|
||||||
|
"tags": selectedTags,
|
||||||
|
"project": @scope.projectId
|
||||||
|
"milestone": null
|
||||||
|
})
|
||||||
|
|
||||||
## Template actions
|
## Template actions
|
||||||
|
|
||||||
|
@ -514,7 +523,6 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
|
|
||||||
module.controller("BacklogController", BacklogController)
|
module.controller("BacklogController", BacklogController)
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Backlog Directive
|
## Backlog Directive
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
@ -531,29 +539,27 @@ BacklogDirective = ($repo, $rootscope) ->
|
||||||
if $scope.stats?
|
if $scope.stats?
|
||||||
removeDoomlineDom()
|
removeDoomlineDom()
|
||||||
|
|
||||||
elements = getUsItems()
|
|
||||||
stats = $scope.stats
|
stats = $scope.stats
|
||||||
|
|
||||||
total_points = stats.total_points
|
total_points = stats.total_points
|
||||||
current_sum = stats.assigned_points
|
current_sum = stats.assigned_points
|
||||||
|
|
||||||
for element in elements
|
return if not $scope.visibleUserstories
|
||||||
scope = element.scope()
|
|
||||||
|
|
||||||
if not scope.us?
|
for us, i in $scope.visibleUserstories
|
||||||
continue
|
current_sum += us.total_points
|
||||||
|
|
||||||
current_sum += scope.us.total_points
|
|
||||||
|
|
||||||
if current_sum > total_points
|
if current_sum > total_points
|
||||||
addDoomLineDom(element)
|
domElement = $el.find('.backlog-table-body .us-item-row')[i]
|
||||||
|
addDoomLineDom(domElement)
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
removeDoomlineDom = ->
|
removeDoomlineDom = ->
|
||||||
$el.find(".doom-line").remove()
|
$el.find(".doom-line").remove()
|
||||||
|
|
||||||
addDoomLineDom = (element) ->
|
addDoomLineDom = (element) ->
|
||||||
element?.before(doomLineTemplate({}))
|
$(element).before(doomLineTemplate({}))
|
||||||
|
|
||||||
getUsItems = ->
|
getUsItems = ->
|
||||||
rowElements = $el.find('.backlog-table-body .us-item-row')
|
rowElements = $el.find('.backlog-table-body .us-item-row')
|
||||||
|
@ -604,7 +610,8 @@ BacklogDirective = ($repo, $rootscope) ->
|
||||||
ussDom = $el.find(".backlog-table-body .user-stories input:checkbox:checked")
|
ussDom = $el.find(".backlog-table-body .user-stories input:checkbox:checked")
|
||||||
|
|
||||||
ussToMove = _.map ussDom, (item) ->
|
ussToMove = _.map ussDom, (item) ->
|
||||||
itemScope = angular.element(item).scope()
|
item = $(item).closest('.tg-scope')
|
||||||
|
itemScope = item.scope()
|
||||||
itemScope.us.milestone = $scope.sprints[0].id
|
itemScope.us.milestone = $scope.sprints[0].id
|
||||||
return itemScope.us
|
return itemScope.us
|
||||||
|
|
||||||
|
@ -727,7 +734,6 @@ UsRolePointsSelectorDirective = ($rootscope, $template) ->
|
||||||
$el.on "click", ".role", (event) ->
|
$el.on "click", ".role", (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
target = angular.element(event.currentTarget)
|
target = angular.element(event.currentTarget)
|
||||||
rolScope = target.scope()
|
rolScope = target.scope()
|
||||||
$rootscope.$broadcast("uspoints:select", target.data("role-id"), target.text())
|
$rootscope.$broadcast("uspoints:select", target.data("role-id"), target.text())
|
||||||
|
@ -740,170 +746,107 @@ UsRolePointsSelectorDirective = ($rootscope, $template) ->
|
||||||
module.directive("tgUsRolePointsSelector", ["$rootScope", "$tgTemplate", UsRolePointsSelectorDirective])
|
module.directive("tgUsRolePointsSelector", ["$rootScope", "$tgTemplate", UsRolePointsSelectorDirective])
|
||||||
|
|
||||||
|
|
||||||
UsPointsDirective = ($repo, $tgTemplate) ->
|
UsPointsDirective = ($tgEstimationsService, $repo, $tgTemplate) ->
|
||||||
rolesTemplate = $tgTemplate.get("backlog/us-points-roles-popover.html", true)
|
rolesTemplate = $tgTemplate.get("common/estimation/us-points-roles-popover.html", true)
|
||||||
pointsTemplate = $tgTemplate.get("backlog/us-points-popover.html", true)
|
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
$ctrl = $el.controller()
|
$ctrl = $el.controller()
|
||||||
|
|
||||||
us = $scope.$eval($attrs.tgBacklogUsPoints)
|
|
||||||
|
|
||||||
updatingSelectedRoleId = null
|
updatingSelectedRoleId = null
|
||||||
selectedRoleId = null
|
selectedRoleId = null
|
||||||
numberOfRoles = _.size(us.points)
|
filteringRoleId = null
|
||||||
|
estimationProcess = null
|
||||||
|
|
||||||
# Preselect the role if we have only one
|
$scope.$on "uspoints:select", (ctx, roleId, roleName) ->
|
||||||
if numberOfRoles == 1
|
us = $scope.$eval($attrs.tgBacklogUsPoints)
|
||||||
selectedRoleId = _.keys(us.points)[0]
|
selectedRoleId = roleId
|
||||||
|
estimationProcess.render()
|
||||||
|
|
||||||
roles = []
|
$scope.$on "uspoints:clear-selection", (ctx) ->
|
||||||
updatePointsRoles = ->
|
us = $scope.$eval($attrs.tgBacklogUsPoints)
|
||||||
roles = _.map computableRoles, (role) ->
|
selectedRoleId = null
|
||||||
pointId = us.points[role.id]
|
estimationProcess.render()
|
||||||
pointObj = $scope.pointsById[pointId]
|
|
||||||
|
|
||||||
role = _.clone(role, true)
|
$scope.$watch $attrs.tgBacklogUsPoints, (us) ->
|
||||||
role.points = if pointObj.value? then pointObj.value else "?"
|
if us
|
||||||
return role
|
estimationProcess = $tgEstimationsService.create($el, us, $scope.project)
|
||||||
|
|
||||||
computableRoles = _.filter($scope.project.roles, "computable")
|
|
||||||
updatePointsRoles()
|
|
||||||
|
|
||||||
|
# Update roles
|
||||||
|
roles = estimationProcess.calculateRoles()
|
||||||
if roles.length == 0
|
if roles.length == 0
|
||||||
$el.find(".icon-arrow-bottom").remove()
|
$el.find(".icon-arrow-bottom").remove()
|
||||||
$el.find("a.us-points").addClass("not-clickable")
|
$el.find("a.us-points").addClass("not-clickable")
|
||||||
|
|
||||||
renderPointsSelector = (us, roleId) ->
|
else if roles.length == 1
|
||||||
# Prepare data for rendering
|
# Preselect the role if we have only one
|
||||||
points = _.map $scope.project.points, (point) ->
|
selectedRoleId = _.keys(us.points)[0]
|
||||||
point = _.clone(point, true)
|
|
||||||
point.selected = if us.points[roleId] == point.id then false else true
|
|
||||||
return point
|
|
||||||
|
|
||||||
html = pointsTemplate({"points": points})
|
if estimationProcess.isEditable
|
||||||
|
bindClickElements()
|
||||||
|
|
||||||
# Remove any prevous state
|
estimationProcess.onSelectedPointForRole = (roleId, pointId) ->
|
||||||
$el.find(".popover").popover().close()
|
@save(roleId, pointId).then ->
|
||||||
$el.find(".pop-points-open").remove()
|
$ctrl.loadProjectStats()
|
||||||
|
|
||||||
# Render into DOM and show the new created element
|
estimationProcess.render = () ->
|
||||||
$el.append(html)
|
totalPoints = @calculateTotalPoints()
|
||||||
|
if not selectedRoleId? or roles.length == 1
|
||||||
|
text = totalPoints
|
||||||
|
title = totalPoints
|
||||||
|
else
|
||||||
|
pointId = @us.points[selectedRoleId]
|
||||||
|
pointObj = @pointsById[pointId]
|
||||||
|
text = "#{pointObj.name} / <span>#{totalPoints}</span>"
|
||||||
|
title = "#{pointObj.name} / #{totalPoints}"
|
||||||
|
|
||||||
# If not showing role selection let's move to the left
|
ctx = {
|
||||||
if not $el.find(".pop-role:visible").css("left")?
|
totalPoints: totalPoints
|
||||||
$el.find(".pop-points-open").css("left", "110px")
|
roles: @calculateRoles()
|
||||||
|
editable: @isEditable
|
||||||
|
text: text
|
||||||
|
title: title
|
||||||
|
}
|
||||||
|
mainTemplate = "common/estimation/us-estimation-total.html"
|
||||||
|
template = $tgTemplate.get(mainTemplate, true)
|
||||||
|
html = template(ctx)
|
||||||
|
@$el.html(html)
|
||||||
|
|
||||||
$el.find(".pop-points-open").popover().open()
|
estimationProcess.render()
|
||||||
|
|
||||||
renderRolesSelector = (us) ->
|
|
||||||
updatePointsRoles()
|
|
||||||
|
|
||||||
|
renderRolesSelector = () ->
|
||||||
|
roles = estimationProcess.calculateRoles()
|
||||||
html = rolesTemplate({"roles": roles})
|
html = rolesTemplate({"roles": roles})
|
||||||
|
|
||||||
# Render into DOM and show the new created element
|
# Render into DOM and show the new created element
|
||||||
$el.append(html)
|
$el.append(html)
|
||||||
$el.find(".pop-role").popover().open(() -> $(this).remove())
|
$el.find(".pop-role").popover().open(() -> $(this).remove())
|
||||||
|
|
||||||
renderPoints = (us, roleId) ->
|
bindClickElements = () ->
|
||||||
dom = $el.find("a > span.points-value")
|
|
||||||
|
|
||||||
if roleId == null or numberOfRoles == 1
|
|
||||||
totalPoints = if us.total_points? then us.total_points else "?"
|
|
||||||
dom.text(totalPoints)
|
|
||||||
dom.parent().prop("title", totalPoints)
|
|
||||||
else
|
|
||||||
pointId = us.points[roleId]
|
|
||||||
pointObj = $scope.pointsById[pointId]
|
|
||||||
dom.html("#{pointObj.name} / <span>#{us.total_points}</span>")
|
|
||||||
dom.parent().prop("title", "#{pointObj.name} / #{us.total_points}")
|
|
||||||
|
|
||||||
calculateTotalPoints = ->
|
|
||||||
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)
|
|
||||||
|
|
||||||
$scope.$watch $attrs.tgBacklogUsPoints, (us) ->
|
|
||||||
renderPoints(us, selectedRoleId) if us
|
|
||||||
|
|
||||||
$scope.$on "uspoints:select", (ctx, roleId, roleName) ->
|
|
||||||
us = $scope.$eval($attrs.tgBacklogUsPoints)
|
|
||||||
renderPoints(us, roleId)
|
|
||||||
selectedRoleId = roleId
|
|
||||||
|
|
||||||
$scope.$on "uspoints:clear-selection", (ctx) ->
|
|
||||||
us = $scope.$eval($attrs.tgBacklogUsPoints)
|
|
||||||
renderPoints(us, null)
|
|
||||||
selectedRoleId = null
|
|
||||||
|
|
||||||
if roles.length > 0
|
|
||||||
$el.on "click", "a.us-points span", (event) ->
|
$el.on "click", "a.us-points span", (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
us = $scope.$eval($attrs.tgBacklogUsPoints)
|
us = $scope.$eval($attrs.tgBacklogUsPoints)
|
||||||
updatingSelectedRoleId = selectedRoleId
|
updatingSelectedRoleId = selectedRoleId
|
||||||
|
|
||||||
if selectedRoleId?
|
if selectedRoleId?
|
||||||
renderPointsSelector(us, selectedRoleId)
|
estimationProcess.renderPointsSelector(selectedRoleId)
|
||||||
else
|
else
|
||||||
renderRolesSelector(us)
|
renderRolesSelector()
|
||||||
|
|
||||||
$el.on "click", ".role", (event) ->
|
$el.on "click", ".role", (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
target = angular.element(event.currentTarget)
|
target = angular.element(event.currentTarget)
|
||||||
|
|
||||||
us = $scope.$eval($attrs.tgBacklogUsPoints)
|
us = $scope.$eval($attrs.tgBacklogUsPoints)
|
||||||
|
|
||||||
updatingSelectedRoleId = target.data("role-id")
|
updatingSelectedRoleId = target.data("role-id")
|
||||||
|
|
||||||
popRolesDom = $el.find(".pop-role")
|
popRolesDom = $el.find(".pop-role")
|
||||||
popRolesDom.find("a").removeClass("active")
|
popRolesDom.find("a").removeClass("active")
|
||||||
popRolesDom.find("a[data-role-id='#{updatingSelectedRoleId}']").addClass("active")
|
popRolesDom.find("a[data-role-id='#{updatingSelectedRoleId}']").addClass("active")
|
||||||
|
estimationProcess.renderPointsSelector(updatingSelectedRoleId)
|
||||||
renderPointsSelector(us, updatingSelectedRoleId)
|
|
||||||
|
|
||||||
$el.on "click", ".point", (event) ->
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
|
|
||||||
target = angular.element(event.currentTarget)
|
|
||||||
$el.find(".pop-points-open").hide()
|
|
||||||
$el.find(".pop-role").hide()
|
|
||||||
|
|
||||||
us = $scope.$eval($attrs.tgBacklogUsPoints)
|
|
||||||
|
|
||||||
points = _.clone(us.points, true)
|
|
||||||
points[updatingSelectedRoleId] = target.data("point-id")
|
|
||||||
|
|
||||||
$scope.$apply ->
|
|
||||||
us.points = points
|
|
||||||
us.total_points = calculateTotalPoints(us)
|
|
||||||
|
|
||||||
renderPoints(us, selectedRoleId)
|
|
||||||
|
|
||||||
$repo.save(us).then ->
|
|
||||||
# Little Hack for refresh.
|
|
||||||
$repo.refresh(us).then ->
|
|
||||||
$ctrl.loadProjectStats()
|
|
||||||
|
|
||||||
bindOnce $scope, "project", (project) ->
|
|
||||||
# If the user has not enough permissions the click events are unbinded
|
|
||||||
if project.my_permissions.indexOf("modify_us") == -1
|
|
||||||
$el.unbind("click")
|
|
||||||
$el.find("a").addClass("not-clickable")
|
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
$el.off()
|
$el.off()
|
||||||
|
|
||||||
return {link: link}
|
return {link: link}
|
||||||
|
|
||||||
module.directive("tgBacklogUsPoints", ["$tgRepo", "$tgTemplate", UsPointsDirective])
|
module.directive("tgBacklogUsPoints", ["$tgEstimationsService", "$tgRepo", "$tgTemplate", UsPointsDirective])
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Burndown graph directive
|
## Burndown graph directive
|
||||||
|
|
|
@ -58,6 +58,7 @@ BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm) ->
|
||||||
|
|
||||||
$el.sortable({
|
$el.sortable({
|
||||||
items: ".us-item-row",
|
items: ".us-item-row",
|
||||||
|
cancel: ".popover"
|
||||||
connectWith: ".sprint"
|
connectWith: ".sprint"
|
||||||
containment: ".wrapper"
|
containment: ".wrapper"
|
||||||
dropOnEmpty: true
|
dropOnEmpty: true
|
||||||
|
|
|
@ -61,6 +61,8 @@ BacklogSprintDirective = ($repo, $rootscope) ->
|
||||||
|
|
||||||
# Event Handlers
|
# Event Handlers
|
||||||
$el.on "click", ".sprint-name > .icon-arrow-up", (event) ->
|
$el.on "click", ".sprint-name > .icon-arrow-up", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
toggleSprint($el)
|
toggleSprint($el)
|
||||||
|
|
||||||
$el.find(".sprint-table").slideToggle(slideOptions)
|
$el.find(".sprint-table").slideToggle(slideOptions)
|
||||||
|
@ -135,26 +137,38 @@ module.directive("tgBacklogSprintHeader", ["$tgNavUrls", "$tgTemplate", BacklogS
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
ToggleExcludeClosedSprintsVisualization = ($rootscope, $loading) ->
|
ToggleExcludeClosedSprintsVisualization = ($rootscope, $loading) ->
|
||||||
excludeClosedSprints = false
|
excludeClosedSprints = true
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
|
# insert loading wrapper
|
||||||
|
loadingElm = $("<div>")
|
||||||
|
$el.after(loadingElm)
|
||||||
|
|
||||||
# Event Handlers
|
# Event Handlers
|
||||||
$el.on "click", "", (event) ->
|
$el.on "click", (event) ->
|
||||||
$loading.start($el.parent().siblings('.loading-spinner'))
|
event.preventDefault()
|
||||||
$rootscope.$broadcast("backlog:toggle-closed-sprints-visualization")
|
excludeClosedSprints = not excludeClosedSprints
|
||||||
|
|
||||||
|
$loading.start(loadingElm)
|
||||||
|
|
||||||
|
if excludeClosedSprints
|
||||||
|
$rootscope.$broadcast("backlog:unload-closed-sprints")
|
||||||
|
else
|
||||||
|
$rootscope.$broadcast("backlog:load-closed-sprints")
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
$el.off()
|
$el.off()
|
||||||
|
|
||||||
$scope.$on "sprints:loaded", (ctx, sprints) =>
|
$scope.$on "closed-sprints:reloaded", (ctx, sprints) =>
|
||||||
closedSprints = _.filter(sprints, (sprint) -> sprint.closed)
|
$loading.finish(loadingElm)
|
||||||
$loading.finish($el.parent().siblings('.loading-spinner'))
|
|
||||||
|
|
||||||
#TODO: i18n
|
#TODO: i18n
|
||||||
if closedSprints.length > 0
|
if sprints.length > 0
|
||||||
$el.text("Hide closed sprints")
|
text = "Hide closed sprints"
|
||||||
else
|
else
|
||||||
$el.text("Show closed sprints")
|
text = "Show closed sprints"
|
||||||
|
|
||||||
|
$el.find(".text").text(text)
|
||||||
|
|
||||||
return {link: link}
|
return {link: link}
|
||||||
|
|
||||||
|
|
|
@ -84,13 +84,15 @@ urls = {
|
||||||
"project-admin-project-profile-default-values": "/project/:project/admin/project-profile/default-values"
|
"project-admin-project-profile-default-values": "/project/:project/admin/project-profile/default-values"
|
||||||
"project-admin-project-profile-modules": "/project/:project/admin/project-profile/modules"
|
"project-admin-project-profile-modules": "/project/:project/admin/project-profile/modules"
|
||||||
"project-admin-project-profile-export": "/project/:project/admin/project-profile/export"
|
"project-admin-project-profile-export": "/project/:project/admin/project-profile/export"
|
||||||
"project-admin-project-values-us-status": "/project/:project/admin/project-values/us-status"
|
"project-admin-project-profile-reports": "/project/:project/admin/project-profile/reports"
|
||||||
"project-admin-project-values-us-points": "/project/:project/admin/project-values/us-points"
|
|
||||||
"project-admin-project-values-task-status": "/project/:project/admin/project-values/task-status"
|
"project-admin-project-values-status": "/project/:project/admin/project-values/status"
|
||||||
"project-admin-project-values-issue-status": "/project/:project/admin/project-values/issue-status"
|
"project-admin-project-values-points": "/project/:project/admin/project-values/points"
|
||||||
"project-admin-project-values-issue-types": "/project/:project/admin/project-values/issue-types"
|
"project-admin-project-values-priorities": "/project/:project/admin/project-values/priorities"
|
||||||
"project-admin-project-values-issue-priorities": "/project/:project/admin/project-values/issue-priorities"
|
"project-admin-project-values-severities": "/project/:project/admin/project-values/severities"
|
||||||
"project-admin-project-values-issue-severities": "/project/:project/admin/project-values/issue-severities"
|
"project-admin-project-values-types": "/project/:project/admin/project-values/types"
|
||||||
|
"project-admin-project-values-custom-fields": "/project/:project/admin/project-values/custom-fields"
|
||||||
|
|
||||||
"project-admin-memberships": "/project/:project/admin/memberships"
|
"project-admin-memberships": "/project/:project/admin/memberships"
|
||||||
"project-admin-roles": "/project/:project/admin/roles"
|
"project-admin-roles": "/project/:project/admin/roles"
|
||||||
"project-admin-third-parties-webhooks": "/project/:project/admin/third-parties/webhooks"
|
"project-admin-third-parties-webhooks": "/project/:project/admin/third-parties/webhooks"
|
||||||
|
|
|
@ -28,6 +28,10 @@ locationFactory = ($location, $route, $rootscope) ->
|
||||||
un()
|
un()
|
||||||
|
|
||||||
return $location
|
return $location
|
||||||
|
|
||||||
|
$location.isInCurrentRouteParams = (name, value) ->
|
||||||
|
return $route.current.params[name] == value
|
||||||
|
|
||||||
return $location
|
return $location
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,14 @@ class UrlsService extends taiga.Service
|
||||||
_.str.ltrim(url, "/")
|
_.str.ltrim(url, "/")
|
||||||
])
|
])
|
||||||
|
|
||||||
|
resolveAbsolute: ->
|
||||||
|
url = @.resolve.apply(@, arguments)
|
||||||
|
if (/^https?:\/\//i).test(url)
|
||||||
|
return url
|
||||||
|
if (/^\//).test(url)
|
||||||
|
return "#{window.location.protocol}//#{window.location.host}#{url}"
|
||||||
|
return "#{window.location.protocol}//#{window.location.host}/#{url}"
|
||||||
|
|
||||||
|
|
||||||
module = angular.module("taigaBase")
|
module = angular.module("taigaBase")
|
||||||
module.service('$tgUrls', UrlsService)
|
module.service('$tgUrls', UrlsService)
|
||||||
|
|
|
@ -60,6 +60,42 @@ CheckPermissionDirective = ->
|
||||||
|
|
||||||
module.directive("tgCheckPermission", CheckPermissionDirective)
|
module.directive("tgCheckPermission", CheckPermissionDirective)
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## Add class based on permissions
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
ClassPermissionDirective = ->
|
||||||
|
name = "tgClassPermission"
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs) ->
|
||||||
|
checkPermissions = (project, className, permission) ->
|
||||||
|
negation = permission[0] == "!"
|
||||||
|
|
||||||
|
permission = permission.slice(1) if negation
|
||||||
|
|
||||||
|
if negation && project.my_permissions.indexOf(permission) == -1
|
||||||
|
$el.addClass(className)
|
||||||
|
else if !negation && project.my_permissions.indexOf(permission) != -1
|
||||||
|
$el.addClass(className)
|
||||||
|
else
|
||||||
|
$el.removeClass(className)
|
||||||
|
|
||||||
|
tgClassPermissionWatchAction = (project) ->
|
||||||
|
if project
|
||||||
|
unbindWatcher()
|
||||||
|
|
||||||
|
classes = $scope.$eval($attrs[name])
|
||||||
|
|
||||||
|
for className, permission of classes
|
||||||
|
checkPermissions(project, className, permission)
|
||||||
|
|
||||||
|
|
||||||
|
unbindWatcher = $scope.$watch "project", tgClassPermissionWatchAction
|
||||||
|
|
||||||
|
return {link:link}
|
||||||
|
|
||||||
|
module.directive("tgClassPermission", ClassPermissionDirective)
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Animation frame service, apply css changes in the next render frame
|
## Animation frame service, apply css changes in the next render frame
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
module = angular.module("taigaCommon")
|
||||||
|
|
||||||
|
BindScope = (config) ->
|
||||||
|
if !config.debugInfo
|
||||||
|
jQuery.fn.scope = () -> this.data('scope')
|
||||||
|
|
||||||
|
link = ($scope, $el) ->
|
||||||
|
if !config.debugInfo
|
||||||
|
$el
|
||||||
|
.data('scope', $scope)
|
||||||
|
.addClass('tg-scope')
|
||||||
|
|
||||||
|
return {link: link}
|
||||||
|
|
||||||
|
module.directive("tgBindScope", ["$tgConfig", BindScope])
|
|
@ -536,7 +536,9 @@ EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading,
|
||||||
$el.find('.view-description').hide()
|
$el.find('.view-description').hide()
|
||||||
$el.find('textarea').focus()
|
$el.find('textarea').focus()
|
||||||
|
|
||||||
$el.on "click", ".save", ->
|
$el.on "click", ".save", (e) ->
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
description = $scope.item.description
|
description = $scope.item.description
|
||||||
save(description)
|
save(description)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,198 @@
|
||||||
|
###
|
||||||
|
# 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/custom-field-values.coffee
|
||||||
|
###
|
||||||
|
|
||||||
|
taiga = @.taiga
|
||||||
|
bindMethods = @.taiga.bindMethods
|
||||||
|
bindOnce = @.taiga.bindOnce
|
||||||
|
debounce = @.taiga.debounce
|
||||||
|
generateHash = taiga.generateHash
|
||||||
|
|
||||||
|
module = angular.module("taigaCommon")
|
||||||
|
|
||||||
|
|
||||||
|
class CustomAttributesValuesController extends taiga.Controller
|
||||||
|
@.$inject = ["$scope", "$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", "$q"]
|
||||||
|
|
||||||
|
constructor: (@scope, @rootscope, @repo, @rs, @confirm, @q) ->
|
||||||
|
bindMethods(@)
|
||||||
|
@.type = null
|
||||||
|
@.objectId = null
|
||||||
|
@.projectId = null
|
||||||
|
@.customAttributes = []
|
||||||
|
@.customAttributesValues = null
|
||||||
|
|
||||||
|
initialize: (type, objectId) ->
|
||||||
|
@.project = @scope.project
|
||||||
|
@.type = type
|
||||||
|
@.objectId = objectId
|
||||||
|
@.projectId = @scope.projectId
|
||||||
|
|
||||||
|
loadCustomAttributesValues: ->
|
||||||
|
return @.customAttributesValues if not @.objectId
|
||||||
|
return @rs.customAttributesValues[@.type].get(@.objectId).then (customAttributesValues) =>
|
||||||
|
@.customAttributes = @.project["#{@.type}_custom_attributes"]
|
||||||
|
@.customAttributesValues = customAttributesValues
|
||||||
|
return customAttributesValues
|
||||||
|
|
||||||
|
getAttributeValue: (attribute) ->
|
||||||
|
attributeValue = _.clone(attribute, false)
|
||||||
|
attributeValue.value = @.customAttributesValues.attributes_values[attribute.id]
|
||||||
|
return attributeValue
|
||||||
|
|
||||||
|
updateAttributeValue: (attributeValue) ->
|
||||||
|
onSuccess = =>
|
||||||
|
@rootscope.$broadcast("custom-attributes-values:edit")
|
||||||
|
|
||||||
|
onError = (response) =>
|
||||||
|
@confirm.notify("error")
|
||||||
|
return @q.reject()
|
||||||
|
|
||||||
|
# We need to update the full array so angular understand the model is modified
|
||||||
|
attributesValues = _.clone(@.customAttributesValues.attributes_values, true)
|
||||||
|
attributesValues[attributeValue.id] = attributeValue.value
|
||||||
|
@.customAttributesValues.attributes_values = attributesValues
|
||||||
|
@.customAttributesValues.id = @.objectId
|
||||||
|
return @repo.save(@.customAttributesValues).then(onSuccess, onError)
|
||||||
|
|
||||||
|
|
||||||
|
CustomAttributesValuesDirective = ($templates, $storage) ->
|
||||||
|
template = $templates.get("custom-attributes/custom-attributes-values.html", true)
|
||||||
|
collapsedHash = (type) ->
|
||||||
|
return generateHash(["custom-attributes-collapsed", type])
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs, $ctrls) ->
|
||||||
|
$ctrl = $ctrls[0]
|
||||||
|
$model = $ctrls[1]
|
||||||
|
|
||||||
|
bindOnce $scope, $attrs.ngModel, (value) ->
|
||||||
|
$ctrl.initialize($attrs.type, value.id)
|
||||||
|
$ctrl.loadCustomAttributesValues()
|
||||||
|
|
||||||
|
$el.on "click", ".custom-fields-header a", ->
|
||||||
|
hash = collapsedHash($attrs.type)
|
||||||
|
collapsed = not($storage.get(hash) or false)
|
||||||
|
$storage.set(hash, collapsed)
|
||||||
|
if collapsed
|
||||||
|
$el.find(".custom-fields-header a").removeClass("open")
|
||||||
|
$el.find(".custom-fields-body").removeClass("open")
|
||||||
|
else
|
||||||
|
$el.find(".custom-fields-header a").addClass("open")
|
||||||
|
$el.find(".custom-fields-body").addClass("open")
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
templateFn = ($el, $attrs) ->
|
||||||
|
collapsed = $storage.get(collapsedHash($attrs.type)) or false
|
||||||
|
|
||||||
|
return template({
|
||||||
|
requiredEditionPerm: $attrs.requiredEditionPerm
|
||||||
|
collapsed: collapsed
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
require: ["tgCustomAttributesValues", "ngModel"]
|
||||||
|
controller: CustomAttributesValuesController
|
||||||
|
controllerAs: "ctrl"
|
||||||
|
restrict: "AE"
|
||||||
|
scope: true
|
||||||
|
link: link
|
||||||
|
template: templateFn
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgCustomAttributesValues", ["$tgTemplate", "$tgStorage", CustomAttributesValuesDirective])
|
||||||
|
|
||||||
|
|
||||||
|
CustomAttributeValueDirective = ($template, $selectedText) ->
|
||||||
|
template = $template.get("custom-attributes/custom-attribute-value.html", true)
|
||||||
|
templateEdit = $template.get("custom-attributes/custom-attribute-value-edit.html", true)
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs, $ctrl) ->
|
||||||
|
render = (attributeValue, edit=false) ->
|
||||||
|
value = attributeValue.value
|
||||||
|
editable = isEditable()
|
||||||
|
ctx = {
|
||||||
|
id: attributeValue.id
|
||||||
|
name: attributeValue.name
|
||||||
|
description: attributeValue.description
|
||||||
|
value: value
|
||||||
|
isEditable: editable
|
||||||
|
}
|
||||||
|
|
||||||
|
if editable and (edit or not value)
|
||||||
|
html = templateEdit(ctx)
|
||||||
|
else
|
||||||
|
html = template(ctx)
|
||||||
|
|
||||||
|
$el.html(html)
|
||||||
|
|
||||||
|
isEditable = ->
|
||||||
|
permissions = $scope.project.my_permissions
|
||||||
|
requiredEditionPerm = $attrs.requiredEditionPerm
|
||||||
|
return permissions.indexOf(requiredEditionPerm) > -1
|
||||||
|
|
||||||
|
saveAttributeValue = ->
|
||||||
|
attributeValue.value = $el.find("input").val()
|
||||||
|
|
||||||
|
$scope.$apply ->
|
||||||
|
$ctrl.updateAttributeValue(attributeValue).then ->
|
||||||
|
render(attributeValue, false)
|
||||||
|
|
||||||
|
$el.on "keyup", "input[name=description]", (event) ->
|
||||||
|
if event.keyCode == 13
|
||||||
|
submit(event)
|
||||||
|
else if event.keyCode == 27
|
||||||
|
render(attributeValue, false)
|
||||||
|
|
||||||
|
## Actions (on view mode)
|
||||||
|
$el.on "click", ".custom-field-value.read-mode", ->
|
||||||
|
return if not isEditable()
|
||||||
|
return if $selectedText.get().length
|
||||||
|
render(attributeValue, true)
|
||||||
|
$el.find("input[name='description']").focus().select()
|
||||||
|
|
||||||
|
$el.on "click", "a.icon-edit", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
render(attributeValue, true)
|
||||||
|
$el.find("input[name='description']").focus().select()
|
||||||
|
|
||||||
|
## Actions (on edit mode)
|
||||||
|
submit = debounce 2000, (event) =>
|
||||||
|
event.preventDefault()
|
||||||
|
saveAttributeValue()
|
||||||
|
|
||||||
|
$el.on "submit", "form", submit
|
||||||
|
$el.on "click", "a.icon-floppy", submit
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
# Bootstrap
|
||||||
|
attributeValue = $scope.$eval($attrs.tgCustomAttributeValue)
|
||||||
|
render(attributeValue)
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: link
|
||||||
|
require: "^tgCustomAttributesValues"
|
||||||
|
restrict: "AE"
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgCustomAttributeValue", ["$tgTemplate", "$selectedText", CustomAttributeValueDirective])
|
|
@ -20,6 +20,7 @@
|
||||||
###
|
###
|
||||||
|
|
||||||
taiga = @.taiga
|
taiga = @.taiga
|
||||||
|
groupBy = @.taiga.groupBy
|
||||||
|
|
||||||
module = angular.module("taigaCommon")
|
module = angular.module("taigaCommon")
|
||||||
|
|
||||||
|
@ -27,7 +28,53 @@ module = angular.module("taigaCommon")
|
||||||
## User story estimation directive (for Lightboxes)
|
## User story estimation directive (for Lightboxes)
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
LbUsEstimationDirective = ($rootScope, $repo, $confirm, $template) ->
|
LbUsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $confirm, $template) ->
|
||||||
|
# Display the points of a US and you can edit it.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# tg-lb-us-estimation-progress-bar(ng-model="us")
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# - Us object (ng-model)
|
||||||
|
# - scope.project object
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
$scope.$watch $attrs.ngModel, (us) ->
|
||||||
|
if us
|
||||||
|
estimationProcess = $tgEstimationsService.create($el, us, $scope.project)
|
||||||
|
estimationProcess.onSelectedPointForRole = (roleId, pointId) ->
|
||||||
|
$scope.$apply ->
|
||||||
|
$model.$setViewValue(us)
|
||||||
|
|
||||||
|
estimationProcess.render = () ->
|
||||||
|
ctx = {
|
||||||
|
totalPoints: @calculateTotalPoints()
|
||||||
|
roles: @calculateRoles()
|
||||||
|
editable: @isEditable
|
||||||
|
}
|
||||||
|
mainTemplate = "common/estimation/us-estimation-points-per-role.html"
|
||||||
|
template = $template.get(mainTemplate, true)
|
||||||
|
html = template(ctx)
|
||||||
|
@$el.html(html)
|
||||||
|
|
||||||
|
estimationProcess.render()
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: link
|
||||||
|
restrict: "EA"
|
||||||
|
require: "ngModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgLbUsEstimation", ["$tgEstimationsService", "$rootScope", "$tgRepo", "$tgConfirm", "$tgTemplate", LbUsEstimationDirective])
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## User story estimation directive
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
UsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $confirm, $qqueue, $template) ->
|
||||||
# Display the points of a US and you can edit it.
|
# Display the points of a US and you can edit it.
|
||||||
#
|
#
|
||||||
# Example:
|
# Example:
|
||||||
|
@ -37,90 +84,26 @@ LbUsEstimationDirective = ($rootScope, $repo, $confirm, $template) ->
|
||||||
# - Us object (ng-model)
|
# - Us object (ng-model)
|
||||||
# - scope.project object
|
# - scope.project object
|
||||||
|
|
||||||
mainTemplate = $template.get("common/estimation/lb-us-estimation-points-per-role.html", true)
|
|
||||||
pointsTemplate = $template.get("common/estimation/lb-us-estimation-points.html", true)
|
|
||||||
|
|
||||||
link = ($scope, $el, $attrs, $model) ->
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
render = (points) ->
|
$scope.$watch $attrs.ngModel, (us) ->
|
||||||
totalPoints = calculateTotalPoints(points) or 0
|
if us
|
||||||
computableRoles = _.filter($scope.project.roles, "computable")
|
estimationProcess = $tgEstimationsService.create($el, us, $scope.project)
|
||||||
|
estimationProcess.onSelectedPointForRole = (roleId, pointId) ->
|
||||||
roles = _.map computableRoles, (role) ->
|
@save(roleId, pointId).then ->
|
||||||
pointId = points[role.id]
|
$rootScope.$broadcast("history:reload")
|
||||||
pointObj = $scope.pointsById[pointId]
|
|
||||||
|
|
||||||
role = _.clone(role, true)
|
|
||||||
role.points = if pointObj? and pointObj.name? then pointObj.name else "?"
|
|
||||||
return role
|
|
||||||
|
|
||||||
|
estimationProcess.render = () ->
|
||||||
ctx = {
|
ctx = {
|
||||||
totalPoints: totalPoints
|
totalPoints: @calculateTotalPoints()
|
||||||
roles: roles
|
roles: @calculateRoles()
|
||||||
|
editable: @isEditable
|
||||||
}
|
}
|
||||||
html = mainTemplate(ctx)
|
mainTemplate = "common/estimation/us-estimation-points-per-role.html"
|
||||||
$el.html(html)
|
template = $template.get(mainTemplate, true)
|
||||||
|
html = template(ctx)
|
||||||
|
@$el.html(html)
|
||||||
|
|
||||||
renderPoints = (target, usPoints, roleId) ->
|
estimationProcess.render()
|
||||||
points = _.map $scope.project.points, (point) ->
|
|
||||||
point = _.clone(point, true)
|
|
||||||
point.selected = if usPoints[roleId] == point.id then false else true
|
|
||||||
return point
|
|
||||||
|
|
||||||
html = pointsTemplate({"points": points, roleId: roleId})
|
|
||||||
|
|
||||||
# Remove any prevous state
|
|
||||||
$el.find(".popover").popover().close()
|
|
||||||
$el.find(".pop-points-open").remove()
|
|
||||||
|
|
||||||
# If not showing role selection let's move to the left
|
|
||||||
if not $el.find(".pop-role:visible").css("left")?
|
|
||||||
$el.find(".pop-points-open").css("left", "110px")
|
|
||||||
|
|
||||||
$el.find(".pop-points-open").remove()
|
|
||||||
|
|
||||||
# Render into DOM and show the new created element
|
|
||||||
$el.find(target).append(html)
|
|
||||||
|
|
||||||
$el.find(".pop-points-open").popover().open(-> $(this).removeClass("active"))
|
|
||||||
$el.find(".pop-points-open").show()
|
|
||||||
|
|
||||||
calculateTotalPoints = (points) ->
|
|
||||||
values = _.map(points, (v, k) -> $scope.pointsById[v]?.value or 0)
|
|
||||||
if values.length == 0
|
|
||||||
return "0"
|
|
||||||
return _.reduce(values, (acc, num) -> acc + num)
|
|
||||||
|
|
||||||
$el.on "click", ".total.clickable", (event) ->
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
|
|
||||||
target = angular.element(event.currentTarget)
|
|
||||||
roleId = target.data("role-id")
|
|
||||||
|
|
||||||
points = $model.$modelValue
|
|
||||||
renderPoints(target, points, roleId)
|
|
||||||
|
|
||||||
target.siblings().removeClass('active')
|
|
||||||
target.addClass('active')
|
|
||||||
|
|
||||||
$el.on "click", ".point", (event) ->
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
|
|
||||||
target = angular.element(event.currentTarget)
|
|
||||||
roleId = target.data("role-id")
|
|
||||||
pointId = target.data("point-id")
|
|
||||||
|
|
||||||
$el.find(".popover").popover().close()
|
|
||||||
|
|
||||||
points = _.clone($model.$modelValue, true)
|
|
||||||
points[roleId] = pointId
|
|
||||||
|
|
||||||
$scope.$apply ->
|
|
||||||
$model.$setViewValue(points)
|
|
||||||
|
|
||||||
$scope.$watch $attrs.ngModel, (points) ->
|
|
||||||
render(points) if points
|
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
$el.off()
|
$el.off()
|
||||||
|
@ -131,81 +114,46 @@ LbUsEstimationDirective = ($rootScope, $repo, $confirm, $template) ->
|
||||||
require: "ngModel"
|
require: "ngModel"
|
||||||
}
|
}
|
||||||
|
|
||||||
module.directive("tgLbUsEstimation", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgTemplate", LbUsEstimationDirective])
|
module.directive("tgUsEstimation", ["$tgEstimationsService", "$rootScope", "$tgRepo", "$tgConfirm", "$tgQqueue", "$tgTemplate",
|
||||||
|
UsEstimationDirective])
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## User story estimation directive
|
## Estimations service
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
UsEstimationDirective = ($rootScope, $repo, $confirm, $qqueue, $template) ->
|
EstimationsService = ($template, $qqueue, $repo, $confirm, $q) ->
|
||||||
# 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
|
|
||||||
|
|
||||||
mainTemplate = $template.get("common/estimation/us-estimation-points-per-role.html", true)
|
|
||||||
pointsTemplate = $template.get("common/estimation/us-estimation-points.html", true)
|
pointsTemplate = $template.get("common/estimation/us-estimation-points.html", true)
|
||||||
|
|
||||||
link = ($scope, $el, $attrs, $model) ->
|
class EstimationProcess
|
||||||
isEditable = ->
|
constructor: (@$el, @us, @project) ->
|
||||||
return $scope.project.my_permissions.indexOf("modify_us") != -1
|
@isEditable = @project.my_permissions.indexOf("modify_us") != -1
|
||||||
|
@roles = @project.roles
|
||||||
|
@points = @project.points
|
||||||
|
@pointsById = groupBy(@points, (x) -> x.id)
|
||||||
|
@onSelectedPointForRole = (roleId, pointId) ->
|
||||||
|
@render = () ->
|
||||||
|
|
||||||
render = (us) ->
|
save: (roleId, pointId) ->
|
||||||
totalPoints = if us.total_points? then us.total_points else "?"
|
deferred = $q.defer()
|
||||||
computableRoles = _.filter($scope.project.roles, "computable")
|
$qqueue.add () =>
|
||||||
|
onSuccess = =>
|
||||||
|
deferred.resolve()
|
||||||
|
$confirm.notify("success")
|
||||||
|
|
||||||
roles = _.map computableRoles, (role) ->
|
onError = =>
|
||||||
pointId = us.points[role.id]
|
$confirm.notify("error")
|
||||||
pointObj = $scope.pointsById[pointId]
|
@us.revert()
|
||||||
|
@render()
|
||||||
|
deferred.reject()
|
||||||
|
|
||||||
role = _.clone(role, true)
|
$repo.save(@us).then(onSuccess, onError)
|
||||||
role.points = if pointObj? and pointObj.name? then pointObj.name else "?"
|
|
||||||
return role
|
|
||||||
|
|
||||||
ctx = {
|
return deferred.promise
|
||||||
totalPoints: totalPoints
|
|
||||||
roles: roles
|
|
||||||
editable: isEditable()
|
|
||||||
}
|
|
||||||
html = mainTemplate(ctx)
|
|
||||||
$el.html(html)
|
|
||||||
|
|
||||||
renderPoints = (target, us, roleId) ->
|
calculateTotalPoints: () ->
|
||||||
points = _.map $scope.project.points, (point) ->
|
values = _.map(@us.points, (v, k) => @pointsById[v]?.value)
|
||||||
point = _.clone(point, true)
|
|
||||||
point.selected = if us.points[roleId] == point.id then false else true
|
|
||||||
return point
|
|
||||||
|
|
||||||
html = pointsTemplate({"points": points, roleId: roleId})
|
|
||||||
|
|
||||||
# Remove any prevous state
|
|
||||||
$el.find(".popover").popover().close()
|
|
||||||
$el.find(".pop-points-open").remove()
|
|
||||||
|
|
||||||
# If not showing role selection let's move to the left
|
|
||||||
if not $el.find(".pop-role:visible").css("left")?
|
|
||||||
$el.find(".pop-points-open").css("left", "110px")
|
|
||||||
|
|
||||||
$el.find(".pop-points-open").remove()
|
|
||||||
|
|
||||||
# Render into DOM and show the new created element
|
|
||||||
$el.find(target).append(html)
|
|
||||||
|
|
||||||
$el.find(".pop-points-open").popover().open ->
|
|
||||||
$(this)
|
|
||||||
.removeClass("active")
|
|
||||||
.closest("li").removeClass("active")
|
|
||||||
|
|
||||||
|
|
||||||
$el.find(".pop-points-open").show()
|
|
||||||
|
|
||||||
calculateTotalPoints = (us) ->
|
|
||||||
values = _.map(us.points, (v, k) -> $scope.pointsById[v]?.value)
|
|
||||||
if values.length == 0
|
if values.length == 0
|
||||||
return "0"
|
return "0"
|
||||||
|
|
||||||
|
@ -215,64 +163,82 @@ UsEstimationDirective = ($rootScope, $repo, $confirm, $qqueue, $template) ->
|
||||||
|
|
||||||
return _.reduce(notNullValues, (acc, num) -> acc + num)
|
return _.reduce(notNullValues, (acc, num) -> acc + num)
|
||||||
|
|
||||||
save = $qqueue.bindAdd (roleId, pointId) =>
|
calculateRoles: () ->
|
||||||
$el.find(".popover").popover().close()
|
computableRoles = _.filter(@project.roles, "computable")
|
||||||
|
roles = _.map computableRoles, (role) =>
|
||||||
|
pointId = @us.points[role.id]
|
||||||
|
pointObj = @pointsById[pointId]
|
||||||
|
role = _.clone(role, true)
|
||||||
|
role.points = if pointObj? and pointObj.name? then pointObj.name else "?"
|
||||||
|
return role
|
||||||
|
|
||||||
# Hell starts here
|
return roles
|
||||||
us = angular.copy($model.$modelValue)
|
|
||||||
points = _.clone($model.$modelValue.points, true)
|
|
||||||
points[roleId] = pointId
|
|
||||||
us.setAttr('points', points)
|
|
||||||
us.points = points
|
|
||||||
us.total_points = calculateTotalPoints(us)
|
|
||||||
$model.$setViewValue(us)
|
|
||||||
# Hell ends here
|
|
||||||
|
|
||||||
onSuccess = ->
|
bindClickEvents: =>
|
||||||
$confirm.notify("success")
|
@$el.on "click", ".total.clickable", (event) =>
|
||||||
$rootScope.$broadcast("history:reload")
|
|
||||||
onError = ->
|
|
||||||
$confirm.notify("error")
|
|
||||||
us.revert()
|
|
||||||
$model.$setViewValue(us)
|
|
||||||
|
|
||||||
$repo.save($model.$modelValue).then(onSuccess, onError)
|
|
||||||
|
|
||||||
$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")
|
||||||
|
@renderPointsSelector(roleId, target)
|
||||||
us = $model.$modelValue
|
|
||||||
renderPoints(target, us, roleId)
|
|
||||||
|
|
||||||
target.siblings().removeClass('active')
|
target.siblings().removeClass('active')
|
||||||
target.addClass('active')
|
target.addClass('active')
|
||||||
|
|
||||||
$el.on "click", ".point", (event) ->
|
@$el.on "click", ".point", (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")
|
||||||
pointId = target.data("point-id")
|
pointId = target.data("point-id")
|
||||||
|
@$el.find(".popover").popover().close()
|
||||||
|
points = _.clone(@us.points, true)
|
||||||
|
points[roleId] = pointId
|
||||||
|
@us.points = points
|
||||||
|
@render()
|
||||||
|
@onSelectedPointForRole(roleId, pointId)
|
||||||
|
|
||||||
save(roleId, pointId)
|
renderPointsSelector: (roleId, target) ->
|
||||||
|
points = _.map @points, (point) =>
|
||||||
|
point = _.clone(point, true)
|
||||||
|
point.selected = if @us.points[roleId] == point.id then false else true
|
||||||
|
return point
|
||||||
|
|
||||||
$scope.$watch $attrs.ngModel, (us) ->
|
maxPointLength = 5
|
||||||
render(us) if us
|
horizontalList = _.some points, (point) => point.name.length > maxPointLength
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
html = pointsTemplate({"points": points, roleId: roleId, horizontal: horizontalList})
|
||||||
$el.off()
|
# Remove any previous state
|
||||||
|
@$el.find(".popover").popover().close()
|
||||||
|
@$el.find(".pop-points-open").remove()
|
||||||
|
# Render into DOM and show the new created element
|
||||||
|
if target?
|
||||||
|
@$el.find(target).append(html)
|
||||||
|
else
|
||||||
|
@$el.append(html)
|
||||||
|
|
||||||
|
@$el.find(".pop-points-open").popover().open ->
|
||||||
|
$(this)
|
||||||
|
.removeClass("active")
|
||||||
|
.closest("li").removeClass("active")
|
||||||
|
|
||||||
|
@$el.find(".pop-points-open").show()
|
||||||
|
|
||||||
|
create = ($el, us, project) ->
|
||||||
|
estimationProcess = $el.data("estimationProcess")
|
||||||
|
|
||||||
|
if !estimationProcess
|
||||||
|
estimationProcess = new EstimationProcess($el, us, project)
|
||||||
|
$el.data("estimationProcess", estimationProcess)
|
||||||
|
|
||||||
|
if estimationProcess.isEditable
|
||||||
|
estimationProcess.bindClickEvents()
|
||||||
|
else
|
||||||
|
$el.unbind("click")
|
||||||
|
|
||||||
|
return estimationProcess
|
||||||
|
|
||||||
return {
|
return {
|
||||||
link: link
|
create: create
|
||||||
restrict: "EA"
|
|
||||||
require: "ngModel"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.directive("tgUsEstimation", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgQqueue", "$tgTemplate", UsEstimationDirective])
|
module.factory("$tgEstimationsService", ["$tgTemplate", "$tgQqueue", "$tgRepo", "$tgConfirm", "$q", EstimationsService])
|
||||||
|
|
|
@ -51,6 +51,13 @@ class HistoryController extends taiga.Controller
|
||||||
delete historyResult.values_diff.description_html
|
delete historyResult.values_diff.description_html
|
||||||
delete historyResult.values_diff.description_diff
|
delete historyResult.values_diff.description_diff
|
||||||
|
|
||||||
|
# If block note was modified take only the blocked_note_html field
|
||||||
|
if historyResult.values_diff.blocked_note_diff?
|
||||||
|
historyResult.values_diff.blocked_note = historyResult.values_diff.blocked_note_diff
|
||||||
|
|
||||||
|
delete historyResult.values_diff.blocked_note_html
|
||||||
|
delete historyResult.values_diff.blocked_note_diff
|
||||||
|
|
||||||
@scope.history = history
|
@scope.history = history
|
||||||
@scope.comments = _.filter(history, (item) -> item.comment != "")
|
@scope.comments = _.filter(history, (item) -> item.comment != "")
|
||||||
|
|
||||||
|
@ -66,6 +73,7 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) ->
|
||||||
templateChangePoints = $template.get("common/history/history-change-points.html", true)
|
templateChangePoints = $template.get("common/history/history-change-points.html", true)
|
||||||
templateChangeGeneric = $template.get("common/history/history-change-generic.html", true)
|
templateChangeGeneric = $template.get("common/history/history-change-generic.html", true)
|
||||||
templateChangeAttachment = $template.get("common/history/history-change-attachment.html", true)
|
templateChangeAttachment = $template.get("common/history/history-change-attachment.html", true)
|
||||||
|
templateChangeList = $template.get("common/history/history-change-list.html", true)
|
||||||
templateDeletedComment = $template.get("common/history/history-deleted-comment.html", true)
|
templateDeletedComment = $template.get("common/history/history-deleted-comment.html", true)
|
||||||
templateActivity = $template.get("common/history/history-activity.html", true)
|
templateActivity = $template.get("common/history/history-activity.html", true)
|
||||||
templateBaseEntries = $template.get("common/history/history-base-entries.html", true)
|
templateBaseEntries = $template.get("common/history/history-base-entries.html", true)
|
||||||
|
@ -103,6 +111,9 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) ->
|
||||||
|
|
||||||
# Attachment
|
# Attachment
|
||||||
is_deprecated: "is deprecated"
|
is_deprecated: "is deprecated"
|
||||||
|
|
||||||
|
blocked_note: "blocked note"
|
||||||
|
is_blocked: "is blocked"
|
||||||
} # TODO i18n
|
} # TODO i18n
|
||||||
return humanizedFieldNames[field] or field
|
return humanizedFieldNames[field] or field
|
||||||
|
|
||||||
|
@ -121,18 +132,18 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) ->
|
||||||
formatChange = (change) ->
|
formatChange = (change) ->
|
||||||
if _.isArray(change)
|
if _.isArray(change)
|
||||||
if change.length == 0
|
if change.length == 0
|
||||||
return "nil"
|
return "empty"
|
||||||
return change.join(", ")
|
return change.join(", ")
|
||||||
|
|
||||||
if change == ""
|
if change == ""
|
||||||
return "nil"
|
return "empty"
|
||||||
|
|
||||||
|
if not change? or change == false
|
||||||
|
return "no"
|
||||||
|
|
||||||
if change == true
|
if change == true
|
||||||
return "yes"
|
return "yes"
|
||||||
|
|
||||||
if change == false
|
|
||||||
return "no"
|
|
||||||
|
|
||||||
return change
|
return change
|
||||||
|
|
||||||
# Render into string (operations without mutability)
|
# Render into string (operations without mutability)
|
||||||
|
@ -162,14 +173,50 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) ->
|
||||||
|
|
||||||
return _.flatten(attachments).join("\n")
|
return _.flatten(attachments).join("\n")
|
||||||
|
|
||||||
|
renderCustomAttributesEntry = (value) ->
|
||||||
|
customAttributes = _.map value, (changes, type) ->
|
||||||
|
if type == "new"
|
||||||
|
return _.map changes, (change) ->
|
||||||
|
return templateChangeGeneric({
|
||||||
|
name: change.name,
|
||||||
|
from: formatChange(""),
|
||||||
|
to: formatChange(change.value)
|
||||||
|
})
|
||||||
|
else if type == "deleted"
|
||||||
|
return _.map changes, (change) ->
|
||||||
|
# TODO: i18n
|
||||||
|
return templateChangeDiff({
|
||||||
|
name: "deleted custom attribute",
|
||||||
|
diff: change.name
|
||||||
|
})
|
||||||
|
else
|
||||||
|
return _.map changes, (change) ->
|
||||||
|
customAttrsChanges = _.map change.changes, (values) ->
|
||||||
|
return templateChangeGeneric({
|
||||||
|
name: change.name
|
||||||
|
from: formatChange(values[0])
|
||||||
|
to: formatChange(values[1])
|
||||||
|
})
|
||||||
|
return _.flatten(customAttrsChanges).join("\n")
|
||||||
|
|
||||||
|
return _.flatten(customAttributes).join("\n")
|
||||||
|
|
||||||
renderChangeEntry = (field, value) ->
|
renderChangeEntry = (field, value) ->
|
||||||
if field == "description"
|
if field == "description"
|
||||||
# TODO: i18n
|
return templateChangeDiff({name: getHumanizedFieldName("description"), diff: value[1]})
|
||||||
return templateChangeDiff({name: "description", diff: value[1]})
|
else if field == "blocked_note"
|
||||||
|
return templateChangeDiff({name: getHumanizedFieldName("blocked_note"), diff: value[1]})
|
||||||
else if field == "points"
|
else if field == "points"
|
||||||
return templateChangePoints({points: value})
|
return templateChangePoints({points: value})
|
||||||
else if field == "attachments"
|
else if field == "attachments"
|
||||||
return renderAttachmentEntry(value)
|
return renderAttachmentEntry(value)
|
||||||
|
else if field == "custom_attributes"
|
||||||
|
return renderCustomAttributesEntry(value)
|
||||||
|
else if field in ["tags", "watchers"]
|
||||||
|
name = getHumanizedFieldName(field)
|
||||||
|
removed = _.difference(value[0], value[1])
|
||||||
|
added = _.difference(value[1], value[0])
|
||||||
|
return templateChangeList({name:name, removed:removed, added: added})
|
||||||
else if field == "assigned_to"
|
else if field == "assigned_to"
|
||||||
name = getHumanizedFieldName(field)
|
name = getHumanizedFieldName(field)
|
||||||
from = formatChange(value[0] or "Unassigned")
|
from = formatChange(value[0] or "Unassigned")
|
||||||
|
@ -211,7 +258,7 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) ->
|
||||||
deleteCommentDate: moment(comment.delete_comment_date).format("DD MMM YYYY HH:mm") if comment.delete_comment_date
|
deleteCommentDate: moment(comment.delete_comment_date).format("DD MMM YYYY HH:mm") if comment.delete_comment_date
|
||||||
deleteCommentUser: comment.delete_comment_user.name if comment.delete_comment_user?.name
|
deleteCommentUser: comment.delete_comment_user.name if comment.delete_comment_user?.name
|
||||||
activityId: comment.id
|
activityId: comment.id
|
||||||
canDeleteComment: comment.user.pk == $scope.user.id or $scope.project.my_permissions.indexOf("modify_project") > -1
|
canDeleteComment: comment.user.pk == $scope.user?.id or $scope.project.my_permissions.indexOf("modify_project") > -1
|
||||||
})
|
})
|
||||||
|
|
||||||
renderChange = (change) ->
|
renderChange = (change) ->
|
||||||
|
|
|
@ -45,6 +45,8 @@ class LightboxService extends taiga.Service
|
||||||
|
|
||||||
@animationFrame.add =>
|
@animationFrame.add =>
|
||||||
$el.addClass("open")
|
$el.addClass("open")
|
||||||
|
|
||||||
|
@animationFrame.add =>
|
||||||
lightboxContent.show()
|
lightboxContent.show()
|
||||||
defered.resolve()
|
defered.resolve()
|
||||||
|
|
||||||
|
@ -334,7 +336,6 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService,
|
||||||
submitButton = $el.find(".submit-button")
|
submitButton = $el.find(".submit-button")
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
$el.on "click", ".close", (event) ->
|
$el.on "click", ".close", (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
@ -403,7 +404,6 @@ CreateBulkUserstoriesDirective = ($repo, $rs, $rootscope, lightboxService, $load
|
||||||
submitButton = $el.find(".submit-button")
|
submitButton = $el.find(".submit-button")
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
$el.off()
|
$el.off()
|
||||||
|
@ -604,41 +604,3 @@ WatchersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationS
|
||||||
}
|
}
|
||||||
|
|
||||||
module.directive("tgLbWatchers", ["$tgRepo", "lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", WatchersLightboxDirective])
|
module.directive("tgLbWatchers", ["$tgRepo", "lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", WatchersLightboxDirective])
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
## Notion Lightbox Directive
|
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
# Lightbox
|
|
||||||
NotionLightboxDirective = (lightboxService) ->
|
|
||||||
link = ($scope, $el, $attrs, $model) ->
|
|
||||||
$scope.$on "notion:open", (event, lightboxId) ->
|
|
||||||
if $el.attr("id") == lightboxId
|
|
||||||
lightboxService.open($el)
|
|
||||||
|
|
||||||
$el.on "click", ".button-green", (event) ->
|
|
||||||
lightboxService.close($el)
|
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
|
||||||
$el.off()
|
|
||||||
|
|
||||||
return {link:link}
|
|
||||||
|
|
||||||
module.directive("tgLbNotion", ["lightboxService", NotionLightboxDirective])
|
|
||||||
|
|
||||||
|
|
||||||
# Button
|
|
||||||
NotionButtonDirective = ($log, $rootScope) ->
|
|
||||||
link = ($scope, $el, $attrs, $model) ->
|
|
||||||
if not $attrs.tgLbNotionButton?
|
|
||||||
return $log.error "NotionButtonDirective: the directive need the id of the notion lightbox"
|
|
||||||
|
|
||||||
$el.on "click", ->
|
|
||||||
$rootScope.$broadcast("notion:open", $attrs.tgLbNotionButton)
|
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
|
||||||
$el.off()
|
|
||||||
|
|
||||||
return {link:link}
|
|
||||||
|
|
||||||
module.directive("tgLbNotionButton", ["$log", "$rootScope", NotionButtonDirective])
|
|
||||||
|
|
|
@ -24,6 +24,33 @@ bindOnce = @.taiga.bindOnce
|
||||||
|
|
||||||
module = angular.module("taigaCommon")
|
module = angular.module("taigaCommon")
|
||||||
|
|
||||||
|
# How to test lists (-, *, 1.)
|
||||||
|
# test it with text after & before the list
|
||||||
|
# + is the cursor position
|
||||||
|
|
||||||
|
# CASE 1
|
||||||
|
# - aa+
|
||||||
|
# --> enter
|
||||||
|
# - aa
|
||||||
|
# - +
|
||||||
|
|
||||||
|
# CASE 1
|
||||||
|
# - +
|
||||||
|
# --> enter
|
||||||
|
|
||||||
|
# +
|
||||||
|
|
||||||
|
# CASE 3
|
||||||
|
# - bb+cc
|
||||||
|
# --> enter
|
||||||
|
# - bb
|
||||||
|
# - cc
|
||||||
|
|
||||||
|
# CASE 3
|
||||||
|
# +- aa
|
||||||
|
# --> enter
|
||||||
|
|
||||||
|
# - aa
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## WYSIWYG markitup editor directive
|
## WYSIWYG markitup editor directive
|
||||||
|
@ -64,35 +91,52 @@ tgMarkitupDirective = ($rootscope, $rs, $tr, $selectedText, $template) ->
|
||||||
markdown.off(".preview")
|
markdown.off(".preview")
|
||||||
closePreviewMode()
|
closePreviewMode()
|
||||||
|
|
||||||
markdownCaretPositon = false
|
setCaretPosition = (textarea, caretPosition) ->
|
||||||
|
if textarea.createTextRange
|
||||||
setCaretPosition = (elm, caretPos) ->
|
range = textarea.createTextRange()
|
||||||
if elm.createTextRange
|
range.move("character", caretPosition)
|
||||||
range = elm.createTextRange()
|
|
||||||
range.move("character", caretPos)
|
|
||||||
range.select()
|
range.select()
|
||||||
|
|
||||||
else if elm.selectionStart
|
else if textarea.selectionStart
|
||||||
elm.focus()
|
textarea.focus()
|
||||||
elm.setSelectionRange(caretPos, caretPos)
|
textarea.setSelectionRange(caretPosition, caretPosition)
|
||||||
|
|
||||||
removeEmptyLine = (textarea, line, currentCaretPosition) ->
|
# Calculate the scroll position
|
||||||
|
totalLines = textarea.value.split("\n").length
|
||||||
|
line = textarea.value[0..(caretPosition - 1)].split("\n").length
|
||||||
|
scrollRelation = line / totalLines
|
||||||
|
$el.scrollTop((scrollRelation * $el[0].scrollHeight) - ($el.height() / 2))
|
||||||
|
|
||||||
|
addLine = (textarea, nline, replace) ->
|
||||||
lines = textarea.value.split("\n")
|
lines = textarea.value.split("\n")
|
||||||
removedLineLength = lines[line].length
|
|
||||||
|
|
||||||
lines[line] = ""
|
if replace
|
||||||
|
lines[nline] = replace + lines[nline]
|
||||||
|
else
|
||||||
|
lines[nline] = ""
|
||||||
|
|
||||||
|
cursorPosition = 0
|
||||||
|
|
||||||
|
for line, key in lines
|
||||||
|
cursorPosition += line.length + 1 || 1
|
||||||
|
|
||||||
|
break if key == nline
|
||||||
|
|
||||||
textarea.value = lines.join("\n")
|
textarea.value = lines.join("\n")
|
||||||
|
|
||||||
#return the new position
|
#return the new position
|
||||||
return currentCaretPosition - removedLineLength + 1
|
if replace
|
||||||
|
return cursorPosition - lines[nline].length + replace.length - 1
|
||||||
|
else
|
||||||
|
return cursorPosition
|
||||||
|
|
||||||
markdownSettings =
|
markdownSettings =
|
||||||
nameSpace: "markdown"
|
nameSpace: "markdown"
|
||||||
onShiftEnter: {keepDefault:false, openWith:"\n\n"}
|
onShiftEnter: {keepDefault:false, openWith:"\n\n"}
|
||||||
onEnter:
|
onEnter:
|
||||||
keepDefault: false
|
keepDefault: false,
|
||||||
replaceWith: (data) =>
|
replaceWith: () -> "\n"
|
||||||
|
afterInsert: (data) ->
|
||||||
lines = data.textarea.value.split("\n")
|
lines = data.textarea.value.split("\n")
|
||||||
cursorLine = data.textarea.value[0..(data.caretPosition - 1)].split("\n").length
|
cursorLine = data.textarea.value[0..(data.caretPosition - 1)].split("\n").length
|
||||||
newLineContent = data.textarea.value[data.caretPosition..].split("\n")[0]
|
newLineContent = data.textarea.value[data.caretPosition..].split("\n")[0]
|
||||||
|
@ -105,12 +149,9 @@ tgMarkitupDirective = ($rootscope, $rs, $tr, $selectedText, $template) ->
|
||||||
emptyListItem = lastLine.match /^(\s*)\-\s$/
|
emptyListItem = lastLine.match /^(\s*)\-\s$/
|
||||||
|
|
||||||
if emptyListItem
|
if emptyListItem
|
||||||
markdownCaretPositon = removeEmptyLine(data.textarea, lines.length - 1, data.caretPosition)
|
markdownCaretPositon = addLine(data.textarea, cursorLine - 1)
|
||||||
else
|
else
|
||||||
breakLineAtBeginning = newLineContent.match /^(\s*)\-\s/
|
markdownCaretPositon = addLine(data.textarea, cursorLine, "#{match[1]}")
|
||||||
|
|
||||||
if !breakLineAtBeginning
|
|
||||||
return "\n#{match[1]}" if match
|
|
||||||
|
|
||||||
# unordered list *
|
# unordered list *
|
||||||
match = lastLine.match /^(\s*\* ).*/
|
match = lastLine.match /^(\s*\* ).*/
|
||||||
|
@ -119,12 +160,9 @@ tgMarkitupDirective = ($rootscope, $rs, $tr, $selectedText, $template) ->
|
||||||
emptyListItem = lastLine.match /^(\s*\* )$/
|
emptyListItem = lastLine.match /^(\s*\* )$/
|
||||||
|
|
||||||
if emptyListItem
|
if emptyListItem
|
||||||
markdownCaretPositon = removeEmptyLine(data.textarea, lines.length - 1, data.caretPosition)
|
markdownCaretPositon = addLine(data.textarea, cursorLine - 1)
|
||||||
else
|
else
|
||||||
breakLineAtBeginning = newLineContent.match /^(\s*)\*\s/
|
markdownCaretPositon = addLine(data.textarea, cursorLine, "#{match[1]}")
|
||||||
|
|
||||||
if !breakLineAtBeginning
|
|
||||||
return "\n#{match[1]}" if match
|
|
||||||
|
|
||||||
# ordered list
|
# ordered list
|
||||||
match = lastLine.match /^(\s*)(\d+)\.\s/
|
match = lastLine.match /^(\s*)(\d+)\.\s/
|
||||||
|
@ -133,29 +171,12 @@ tgMarkitupDirective = ($rootscope, $rs, $tr, $selectedText, $template) ->
|
||||||
emptyListItem = lastLine.match /^(\s*)(\d+)\.\s$/
|
emptyListItem = lastLine.match /^(\s*)(\d+)\.\s$/
|
||||||
|
|
||||||
if emptyListItem
|
if emptyListItem
|
||||||
markdownCaretPositon = removeEmptyLine(data.textarea, lines.length - 1, data.caretPosition)
|
markdownCaretPositon = addLine(data.textarea, cursorLine - 1)
|
||||||
else
|
else
|
||||||
breakLineAtBeginning = newLineContent.match /^(\s*)(\d+)\.\s/
|
markdownCaretPositon = addLine(data.textarea, cursorLine, "#{match[1] + (parseInt(match[2], 10) + 1)}. ")
|
||||||
|
|
||||||
if !breakLineAtBeginning
|
|
||||||
return "\n#{match[1] + (parseInt(match[2], 10) + 1)}. "
|
|
||||||
|
|
||||||
return "\n"
|
setCaretPosition(data.textarea, markdownCaretPositon) if markdownCaretPositon
|
||||||
|
|
||||||
afterInsert: (data) ->
|
|
||||||
# Calculate the scroll position
|
|
||||||
|
|
||||||
if markdownCaretPositon
|
|
||||||
setCaretPosition(data.textarea, markdownCaretPositon)
|
|
||||||
caretPosition = markdownCaretPositon
|
|
||||||
markdownCaretPositon = false
|
|
||||||
else
|
|
||||||
caretPosition = data.caretPosition
|
|
||||||
|
|
||||||
totalLines = data.textarea.value.split("\n").length
|
|
||||||
line = data.textarea.value[0..(caretPosition - 1)].split("\n").length
|
|
||||||
scrollRelation = line / totalLines
|
|
||||||
$el.scrollTop((scrollRelation * $el[0].scrollHeight) - ($el.height() / 2))
|
|
||||||
|
|
||||||
markupSet: [
|
markupSet: [
|
||||||
{
|
{
|
||||||
|
@ -214,14 +235,18 @@ tgMarkitupDirective = ($rootscope, $rs, $tr, $selectedText, $template) ->
|
||||||
{
|
{
|
||||||
name: $tr.t("markdown-editor.picture")
|
name: $tr.t("markdown-editor.picture")
|
||||||
key: "P"
|
key: "P"
|
||||||
replaceWith: '![[![Alternative text]!]]([![Url:!:http://]!] "[![Title]!]")'
|
replaceWith: '![[![Alternative text]!]](<<<[![Url:!:http://]!]>>> "[![Title]!]")'
|
||||||
|
beforeInsert:(markItUp) -> prepareUrlFormatting(markItUp)
|
||||||
|
afterInsert:(markItUp) -> urlFormatting(markItUp)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: $tr.t("markdown-editor.link")
|
name: $tr.t("markdown-editor.link")
|
||||||
key: "L"
|
key: "L"
|
||||||
openWith: "["
|
openWith: "["
|
||||||
closeWith: ']([![Url:!:http://]!] "[![Title]!]")'
|
closeWith: '](<<<[![Url:!:http://]!]>>> "[![Title]!]")'
|
||||||
placeHolder: $tr.t("markdown-editor.link-placeholder")
|
placeHolder: $tr.t("markdown-editor.link-placeholder")
|
||||||
|
beforeInsert:(markItUp) -> prepareUrlFormatting(markItUp)
|
||||||
|
afterInsert:(markItUp) -> urlFormatting(markItUp)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
separator: "---------------"
|
separator: "---------------"
|
||||||
|
@ -256,6 +281,45 @@ tgMarkitupDirective = ($rootscope, $rs, $tr, $selectedText, $template) ->
|
||||||
target = angular.element(event.textarea)
|
target = angular.element(event.textarea)
|
||||||
$model.$setViewValue(target.val())
|
$model.$setViewValue(target.val())
|
||||||
|
|
||||||
|
prepareUrlFormatting = (markItUp) ->
|
||||||
|
console.log(markItUp)
|
||||||
|
regex = /(<<<|>>>)/gi
|
||||||
|
result = 0
|
||||||
|
indices = []
|
||||||
|
(indices.push(result.index)) while ( (result = regex.exec(markItUp.textarea.value)) )
|
||||||
|
markItUp.donotparse = indices
|
||||||
|
console.log(indices)
|
||||||
|
|
||||||
|
urlFormatting = (markItUp) ->
|
||||||
|
console.log(markItUp.donotparse)
|
||||||
|
regex = /<<</gi
|
||||||
|
result = 0
|
||||||
|
startIndex = 0
|
||||||
|
|
||||||
|
loop
|
||||||
|
result = regex.exec(markItUp.textarea.value)
|
||||||
|
break if !result
|
||||||
|
if result.index not in markItUp.donotparse
|
||||||
|
startIndex = result.index
|
||||||
|
break
|
||||||
|
|
||||||
|
regex = />>>/gi
|
||||||
|
endIndex = 0
|
||||||
|
loop
|
||||||
|
result = regex.exec(markItUp.textarea.value)
|
||||||
|
break if !result
|
||||||
|
if result.index not in markItUp.donotparse
|
||||||
|
endIndex = result.index
|
||||||
|
break
|
||||||
|
|
||||||
|
value = markItUp.textarea.value
|
||||||
|
url = value.substring(startIndex, endIndex).replace('<<<', '').replace('>>>', '')
|
||||||
|
url = url.replace('(', '%28').replace(')', '%29')
|
||||||
|
url = url.replace('[', '%5B').replace(']', '%5D')
|
||||||
|
value = value.substring(0, startIndex) + url + value.substring(endIndex+3, value.length)
|
||||||
|
markItUp.textarea.value = value
|
||||||
|
markItUp.donotparse = undefined
|
||||||
|
|
||||||
markdownTitle = (markItUp, char) ->
|
markdownTitle = (markItUp, char) ->
|
||||||
heading = ""
|
heading = ""
|
||||||
n = $.trim(markItUp.selection or markItUp.placeHolder).length
|
n = $.trim(markItUp.selection or markItUp.placeHolder).length
|
||||||
|
|
|
@ -62,7 +62,7 @@ taiga.PageMixin = PageMixin
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Filters Mixin
|
## Filters Mixin
|
||||||
#############################################################################
|
#############################################################################
|
||||||
# This mixin requires @location ($tgLocation) and @scope
|
# This mixin requires @location ($tgLocation), and @scope
|
||||||
|
|
||||||
class FiltersMixin
|
class FiltersMixin
|
||||||
selectFilter: (name, value, load=false) ->
|
selectFilter: (name, value, load=false) ->
|
||||||
|
@ -73,10 +73,12 @@ class FiltersMixin
|
||||||
existing = _.compact(existing)
|
existing = _.compact(existing)
|
||||||
value = joinStr(",", _.uniq(existing))
|
value = joinStr(",", _.uniq(existing))
|
||||||
|
|
||||||
|
if !@location.isInCurrentRouteParams(name, value)
|
||||||
location = if load then @location else @location.noreload(@scope)
|
location = if load then @location else @location.noreload(@scope)
|
||||||
location.search(name, value)
|
location.search(name, value)
|
||||||
|
|
||||||
replaceFilter: (name, value, load=false) ->
|
replaceFilter: (name, value, load=false) ->
|
||||||
|
if !@location.isInCurrentRouteParams(name, value)
|
||||||
location = if load then @location else @location.noreload(@scope)
|
location = if load then @location else @location.noreload(@scope)
|
||||||
location.search(name, value)
|
location.search(name, value)
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,6 @@ FeedbackDirective = ($lightboxService, $repo, $confirm, $loading)->
|
||||||
submitButton = $el.find(".submit-button")
|
submitButton = $el.find(".submit-button")
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
$scope.$on "feedback:show", ->
|
$scope.$on "feedback:show", ->
|
||||||
$scope.$apply ->
|
$scope.$apply ->
|
||||||
|
|
|
@ -1,111 +0,0 @@
|
||||||
###
|
|
||||||
# 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/integrations/github.coffee
|
|
||||||
###
|
|
||||||
|
|
||||||
taiga = @.taiga
|
|
||||||
|
|
||||||
module = angular.module("taigaIntegrations")
|
|
||||||
|
|
||||||
AUTH_URL = "https://github.com/login/oauth/authorize"
|
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
## User story team requirements button directive
|
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
GithubLoginButtonDirective = ($window, $params, $location, $config, $events, $confirm, $auth, $navUrls, $loader) ->
|
|
||||||
# Login or registar a user with his/her github account.
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
# tg-github-login-button()
|
|
||||||
#
|
|
||||||
# Requirements:
|
|
||||||
# - ...
|
|
||||||
|
|
||||||
template = """
|
|
||||||
<a class="button button-github" href="" title="Enter with your github account">
|
|
||||||
<span class="icon icon-github"></span>
|
|
||||||
<span>Login with Github</span>
|
|
||||||
</a>
|
|
||||||
""" #TODO: i18n
|
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
|
||||||
clientId = $config.get("gitHubClientId", null)
|
|
||||||
return if not clientId
|
|
||||||
|
|
||||||
renderGitHubButton = ->
|
|
||||||
$el.html(template) if clientId
|
|
||||||
|
|
||||||
loginOnSuccess = (response) ->
|
|
||||||
if $params.next and $params.next != $navUrls.resolve("login")
|
|
||||||
nextUrl = $params.next
|
|
||||||
else
|
|
||||||
nextUrl = $navUrls.resolve("home")
|
|
||||||
|
|
||||||
$events.setupConnection()
|
|
||||||
|
|
||||||
$location.search("next", null)
|
|
||||||
$location.search("token", null)
|
|
||||||
$location.search("state", null)
|
|
||||||
$location.search("code", null)
|
|
||||||
$location.path(nextUrl)
|
|
||||||
|
|
||||||
loginOnError = (response) ->
|
|
||||||
$location.search("state", null)
|
|
||||||
$location.search("code", null)
|
|
||||||
$loader.pageLoaded()
|
|
||||||
|
|
||||||
if response.data.error_message
|
|
||||||
$confirm.notify("light-error", response.data.error_message )
|
|
||||||
else
|
|
||||||
$confirm.notify("light-error", "Our Oompa Loompas have not been able to get you
|
|
||||||
credentials from GitHub.") #TODO: i18n
|
|
||||||
|
|
||||||
loginWithGitHubAccount = ->
|
|
||||||
type = $params.state
|
|
||||||
code = $params.code
|
|
||||||
token = $params.token
|
|
||||||
|
|
||||||
return if not (type == "github" and code)
|
|
||||||
$loader.start()
|
|
||||||
|
|
||||||
data = {code: code, token: token}
|
|
||||||
$auth.login(data, type).then(loginOnSuccess, loginOnError)
|
|
||||||
|
|
||||||
renderGitHubButton()
|
|
||||||
loginWithGitHubAccount()
|
|
||||||
|
|
||||||
$el.on "click", ".button-github", (event) ->
|
|
||||||
redirectToUri = $location.absUrl()
|
|
||||||
url = "#{AUTH_URL}?client_id=#{clientId}&redirect_uri=#{redirectToUri}&state=github&scope=user:email"
|
|
||||||
$window.location.href = url
|
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
|
||||||
$el.off()
|
|
||||||
|
|
||||||
return {
|
|
||||||
link: link
|
|
||||||
restrict: "EA"
|
|
||||||
template: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
module.directive("tgGithubLoginButton", ["$window", '$routeParams', "$tgLocation", "$tgConfig", "$tgEvents",
|
|
||||||
"$tgConfirm", "$tgAuth", "$tgNavUrls", "tgLoader",
|
|
||||||
GithubLoginButtonDirective])
|
|
|
@ -85,6 +85,9 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
@rootscope.$broadcast("history:reload")
|
@rootscope.$broadcast("history:reload")
|
||||||
@.loadIssue()
|
@.loadIssue()
|
||||||
|
|
||||||
|
@scope.$on "custom-attributes-values:edit", =>
|
||||||
|
@rootscope.$broadcast("history:reload")
|
||||||
|
|
||||||
initializeOnDeleteGoToUrl: ->
|
initializeOnDeleteGoToUrl: ->
|
||||||
ctx = {project: @scope.project.slug}
|
ctx = {project: @scope.project.slug}
|
||||||
if @scope.project.is_issues_activated
|
if @scope.project.is_issues_activated
|
||||||
|
|
|
@ -75,7 +75,6 @@ CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService, $loading)
|
||||||
submitButton = $el.find(".submit-button")
|
submitButton = $el.find(".submit-button")
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
|
|
||||||
return {link:link}
|
return {link:link}
|
||||||
|
@ -123,7 +122,6 @@ CreateBulkIssuesDirective = ($repo, $rs, $confirm, $rootscope, $loading, lightbo
|
||||||
submitButton = $el.find(".submit-button")
|
submitButton = $el.find(".submit-button")
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
$el.off()
|
$el.off()
|
||||||
|
|
|
@ -94,6 +94,9 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
||||||
|
|
||||||
loadProject: ->
|
loadProject: ->
|
||||||
return @rs.projects.getBySlug(@params.pslug).then (project) =>
|
return @rs.projects.getBySlug(@params.pslug).then (project) =>
|
||||||
|
if not project.is_issues_activated
|
||||||
|
@location.path(@navUrls.resolve("permission-denied"))
|
||||||
|
|
||||||
@scope.projectId = project.id
|
@scope.projectId = project.id
|
||||||
@scope.project = project
|
@scope.project = project
|
||||||
@scope.$emit('project:loaded', project)
|
@scope.$emit('project:loaded', project)
|
||||||
|
@ -530,6 +533,9 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template) -
|
||||||
|
|
||||||
selectQFilter = debounceLeading 100, (value) ->
|
selectQFilter = debounceLeading 100, (value) ->
|
||||||
return if value is undefined
|
return if value is undefined
|
||||||
|
|
||||||
|
$ctrl.replaceFilter("page", null)
|
||||||
|
|
||||||
if value.length == 0
|
if value.length == 0
|
||||||
$ctrl.replaceFilter("q", null)
|
$ctrl.replaceFilter("q", null)
|
||||||
$ctrl.storeFilters()
|
$ctrl.storeFilters()
|
||||||
|
|
|
@ -138,13 +138,20 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
||||||
@scope.userstories = userstories
|
@scope.userstories = userstories
|
||||||
|
|
||||||
usByStatus = _.groupBy(userstories, "status")
|
usByStatus = _.groupBy(userstories, "status")
|
||||||
|
us_archived = []
|
||||||
for status in @scope.usStatusList
|
for status in @scope.usStatusList
|
||||||
if not usByStatus[status.id]?
|
if not usByStatus[status.id]?
|
||||||
usByStatus[status.id] = []
|
usByStatus[status.id] = []
|
||||||
|
if @scope.usByStatus?
|
||||||
|
for us in @scope.usByStatus[status.id]
|
||||||
|
if us.status != status.id
|
||||||
|
us_archived.push(us)
|
||||||
|
|
||||||
# Must preserve the archived columns if loaded
|
# Must preserve the archived columns if loaded
|
||||||
if status.is_archived and @scope.usByStatus?
|
if status.is_archived and @scope.usByStatus? and @scope.usByStatus[status.id].length != 0
|
||||||
usByStatus[status.id] = @scope.usByStatus[status.id]
|
for us in @scope.usByStatus[status.id].concat(us_archived)
|
||||||
|
if us.status == status.id
|
||||||
|
usByStatus[status.id].push(us)
|
||||||
|
|
||||||
usByStatus[status.id] = _.sortBy(usByStatus[status.id], "kanban_order")
|
usByStatus[status.id] = _.sortBy(usByStatus[status.id], "kanban_order")
|
||||||
|
|
||||||
|
@ -176,6 +183,9 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
||||||
|
|
||||||
loadProject: ->
|
loadProject: ->
|
||||||
return @rs.projects.getBySlug(@params.pslug).then (project) =>
|
return @rs.projects.getBySlug(@params.pslug).then (project) =>
|
||||||
|
if not project.is_kanban_activated
|
||||||
|
@location.path(@navUrls.resolve("permission-denied"))
|
||||||
|
|
||||||
@scope.projectId = project.id
|
@scope.projectId = project.id
|
||||||
@scope.project = project
|
@scope.project = project
|
||||||
@scope.projectId = project.id
|
@scope.projectId = project.id
|
||||||
|
@ -293,36 +303,6 @@ KanbanDirective = ($repo, $rootscope) ->
|
||||||
|
|
||||||
module.directive("tgKanban", ["$tgRepo", "$rootScope", KanbanDirective])
|
module.directive("tgKanban", ["$tgRepo", "$rootScope", KanbanDirective])
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
## Kanban Column Height Fixer Directive
|
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
KanbanColumnHeightFixerDirective = ->
|
|
||||||
mainPadding = 32 # px
|
|
||||||
scrollPadding = 0 # px
|
|
||||||
|
|
||||||
renderSize = ($el) ->
|
|
||||||
elementOffset = $el.parent().parent().offset().top
|
|
||||||
windowHeight = angular.element(window).height()
|
|
||||||
columnHeight = windowHeight - elementOffset - mainPadding - scrollPadding
|
|
||||||
$el.css("height", "#{columnHeight}px")
|
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
|
||||||
timeout(500, -> renderSize($el))
|
|
||||||
|
|
||||||
$scope.$on "resize", ->
|
|
||||||
renderSize($el)
|
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
|
||||||
$el.off()
|
|
||||||
|
|
||||||
return {link:link}
|
|
||||||
|
|
||||||
|
|
||||||
module.directive("tgKanbanColumnHeightFixer", KanbanColumnHeightFixerDirective)
|
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Kanban Archived Status Column Header Control
|
## Kanban Archived Status Column Header Control
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
@ -527,6 +507,9 @@ KanbanUserDirective = ($log) ->
|
||||||
clickable = false
|
clickable = false
|
||||||
|
|
||||||
link = ($scope, $el, $attrs, $model) ->
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
username_label = $el.parent().find("a.task-assigned")
|
||||||
|
username_label.addClass("not-clickable")
|
||||||
|
|
||||||
if not $attrs.tgKanbanUserAvatar
|
if not $attrs.tgKanbanUserAvatar
|
||||||
return $log.error "KanbanUserDirective: no attr is defined"
|
return $log.error "KanbanUserDirective: no attr is defined"
|
||||||
|
|
||||||
|
@ -546,9 +529,12 @@ KanbanUserDirective = ($log) ->
|
||||||
|
|
||||||
html = template(ctx)
|
html = template(ctx)
|
||||||
$el.html(html)
|
$el.html(html)
|
||||||
username_label = $el.parent().find("a.task-assigned")
|
username_label.text(ctx.name)
|
||||||
username_label.html(ctx.name)
|
|
||||||
username_label.on "click", (event) ->
|
bindOnce $scope, "project", (project) ->
|
||||||
|
if project.my_permissions.indexOf("modify_us") > -1
|
||||||
|
clickable = true
|
||||||
|
$el.on "click", (event) =>
|
||||||
if $el.find("a").hasClass("noclick")
|
if $el.find("a").hasClass("noclick")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -556,10 +542,8 @@ KanbanUserDirective = ($log) ->
|
||||||
$ctrl = $el.controller()
|
$ctrl = $el.controller()
|
||||||
$ctrl.changeUsAssignedTo(us)
|
$ctrl.changeUsAssignedTo(us)
|
||||||
|
|
||||||
bindOnce $scope, "project", (project) ->
|
username_label.removeClass("not-clickable")
|
||||||
if project.my_permissions.indexOf("modify_us") > -1
|
username_label.on "click", (event) ->
|
||||||
clickable = true
|
|
||||||
$el.on "click", (event) =>
|
|
||||||
if $el.find("a").hasClass("noclick")
|
if $el.find("a").hasClass("noclick")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ class ProjectsNavigationController extends taiga.Controller
|
||||||
@.loadInitialData()
|
@.loadInitialData()
|
||||||
|
|
||||||
loadInitialData: ->
|
loadInitialData: ->
|
||||||
return @rs.projects.list().then (projects) =>
|
return @rs.projects.listByMember(@rootscope.user?.id).then (projects) =>
|
||||||
for project in projects
|
for project in projects
|
||||||
project.url = @projectUrl.get(project)
|
project.url = @projectUrl.get(project)
|
||||||
@scope.projects = projects
|
@scope.projects = projects
|
||||||
|
|
|
@ -118,7 +118,6 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
|
||||||
submitButton = $el.find(".submit-button")
|
submitButton = $el.find(".submit-button")
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
$el.on "click", ".close", (event) ->
|
$el.on "click", ".close", (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
|
@ -50,7 +50,7 @@ class ProjectsController extends taiga.Controller
|
||||||
promise = @.loadInitialData()
|
promise = @.loadInitialData()
|
||||||
|
|
||||||
promise.then () =>
|
promise.then () =>
|
||||||
@scope.$emit("projects:loaded")
|
@scope.$emit("projects:loaded", @.projects)
|
||||||
|
|
||||||
promise.then null, @.onInitialDataError.bind(@)
|
promise.then null, @.onInitialDataError.bind(@)
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ class ProjectsController extends taiga.Controller
|
||||||
promise.finally tgLoader.pageLoaded
|
promise.finally tgLoader.pageLoaded
|
||||||
|
|
||||||
loadInitialData: ->
|
loadInitialData: ->
|
||||||
return @rs.projects.list().then (projects) =>
|
return @rs.projects.listByMember(@rootscope.user?.id).then (projects) =>
|
||||||
@.projects = {'recents': projects.slice(0, 8), 'all': projects}
|
@.projects = {'recents': projects.slice(0, 8), 'all': projects}
|
||||||
for project in projects
|
for project in projects
|
||||||
project.url = @projectUrl.get(project)
|
project.url = @projectUrl.get(project)
|
||||||
|
@ -257,8 +257,8 @@ ProjectsListDirective = ($compile, $template) ->
|
||||||
$el.html($compile(template({projects: projects}))($scope))
|
$el.html($compile(template({projects: projects}))($scope))
|
||||||
$scope.$emit("regenerate:project-pagination")
|
$scope.$emit("regenerate:project-pagination")
|
||||||
|
|
||||||
$scope.$watch "projects", (projects) ->
|
$scope.$on "projects:loaded", (ctx, projects) ->
|
||||||
render(projects) if projects?
|
render(projects.all) if projects.all?
|
||||||
|
|
||||||
return {
|
return {
|
||||||
link: link
|
link: link
|
||||||
|
|
|
@ -102,12 +102,27 @@ urls = {
|
||||||
"attachments/task": "/tasks/attachments"
|
"attachments/task": "/tasks/attachments"
|
||||||
"attachments/wiki_page": "/wiki/attachments"
|
"attachments/wiki_page": "/wiki/attachments"
|
||||||
|
|
||||||
|
# Custom Attributess
|
||||||
|
"custom-attributes/userstory": "/userstory-custom-attributes"
|
||||||
|
"custom-attributes/issue": "/issue-custom-attributes"
|
||||||
|
"custom-attributes/task": "/task-custom-attributes"
|
||||||
|
|
||||||
|
# Custom field values
|
||||||
|
"custom-attributes-values/userstory": "/userstories/custom-attributes-values"
|
||||||
|
"custom-attributes-values/issue": "/issues/custom-attributes-values"
|
||||||
|
"custom-attributes-values/task": "/tasks/custom-attributes-values"
|
||||||
|
|
||||||
# Feedback
|
# Feedback
|
||||||
"feedback": "/feedback"
|
"feedback": "/feedback"
|
||||||
|
|
||||||
# Export/Import
|
# Export/Import
|
||||||
"exporter": "/exporter"
|
"exporter": "/exporter"
|
||||||
"importer": "/importer/load_dump"
|
"importer": "/importer/load_dump"
|
||||||
|
|
||||||
|
# CSV
|
||||||
|
"userstories-csv": "/userstories/csv?uuid=%s"
|
||||||
|
"tasks-csv": "/tasks/csv?uuid=%s"
|
||||||
|
"issues-csv": "/issues/csv?uuid=%s"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Initialize api urls service
|
# Initialize api urls service
|
||||||
|
@ -133,6 +148,8 @@ module.run([
|
||||||
"$log",
|
"$log",
|
||||||
"$tgResources",
|
"$tgResources",
|
||||||
"$tgProjectsResourcesProvider",
|
"$tgProjectsResourcesProvider",
|
||||||
|
"$tgCustomAttributesResourcesProvider",
|
||||||
|
"$tgCustomAttributesValuesResourcesProvider",
|
||||||
"$tgMembershipsResourcesProvider",
|
"$tgMembershipsResourcesProvider",
|
||||||
"$tgNotifyPoliciesResourcesProvider",
|
"$tgNotifyPoliciesResourcesProvider",
|
||||||
"$tgInvitationsResourcesProvider",
|
"$tgInvitationsResourcesProvider",
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
###
|
||||||
|
# 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/resources/custom-field-values.coffee
|
||||||
|
###
|
||||||
|
|
||||||
|
taiga = @.taiga
|
||||||
|
|
||||||
|
resourceProvider = ($repo) ->
|
||||||
|
_get = (objectId, resource) ->
|
||||||
|
return $repo.queryOne(resource, objectId)
|
||||||
|
|
||||||
|
service = {
|
||||||
|
userstory: {
|
||||||
|
get: (objectId) -> _get(objectId, "custom-attributes-values/userstory")
|
||||||
|
}
|
||||||
|
task: {
|
||||||
|
get: (objectId) -> _get(objectId, "custom-attributes-values/task")
|
||||||
|
}
|
||||||
|
issue: {
|
||||||
|
get: (objectId) -> _get(objectId, "custom-attributes-values/issue")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (instance) ->
|
||||||
|
instance.customAttributesValues = service
|
||||||
|
|
||||||
|
module = angular.module("taigaResources")
|
||||||
|
module.factory("$tgCustomAttributesValuesResourcesProvider", ["$tgRepo", resourceProvider])
|
|
@ -0,0 +1,48 @@
|
||||||
|
###
|
||||||
|
# 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/resources/projects.coffee
|
||||||
|
###
|
||||||
|
|
||||||
|
|
||||||
|
taiga = @.taiga
|
||||||
|
sizeFormat = @.taiga.sizeFormat
|
||||||
|
|
||||||
|
|
||||||
|
resourceProvider = ($repo) ->
|
||||||
|
_list = (projectId, resource) ->
|
||||||
|
return $repo.queryMany(resource, {project: projectId})
|
||||||
|
|
||||||
|
service = {
|
||||||
|
userstory:{
|
||||||
|
list: (projectId) -> _list(projectId, "custom-attributes/userstory")
|
||||||
|
}
|
||||||
|
task:{
|
||||||
|
list: (projectId) -> _list(projectId, "custom-attributes/task")
|
||||||
|
}
|
||||||
|
issue: {
|
||||||
|
list: (projectId) -> _list(projectId, "custom-attributes/issue")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (instance) ->
|
||||||
|
instance.customAttributes = service
|
||||||
|
|
||||||
|
|
||||||
|
module = angular.module("taigaResources")
|
||||||
|
module.factory("$tgCustomAttributesResourcesProvider", ["$tgRepo", resourceProvider])
|
|
@ -36,6 +36,10 @@ resourceProvider = ($config, $repo, $http, $urls, $auth, $q, $rootScope) ->
|
||||||
service.list = ->
|
service.list = ->
|
||||||
return $repo.queryMany("projects")
|
return $repo.queryMany("projects")
|
||||||
|
|
||||||
|
service.listByMember = (memberId) ->
|
||||||
|
params = {"member": memberId}
|
||||||
|
return $repo.queryMany("projects", params)
|
||||||
|
|
||||||
service.templates = ->
|
service.templates = ->
|
||||||
return $repo.queryMany("project-templates")
|
return $repo.queryMany("project-templates")
|
||||||
|
|
||||||
|
@ -50,6 +54,18 @@ resourceProvider = ($config, $repo, $http, $urls, $auth, $q, $rootScope) ->
|
||||||
service.stats = (projectId) ->
|
service.stats = (projectId) ->
|
||||||
return $repo.queryOneRaw("projects", "#{projectId}/stats")
|
return $repo.queryOneRaw("projects", "#{projectId}/stats")
|
||||||
|
|
||||||
|
service.regenerate_userstories_csv_uuid = (projectId) ->
|
||||||
|
url = "#{$urls.resolve("projects")}/#{projectId}/regenerate_userstories_csv_uuid"
|
||||||
|
return $http.post(url)
|
||||||
|
|
||||||
|
service.regenerate_issues_csv_uuid = (projectId) ->
|
||||||
|
url = "#{$urls.resolve("projects")}/#{projectId}/regenerate_issues_csv_uuid"
|
||||||
|
return $http.post(url)
|
||||||
|
|
||||||
|
service.regenerate_tasks_csv_uuid = (projectId) ->
|
||||||
|
url = "#{$urls.resolve("projects")}/#{projectId}/regenerate_tasks_csv_uuid"
|
||||||
|
return $http.post(url)
|
||||||
|
|
||||||
service.leave = (projectId) ->
|
service.leave = (projectId) ->
|
||||||
url = "#{$urls.resolve("projects")}/#{projectId}/leave"
|
url = "#{$urls.resolve("projects")}/#{projectId}/leave"
|
||||||
return $http.post(url)
|
return $http.post(url)
|
||||||
|
|
|
@ -134,7 +134,6 @@ SearchBoxDirective = ($lightboxService, $navurls, $location, $route)->
|
||||||
$el.find("#search-text").val("")
|
$el.find("#search-text").val("")
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
return {link:link}
|
return {link:link}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxSer
|
||||||
$scope.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").html("Create") #TODO: i18n
|
||||||
$el.find(".title").html("New task ") #TODO: i18n
|
$el.find(".title").html("New task ") #TODO: i18n
|
||||||
$el.find(".tag-input").val("")
|
$el.find(".tag-input").val("")
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxSer
|
||||||
$scope.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").html("Save") #TODO: i18n
|
||||||
$el.find(".title").html("Edit task ") #TODO: i18n
|
$el.find(".title").html("Edit task ") #TODO: i18n
|
||||||
$el.find(".tag-input").val("")
|
$el.find(".tag-input").val("")
|
||||||
|
|
||||||
|
@ -83,7 +83,6 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxSer
|
||||||
$rootscope.$broadcast(broadcastEvent, data)
|
$rootscope.$broadcast(broadcastEvent, data)
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
$el.off()
|
$el.off()
|
||||||
|
@ -127,7 +126,6 @@ CreateBulkTasksDirective = ($repo, $rs, $rootscope, $loading, lightboxService) -
|
||||||
submitButton = $el.find(".submit-button")
|
submitButton = $el.find(".submit-button")
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
$el.off()
|
$el.off()
|
||||||
|
|
|
@ -104,6 +104,9 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
|
|
||||||
loadProject: ->
|
loadProject: ->
|
||||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||||
|
if not project.is_backlog_activated
|
||||||
|
@location.path(@navUrls.resolve("permission-denied"))
|
||||||
|
|
||||||
@scope.project = project
|
@scope.project = project
|
||||||
# Not used at this momment
|
# Not used at this momment
|
||||||
@scope.pointsList = _.sortBy(project.points, "order")
|
@scope.pointsList = _.sortBy(project.points, "order")
|
||||||
|
@ -116,6 +119,8 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
|
|
||||||
@scope.$emit('project:loaded', project)
|
@scope.$emit('project:loaded', project)
|
||||||
|
|
||||||
|
@.fillUsersAndRoles(project.users, project.roles)
|
||||||
|
|
||||||
return project
|
return project
|
||||||
|
|
||||||
loadSprintStats: ->
|
loadSprintStats: ->
|
||||||
|
@ -185,7 +190,6 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
return promise.then(=> @.loadProject())
|
return promise.then(=> @.loadProject())
|
||||||
.then(=> @.loadUsersAndRoles())
|
|
||||||
.then(=> @.loadTaskboard())
|
.then(=> @.loadTaskboard())
|
||||||
|
|
||||||
refreshTasksOrder: (tasks) ->
|
refreshTasksOrder: (tasks) ->
|
||||||
|
@ -297,30 +301,6 @@ TaskboardTaskDirective = ($rootscope) ->
|
||||||
|
|
||||||
module.directive("tgTaskboardTask", ["$rootScope", TaskboardTaskDirective])
|
module.directive("tgTaskboardTask", ["$rootScope", TaskboardTaskDirective])
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
## Taskboard Table Height Fixer Directive
|
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
TaskboardTableHeightFixerDirective = ->
|
|
||||||
mainPadding = 32 # px
|
|
||||||
|
|
||||||
renderSize = ($el) ->
|
|
||||||
elementOffset = $el.offset().top
|
|
||||||
windowHeight = angular.element(window).height()
|
|
||||||
columnHeight = windowHeight - elementOffset - mainPadding
|
|
||||||
$el.css("height", "#{columnHeight}px")
|
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
|
||||||
timeout(500, -> renderSize($el))
|
|
||||||
|
|
||||||
$scope.$on "resize", ->
|
|
||||||
renderSize($el)
|
|
||||||
|
|
||||||
return {link:link}
|
|
||||||
|
|
||||||
|
|
||||||
module.directive("tgTaskboardTableHeightFixer", TaskboardTableHeightFixerDirective)
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Taskboard Squish Column Directive
|
## Taskboard Squish Column Directive
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
@ -421,12 +401,7 @@ TaskboardUserDirective = ($log) ->
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
username_label = $el.parent().find("a.task-assigned")
|
username_label = $el.parent().find("a.task-assigned")
|
||||||
username_label.on "click", (event) ->
|
username_label.addClass("not-clickable")
|
||||||
if $el.find('a').hasClass('noclick')
|
|
||||||
return
|
|
||||||
|
|
||||||
$ctrl = $el.controller()
|
|
||||||
$ctrl.editTaskAssignedTo($scope.task)
|
|
||||||
|
|
||||||
$scope.$watch 'task.assigned_to', (assigned_to) ->
|
$scope.$watch 'task.assigned_to', (assigned_to) ->
|
||||||
user = $scope.usersById[assigned_to]
|
user = $scope.usersById[assigned_to]
|
||||||
|
@ -449,6 +424,15 @@ TaskboardUserDirective = ($log) ->
|
||||||
$ctrl = $el.controller()
|
$ctrl = $el.controller()
|
||||||
$ctrl.editTaskAssignedTo($scope.task)
|
$ctrl.editTaskAssignedTo($scope.task)
|
||||||
|
|
||||||
|
username_label.removeClass("not-clickable")
|
||||||
|
username_label.on "click", (event) ->
|
||||||
|
if $el.find('a').hasClass('noclick')
|
||||||
|
return
|
||||||
|
|
||||||
|
$ctrl = $el.controller()
|
||||||
|
$ctrl.editTaskAssignedTo($scope.task)
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
link: link,
|
link: link,
|
||||||
templateUrl: "taskboard/taskboard-user.html",
|
templateUrl: "taskboard/taskboard-user.html",
|
||||||
|
|
|
@ -36,6 +36,11 @@ module = angular.module("taigaBacklog")
|
||||||
|
|
||||||
TaskboardSortableDirective = ($repo, $rs, $rootscope) ->
|
TaskboardSortableDirective = ($repo, $rs, $rootscope) ->
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
|
bindOnce $scope, "project", (project) ->
|
||||||
|
# If the user has not enough permissions we don't enable the sortable
|
||||||
|
if not (project.my_permissions.indexOf("modify_us") > -1)
|
||||||
|
return
|
||||||
|
|
||||||
oldParentScope = null
|
oldParentScope = null
|
||||||
newParentScope = null
|
newParentScope = null
|
||||||
itemEl = null
|
itemEl = null
|
||||||
|
|
|
@ -71,6 +71,8 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
@rootscope.$broadcast("history:reload")
|
@rootscope.$broadcast("history:reload")
|
||||||
@scope.$on "attachment:delete", =>
|
@scope.$on "attachment:delete", =>
|
||||||
@rootscope.$broadcast("history:reload")
|
@rootscope.$broadcast("history:reload")
|
||||||
|
@scope.$on "custom-attributes-values:edit", =>
|
||||||
|
@rootscope.$broadcast("history:reload")
|
||||||
|
|
||||||
initializeOnDeleteGoToUrl: ->
|
initializeOnDeleteGoToUrl: ->
|
||||||
ctx = {project: @scope.project.slug}
|
ctx = {project: @scope.project.slug}
|
||||||
|
|
|
@ -69,18 +69,18 @@ class TeamController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
loadMembers: ->
|
loadMembers: ->
|
||||||
return @rs.memberships.list(@scope.projectId, {}, false).then (data) =>
|
return @rs.memberships.list(@scope.projectId, {}, false).then (data) =>
|
||||||
currentUser = @auth.getUser()
|
currentUser = @auth.getUser()
|
||||||
if not currentUser.photo?
|
if currentUser? and not currentUser.photo?
|
||||||
currentUser.photo = "/images/unnamed.png"
|
currentUser.photo = "/images/unnamed.png"
|
||||||
|
|
||||||
@scope.currentUser = _.find data, (membership) =>
|
@scope.currentUser = _.find data, (membership) =>
|
||||||
return membership.user == currentUser.id
|
return currentUser? and membership.user == currentUser.id
|
||||||
|
|
||||||
@scope.totals = {}
|
@scope.totals = {}
|
||||||
_.forEach data, (membership) =>
|
_.forEach data, (membership) =>
|
||||||
@scope.totals[membership.user] = 0
|
@scope.totals[membership.user] = 0
|
||||||
|
|
||||||
@scope.memberships = _.filter data, (membership) =>
|
@scope.memberships = _.filter data, (membership) =>
|
||||||
if membership.user && membership.user != currentUser.id && membership.is_user_active
|
if membership.user && (not currentUser? or membership.user != currentUser.id) && membership.is_user_active
|
||||||
return membership
|
return membership
|
||||||
|
|
||||||
for membership in @scope.memberships
|
for membership in @scope.memberships
|
||||||
|
|
|
@ -97,7 +97,6 @@ UserChangePasswordDirective = ($rs, $confirm, $loading) ->
|
||||||
submitButton = $el.find(".submit-button")
|
submitButton = $el.find(".submit-button")
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
$el.off()
|
$el.off()
|
||||||
|
|
|
@ -91,7 +91,8 @@ UserProfileDirective = ($confirm, $auth, $repo) ->
|
||||||
changeEmail = $scope.user.isAttributeModified("email")
|
changeEmail = $scope.user.isAttributeModified("email")
|
||||||
|
|
||||||
onSuccess = (data) =>
|
onSuccess = (data) =>
|
||||||
$auth.setUser($scope.user)
|
$auth.setUser(data)
|
||||||
|
|
||||||
if changeEmail
|
if changeEmail
|
||||||
$confirm.success("<strong>Check your inbox!</strong><br />
|
$confirm.success("<strong>Check your inbox!</strong><br />
|
||||||
We have sent a mail to your account<br />
|
We have sent a mail to your account<br />
|
||||||
|
@ -107,8 +108,6 @@ UserProfileDirective = ($confirm, $auth, $repo) ->
|
||||||
|
|
||||||
$el.on "submit", "form", submit
|
$el.on "submit", "form", submit
|
||||||
|
|
||||||
$el.on "click", ".submit-button", submit
|
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
$el.off()
|
$el.off()
|
||||||
|
|
||||||
|
@ -131,26 +130,26 @@ UserAvatarDirective = ($auth, $model, $rs, $confirm) ->
|
||||||
$auth.setUser(user)
|
$auth.setUser(user)
|
||||||
$scope.user = user
|
$scope.user = user
|
||||||
|
|
||||||
$el.find('.overlay').hide()
|
$el.find('.overlay').addClass('hidden')
|
||||||
$confirm.notify('success')
|
$confirm.notify('success')
|
||||||
|
|
||||||
onError = (response) ->
|
onError = (response) ->
|
||||||
showSizeInfo() if response.status == 413
|
showSizeInfo() if response.status == 413
|
||||||
$el.find('.overlay').hide()
|
$el.find('.overlay').addClass('hidden')
|
||||||
$confirm.notify('error', response.data._error_message)
|
$confirm.notify('error', response.data._error_message)
|
||||||
|
|
||||||
# Change photo
|
# Change photo
|
||||||
$el.on "click", ".button.change", ->
|
$el.on "click", ".js-change-avatar", ->
|
||||||
$el.find("#avatar-field").click()
|
$el.find("#avatar-field").click()
|
||||||
|
|
||||||
$el.on "change", "#avatar-field", (event) ->
|
$el.on "change", "#avatar-field", (event) ->
|
||||||
if $scope.avatarAttachment
|
if $scope.avatarAttachment
|
||||||
$el.find('.overlay').css('display', 'flex')
|
$el.find('.overlay').removeClass('hidden')
|
||||||
$rs.userSettings.changeAvatar($scope.avatarAttachment).then(onSuccess, onError)
|
$rs.userSettings.changeAvatar($scope.avatarAttachment).then(onSuccess, onError)
|
||||||
|
|
||||||
# Use gravatar photo
|
# Use gravatar photo
|
||||||
$el.on "click", "a.use-gravatar", (event) ->
|
$el.on "click", "a.use-gravatar", (event) ->
|
||||||
$el.find('.overlay').show()
|
$el.find('.overlay').removeClass('hidden')
|
||||||
$rs.userSettings.removeAvatar().then(onSuccess, onError)
|
$rs.userSettings.removeAvatar().then(onSuccess, onError)
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
|
|
|
@ -80,6 +80,9 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
@scope.$on "attachment:delete", =>
|
@scope.$on "attachment:delete", =>
|
||||||
@rootscope.$broadcast("history:reload")
|
@rootscope.$broadcast("history:reload")
|
||||||
|
|
||||||
|
@scope.$on "custom-attributes-values:edit", =>
|
||||||
|
@rootscope.$broadcast("history:reload")
|
||||||
|
|
||||||
initializeOnDeleteGoToUrl: ->
|
initializeOnDeleteGoToUrl: ->
|
||||||
ctx = {project: @scope.project.slug}
|
ctx = {project: @scope.project.slug}
|
||||||
@scope.onDeleteGoToUrl = @navUrls.resolve("project", ctx)
|
@scope.onDeleteGoToUrl = @navUrls.resolve("project", ctx)
|
||||||
|
|
|
@ -70,6 +70,9 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
|
|
||||||
loadProject: ->
|
loadProject: ->
|
||||||
return @rs.projects.getBySlug(@params.pslug).then (project) =>
|
return @rs.projects.getBySlug(@params.pslug).then (project) =>
|
||||||
|
if not project.is_wiki_activated
|
||||||
|
@location.path(@navUrls.resolve("permission-denied"))
|
||||||
|
|
||||||
@scope.projectId = project.id
|
@scope.projectId = project.id
|
||||||
@scope.project = project
|
@scope.project = project
|
||||||
@scope.$emit('project:loaded', project)
|
@scope.$emit('project:loaded', project)
|
||||||
|
@ -159,9 +162,6 @@ WikiSummaryDirective = ($log, $template) ->
|
||||||
return if not wikiPage
|
return if not wikiPage
|
||||||
render(wikiPage)
|
render(wikiPage)
|
||||||
|
|
||||||
$scope.$on "wiki:edit", (event, wikiPage) ->
|
|
||||||
render(wikiPage)
|
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
$el.off()
|
$el.off()
|
||||||
|
|
||||||
|
@ -215,8 +215,7 @@ EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $
|
||||||
if not wiki.id?
|
if not wiki.id?
|
||||||
$analytics.trackEvent("wikipage", "create", "create wiki page", 1)
|
$analytics.trackEvent("wikipage", "create", "create wiki page", 1)
|
||||||
|
|
||||||
$model.$modelValue = wikiPage
|
$model.$setViewValue wikiPage
|
||||||
$scope.$broadcast("wiki:edit", wikiPage)
|
|
||||||
|
|
||||||
$confirm.notify("success")
|
$confirm.notify("success")
|
||||||
switchToReadMode()
|
switchToReadMode()
|
||||||
|
@ -235,23 +234,16 @@ EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $
|
||||||
$loading.finish($el.find('.save-container'))
|
$loading.finish($el.find('.save-container'))
|
||||||
|
|
||||||
$el.on "mousedown", ".view-wiki-content", (event) ->
|
$el.on "mousedown", ".view-wiki-content", (event) ->
|
||||||
# Prepare the scroll movement detection
|
|
||||||
target = angular.element(event.target)
|
|
||||||
if target.is('pre')
|
|
||||||
target.data("scroll-pos", target[0].scrollLeft)
|
|
||||||
|
|
||||||
$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)
|
target = angular.element(event.target)
|
||||||
return if not isEditable()
|
return if not isEditable()
|
||||||
return if target.is('a')
|
return if event.button == 2
|
||||||
|
|
||||||
|
$el.on "mouseup", ".view-wiki-content", (event) ->
|
||||||
|
target = angular.element(event.target)
|
||||||
return if getSelectedText()
|
return if getSelectedText()
|
||||||
if target.is('pre')
|
return if not isEditable()
|
||||||
prevPos = target.data("scroll-pos")
|
return if target.is('a')
|
||||||
target.data("scroll-pos", null)
|
return if target.is('pre')
|
||||||
if prevPos != target[0].scrollLeft
|
|
||||||
return
|
|
||||||
|
|
||||||
switchToEditMode()
|
switchToEditMode()
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ div.wrapper.memberships(ng-controller="MembershipsController as ctrl",
|
||||||
include ../includes/components/mainTitle
|
include ../includes/components/mainTitle
|
||||||
|
|
||||||
.action-buttons
|
.action-buttons
|
||||||
a.button.button-green(title="Add new member" href="" ng-click="ctrl.addNewMembers()")
|
a.button-green(title="Add new member" href="" ng-click="ctrl.addNewMembers()")
|
||||||
span.text + New member
|
span.text + New member
|
||||||
|
|
||||||
include ../includes/modules/admin/admin-membership-table
|
include ../includes/modules/admin/admin-membership-table
|
||||||
|
|
|
@ -12,10 +12,15 @@ div.wrapper(ng-controller="ProjectProfileController as ctrl",
|
||||||
p.admin-subtitle Export your project to save a backup or to create a new one based on this.
|
p.admin-subtitle Export your project to save a backup or to create a new one based on this.
|
||||||
|
|
||||||
div.admin-project-export-buttons
|
div.admin-project-export-buttons
|
||||||
a.button.button-green.button-export(href="", title="Export your project") Export
|
a.button-green.button-export(href="", title="Export your project")
|
||||||
|
span Export
|
||||||
|
|
||||||
div.admin-project-export-result.hidden
|
div.admin-project-export-result.hidden
|
||||||
div.spin.hidden
|
div.spin.hidden
|
||||||
img(src="/svg/spinner-circle.svg", alt="loading...")
|
img(src="/svg/spinner-circle.svg", alt="loading...")
|
||||||
h3.result-title
|
h3.result-title
|
||||||
p.result-message
|
p.result-message
|
||||||
|
|
||||||
|
a.help-button(href="https://taiga.io/support/import-export-projects/", target="_blank")
|
||||||
|
span.icon.icon-help
|
||||||
|
span Do you need help? Check out our support page!
|
||||||
|
|
|
@ -87,5 +87,4 @@ div.wrapper(tg-project-modules, ng-controller="ProjectProfileController as ctrl"
|
||||||
option(value="") Select a videoconference system
|
option(value="") Select a videoconference system
|
||||||
input(type="text", ng-model="project.videoconferences_salt",
|
input(type="text", ng-model="project.videoconferences_salt",
|
||||||
placeholder="If you want you can append a salt code to the name of the chat room")
|
placeholder="If you want you can append a salt code to the name of the chat room")
|
||||||
button(type="submit", class="hidden")
|
button.button-green.submit-button(type="submit", title="Save") Save
|
||||||
a.button.button-green.submit-button(href="", title="Save") Save
|
|
||||||
|
|
|
@ -37,19 +37,18 @@ div.wrapper(tg-project-profile, ng-controller="ProjectProfileController as ctrl"
|
||||||
textarea(name="description", placeholder="Description", id="project-description",
|
textarea(name="description", placeholder="Description", id="project-description",
|
||||||
ng-model="project.description", data-required="true")
|
ng-model="project.description", data-required="true")
|
||||||
|
|
||||||
tg-privacy-settings-inputs
|
div
|
||||||
div.privacy-settings
|
div.privacy-settings
|
||||||
div
|
div
|
||||||
input.hidden(type="radio", disabled="disabled")
|
input.privacy-project(type="radio", name="private-project", ng-model="project.is_private", ng-value="false")
|
||||||
label.button(for="public-project") Public Project
|
label.trans-button(for="public-project")
|
||||||
|
span Public Project
|
||||||
div
|
div
|
||||||
input.hidden(type="radio", checked="checked", disabled="disabled")
|
input.privacy-project(type="radio", name="private-project", ng-model="project.is_private", ng-value="true")
|
||||||
label.button(for="private-project") Private Project
|
label.trans-button(for="private-project")
|
||||||
|
span Private Project
|
||||||
|
|
||||||
p All projects are private during Taiga's beta period.
|
button.button-green.submit-button(type="submit", title="Save") Save
|
||||||
|
|
||||||
button(type="submit", class="hidden")
|
|
||||||
a.button.button-green.submit-button(href="", title="Save") 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(tg-lb-delete-project)
|
div.lightbox.lightbox-delete-project(tg-lb-delete-project)
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
div.wrapper(ng-controller="ProjectProfileController as ctrl",
|
||||||
|
ng-init="section='admin'; sectionName='Reports'")
|
||||||
|
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile")
|
||||||
|
include ../includes/modules/admin-menu
|
||||||
|
|
||||||
|
sidebar.menu-tertiary.sidebar(tg-admin-navigation="reports")
|
||||||
|
include ../includes/modules/admin-submenu-project-profile
|
||||||
|
|
||||||
|
section.main.admin-common(tg-project-export)
|
||||||
|
header
|
||||||
|
include ../includes/components/mainTitle
|
||||||
|
p.admin-subtitle Export your project data in CSV format and make your own reports.
|
||||||
|
|
||||||
|
p Download a CSV file or copy the generated URL and open it in your favourite text editor or spreadsheet to make your own project data reports. You will be able to visualize and analize all your data easily.
|
||||||
|
|
||||||
|
- var csvType = "US";
|
||||||
|
- var controller = "CsvExporterUserstoriesController";
|
||||||
|
div.admin-attributes-section
|
||||||
|
include ../includes/modules/admin/project-csv
|
||||||
|
|
||||||
|
- var csvType = "Task";
|
||||||
|
- var controller = "CsvExporterTasksController";
|
||||||
|
div.admin-attributes-section
|
||||||
|
include ../includes/modules/admin/project-csv
|
||||||
|
|
||||||
|
- var csvType = "Issues";
|
||||||
|
- var controller = "CsvExporterIssuesController";
|
||||||
|
div.admin-attributes-section
|
||||||
|
include ../includes/modules/admin/project-csv
|
||||||
|
a.help-button(href="https://taiga.io/support/csv-reports/", target="_blank")
|
||||||
|
span.icon.icon-help
|
||||||
|
span How to use this on my own spreadsheet?
|
|
@ -0,0 +1,31 @@
|
||||||
|
div.wrapper(ng-controller="ProjectValuesSectionController")
|
||||||
|
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values")
|
||||||
|
include ../includes/modules/admin-menu
|
||||||
|
|
||||||
|
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-custom-fields")
|
||||||
|
include ../includes/modules/admin-submenu-project-values
|
||||||
|
|
||||||
|
section.main.admin-common.admin-attributes
|
||||||
|
include ../includes/components/mainTitle
|
||||||
|
p.admin-subtitle Specify the custom fields for your user stories, tasks and issues
|
||||||
|
|
||||||
|
div.admin-attributes-section(tg-project-custom-attributes,
|
||||||
|
ng-controller="ProjectCustomAttributesController as ctrl",
|
||||||
|
ng-init="type='userstory';")
|
||||||
|
- var customFieldSectionTitle = "User stories custom fields"
|
||||||
|
- var customFieldButtonTitle = "Add a custom field in user stories"
|
||||||
|
include ../includes/modules/admin/admin-custom-attributes
|
||||||
|
|
||||||
|
div.admin-attributes-section(tg-project-custom-attributes,
|
||||||
|
ng-controller="ProjectCustomAttributesController as ctrl",
|
||||||
|
ng-init="type='task';")
|
||||||
|
- var customFieldSectionTitle = "Tasks custom fields"
|
||||||
|
- var customFieldButtonTitle = "Add a custom field in tasks"
|
||||||
|
include ../includes/modules/admin/admin-custom-attributes
|
||||||
|
|
||||||
|
div.admin-attributes-section(tg-project-custom-attributes,
|
||||||
|
ng-controller="ProjectCustomAttributesController as ctrl",
|
||||||
|
ng-init="type='issue';")
|
||||||
|
- var customFieldSectionTitle = "Issues custom fields"
|
||||||
|
- var customFieldButtonTitle = "Add a custom field in issues"
|
||||||
|
include ../includes/modules/admin/admin-custom-attributes
|
|
@ -1,18 +0,0 @@
|
||||||
div.wrapper(tg-project-values, ng-controller="ProjectValuesController as ctrl",
|
|
||||||
ng-init="section='admin'; resource='issues'; type='priorities'; sectionName='Issue Priorities'",
|
|
||||||
type="priorities")
|
|
||||||
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values")
|
|
||||||
include ../includes/modules/admin-menu
|
|
||||||
|
|
||||||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-priorities")
|
|
||||||
include ../includes/modules/admin-submenu-project-values
|
|
||||||
|
|
||||||
section.main.admin-common
|
|
||||||
include ../includes/components/mainTitle
|
|
||||||
p.admin-subtitle Specify the priority levels users can assign to issues
|
|
||||||
|
|
||||||
div.project-values-options
|
|
||||||
a.button.button-green.show-add-new(href="", title="Add New")
|
|
||||||
span Add new priority
|
|
||||||
|
|
||||||
include ../includes/modules/admin/project-types
|
|
|
@ -1,18 +0,0 @@
|
||||||
div.wrapper(tg-project-values, ng-controller="ProjectValuesController as ctrl",
|
|
||||||
ng-init="section='admin'; resource='issues'; type='severities'; sectionName='Issue severities'",
|
|
||||||
type="severities")
|
|
||||||
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values")
|
|
||||||
include ../includes/modules/admin-menu
|
|
||||||
|
|
||||||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-severities")
|
|
||||||
include ../includes/modules/admin-submenu-project-values
|
|
||||||
|
|
||||||
section.main.admin-common
|
|
||||||
include ../includes/components/mainTitle
|
|
||||||
p.admin-subtitle Specify the severity level users can select to classify issues
|
|
||||||
|
|
||||||
div.project-values-options
|
|
||||||
a.button.button-green.show-add-new(href="", title="Add New")
|
|
||||||
span Add new severity
|
|
||||||
|
|
||||||
include ../includes/modules/admin/project-types
|
|
|
@ -1,18 +0,0 @@
|
||||||
div.wrapper(tg-project-values, ng-controller="ProjectValuesController as ctrl",
|
|
||||||
ng-init="section='admin'; resource='issues'; type='issue-statuses'; sectionName='Issue Statuses'",
|
|
||||||
type="issue-statuses")
|
|
||||||
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values")
|
|
||||||
include ../includes/modules/admin-menu
|
|
||||||
|
|
||||||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-issue-status")
|
|
||||||
include ../includes/modules/admin-submenu-project-values
|
|
||||||
|
|
||||||
section.main.admin-common
|
|
||||||
include ../includes/components/mainTitle
|
|
||||||
p.admin-subtitle Specify the column headers that you will use to classify Issues
|
|
||||||
|
|
||||||
div.project-values-options
|
|
||||||
a.button.button-green.show-add-new(href="", title="Add New")
|
|
||||||
span Add new status
|
|
||||||
|
|
||||||
include ../includes/modules/admin/project-status
|
|
|
@ -1,18 +0,0 @@
|
||||||
div.wrapper(tg-project-values, ng-controller="ProjectValuesController as ctrl",
|
|
||||||
ng-init="section='admin'; resource='issues'; type='issue-types'; sectionName='Issue Types'",
|
|
||||||
type="issue-types")
|
|
||||||
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values")
|
|
||||||
include ../includes/modules/admin-menu
|
|
||||||
|
|
||||||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-issue-types")
|
|
||||||
include ../includes/modules/admin-submenu-project-values
|
|
||||||
|
|
||||||
section.main.admin-common
|
|
||||||
include ../includes/components/mainTitle
|
|
||||||
p.admin-subtitle Specify the categories users can select to classify issues
|
|
||||||
|
|
||||||
div.project-values-options
|
|
||||||
a.button.button-green.show-add-new(href="", title="Add New")
|
|
||||||
span Add new type
|
|
||||||
|
|
||||||
include ../includes/modules/admin/project-types
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
div.wrapper(ng-controller="ProjectValuesSectionController")
|
||||||
|
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values")
|
||||||
|
include ../includes/modules/admin-menu
|
||||||
|
|
||||||
|
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-points")
|
||||||
|
include ../includes/modules/admin-submenu-project-values
|
||||||
|
|
||||||
|
section.main.admin-common.admin-attributes
|
||||||
|
include ../includes/components/mainTitle
|
||||||
|
p.admin-subtitle Specify the points your user stories could be estimated to
|
||||||
|
|
||||||
|
div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl",
|
||||||
|
ng-init="section='admin'; resource='userstories'; type='points'; sectionName='Us points'",
|
||||||
|
type="points")
|
||||||
|
include ../includes/modules/admin/project-points
|
|
@ -0,0 +1,15 @@
|
||||||
|
div.wrapper(ng-controller="ProjectValuesSectionController")
|
||||||
|
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values")
|
||||||
|
include ../includes/modules/admin-menu
|
||||||
|
|
||||||
|
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-priorities")
|
||||||
|
include ../includes/modules/admin-submenu-project-values
|
||||||
|
|
||||||
|
section.main.admin-common.admin-attributes
|
||||||
|
include ../includes/components/mainTitle
|
||||||
|
p.admin-subtitle Specify the priorities your issues will have
|
||||||
|
|
||||||
|
div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl",
|
||||||
|
ng-init="section='admin'; resource='issues'; type='priorities'; sectionName='Issue priorities'; objName='priority'",
|
||||||
|
type="priorities")
|
||||||
|
include ../includes/modules/admin/project-types
|
|
@ -0,0 +1,15 @@
|
||||||
|
div.wrapper(ng-controller="ProjectValuesSectionController")
|
||||||
|
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values")
|
||||||
|
include ../includes/modules/admin-menu
|
||||||
|
|
||||||
|
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-severities")
|
||||||
|
include ../includes/modules/admin-submenu-project-values
|
||||||
|
|
||||||
|
section.main.admin-common.admin-attributes
|
||||||
|
include ../includes/components/mainTitle
|
||||||
|
p.admin-subtitle Specify the severities your issues will have
|
||||||
|
|
||||||
|
div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl",
|
||||||
|
ng-init="section='admin'; resource='issues'; type='severities'; sectionName='Issue severities'; objName='severity'",
|
||||||
|
type="severities")
|
||||||
|
include ../includes/modules/admin/project-types
|
|
@ -0,0 +1,25 @@
|
||||||
|
div.wrapper(ng-controller="ProjectValuesSectionController")
|
||||||
|
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values")
|
||||||
|
include ../includes/modules/admin-menu
|
||||||
|
|
||||||
|
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-status")
|
||||||
|
include ../includes/modules/admin-submenu-project-values
|
||||||
|
|
||||||
|
section.main.admin-common.admin-attributes
|
||||||
|
include ../includes/components/mainTitle
|
||||||
|
p.admin-subtitle Specify the statuses your user stories, tasks and issues will go through
|
||||||
|
|
||||||
|
div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl",
|
||||||
|
ng-init="section='admin'; resource='userstories'; type='userstory-statuses'; sectionName='Us Statuses'",
|
||||||
|
type="userstory-statuses")
|
||||||
|
include ../includes/modules/admin/project-us-status
|
||||||
|
|
||||||
|
div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl",
|
||||||
|
ng-init="section='admin'; resource='tasks'; type='task-statuses'; sectionName='Task Statuses'",
|
||||||
|
type="task-statuses")
|
||||||
|
include ../includes/modules/admin/project-status
|
||||||
|
|
||||||
|
div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl",
|
||||||
|
ng-init="section='admin'; resource='issues'; type='issue-statuses'; sectionName='Issue Statuses'",
|
||||||
|
type="issue-statuses")
|
||||||
|
include ../includes/modules/admin/project-status
|
|
@ -1,18 +0,0 @@
|
||||||
div.wrapper(tg-project-values, ng-controller="ProjectValuesController as ctrl",
|
|
||||||
ng-init="section='admin'; resource='tasks'; type='task-statuses'; sectionName='Task Statuses'",
|
|
||||||
type="task-statuses")
|
|
||||||
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values")
|
|
||||||
include ../includes/modules/admin-menu
|
|
||||||
|
|
||||||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-task-status")
|
|
||||||
include ../includes/modules/admin-submenu-project-values
|
|
||||||
|
|
||||||
section.main.admin-common
|
|
||||||
include ../includes/components/mainTitle
|
|
||||||
p.admin-subtitle Specify the column headers that you will use to classify Tasks related to each User Stories
|
|
||||||
|
|
||||||
div.project-values-options
|
|
||||||
a.button.button-green.show-add-new(href="", title="Add New")
|
|
||||||
span Add new status
|
|
||||||
|
|
||||||
include ../includes/modules/admin/project-status
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
div.wrapper(ng-controller="ProjectValuesSectionController")
|
||||||
|
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values")
|
||||||
|
include ../includes/modules/admin-menu
|
||||||
|
|
||||||
|
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-types")
|
||||||
|
include ../includes/modules/admin-submenu-project-values
|
||||||
|
|
||||||
|
section.main.admin-common.admin-attributes
|
||||||
|
include ../includes/components/mainTitle
|
||||||
|
p.admin-subtitle Specify the types your user stories could be estimated to
|
||||||
|
|
||||||
|
div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl",
|
||||||
|
ng-init="section='admin'; resource='issues'; type='issue-types'; sectionName='Issue types'; objName='type'",
|
||||||
|
type="issue-types")
|
||||||
|
include ../includes/modules/admin/project-types
|
|
@ -1,24 +0,0 @@
|
||||||
div.wrapper(tg-project-values, ng-controller="ProjectValuesController as ctrl",
|
|
||||||
ng-init="section='admin'; resource='userstories'; type='points'; sectionName='Us points'",
|
|
||||||
type="points")
|
|
||||||
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values")
|
|
||||||
include ../includes/modules/admin-menu
|
|
||||||
|
|
||||||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-us-points")
|
|
||||||
include ../includes/modules/admin-submenu-project-values
|
|
||||||
|
|
||||||
section.main.admin-common
|
|
||||||
include ../includes/components/mainTitle
|
|
||||||
p.admin-subtitle Specify the numerical system you will use to indicate the level of difficulty for each User Story
|
|
||||||
|
|
||||||
- var helpLightboxId = "notion-admin-project-values-us-points"
|
|
||||||
include ../includes/components/help-notion-button
|
|
||||||
|
|
||||||
div.project-values-options
|
|
||||||
a.button.button-green.show-add-new(href="", title="Add New")
|
|
||||||
span Add new point
|
|
||||||
|
|
||||||
include ../includes/modules/admin/project-points
|
|
||||||
|
|
||||||
div.lightbox.lightbox-generic-notion.notion-admin-project-values-us-points(id="notion-admin-project-values-us-points", tg-lb-notion)
|
|
||||||
include ../includes/modules/help-notions/lightbox-notion-admin-project-values-us-points
|
|
|
@ -1,18 +0,0 @@
|
||||||
div.wrapper(tg-project-values, ng-controller="ProjectValuesController as ctrl",
|
|
||||||
ng-init="section='admin'; resource='userstories'; type='userstory-statuses'; sectionName='Us Statuses'",
|
|
||||||
type="userstory-statuses")
|
|
||||||
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values")
|
|
||||||
include ../includes/modules/admin-menu
|
|
||||||
|
|
||||||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-us-status")
|
|
||||||
include ../includes/modules/admin-submenu-project-values
|
|
||||||
|
|
||||||
section.main.admin-common
|
|
||||||
include ../includes/components/mainTitle
|
|
||||||
p.admin-subtitle Specify the column headers that you will use to classify User Stories
|
|
||||||
|
|
||||||
div.project-values-options
|
|
||||||
a.button.button-green.show-add-new(href="", title="Add New")
|
|
||||||
span Add new status
|
|
||||||
|
|
||||||
include ../includes/modules/admin/project-us-status
|
|
|
@ -8,10 +8,12 @@ div.wrapper.roles(ng-controller="RolesController as ctrl",
|
||||||
section.main.admin-roles.admin-common
|
section.main.admin-roles.admin-common
|
||||||
.header-with-actions
|
.header-with-actions
|
||||||
include ../includes/components/mainTitle
|
include ../includes/components/mainTitle
|
||||||
.action-buttons
|
.action-buttons(ng-if="!role.external_user")
|
||||||
a.button.button-red.delete-role(href="", title="Delete", ng-click="ctrl.delete()") Delete
|
a.button-red.delete-role(href="", title="Delete", ng-click="ctrl.delete()")
|
||||||
|
span Delete
|
||||||
|
|
||||||
|
|
||||||
|
div(ng-if="!role.external_user")
|
||||||
div(tg-edit-role)
|
div(tg-edit-role)
|
||||||
.edit-role
|
.edit-role
|
||||||
input(type="text", value="{{ role.name }}")
|
input(type="text", value="{{ role.name }}")
|
||||||
|
@ -31,4 +33,8 @@ div.wrapper.roles(ng-controller="RolesController as ctrl",
|
||||||
span.check-text.check-yes Yes
|
span.check-text.check-yes Yes
|
||||||
span.check-text.check-no No
|
span.check-text.check-no No
|
||||||
|
|
||||||
|
div(ng-if="role.external_user")
|
||||||
|
p.total
|
||||||
|
span.role-name {{ role.name }}
|
||||||
|
|
||||||
div(tg-role-permissions, ng-model="role")
|
div(tg-role-permissions, ng-model="role")
|
||||||
|
|
|
@ -27,9 +27,7 @@ div.wrapper.roles(tg-bitbucket-webhooks, ng-controller="BitbucketController as c
|
||||||
label(for="valid-origin-ips") Valid origin ips (separated by ,)
|
label(for="valid-origin-ips") Valid origin ips (separated by ,)
|
||||||
input(type="text", name="valid-origin-ips", tg-valid-origin-ips, ng-model="bitbucket.valid_origin_ips", placeholder="Bitbucket requests are not signed so the best way of verifying the origin is by IP. If the field is empty there will be no IP validation.", id="valid-origin-ips")
|
input(type="text", name="valid-origin-ips", tg-valid-origin-ips, ng-model="bitbucket.valid_origin_ips", placeholder="Bitbucket requests are not signed so the best way of verifying the origin is by IP. If the field is empty there will be no IP validation.", id="valid-origin-ips")
|
||||||
|
|
||||||
button(type="submit", class="hidden")
|
button.button-green.submit-button(type="submit", title="Save") Save
|
||||||
a.button.button-green.submit-button(href="", title="Save") Save
|
|
||||||
|
|
||||||
|
|
||||||
a.help-button(href="https://taiga.io/support/bitbucket-integration/", target="_blank")
|
a.help-button(href="https://taiga.io/support/bitbucket-integration/", target="_blank")
|
||||||
span.icon.icon-help
|
span.icon.icon-help
|
||||||
|
|
|
@ -23,9 +23,7 @@ div.wrapper.roles(tg-github-webhooks, ng-controller="GithubController as ctrl",
|
||||||
.icon.icon-copy
|
.icon.icon-copy
|
||||||
.help-copy Copy to clipboard: Ctrl+C
|
.help-copy Copy to clipboard: Ctrl+C
|
||||||
|
|
||||||
button(type="submit", class="hidden")
|
button.button-green.submit-button(type="submit", title="Save") Save
|
||||||
a.button.button-green.submit-button(href="", title="Save") Save
|
|
||||||
|
|
||||||
|
|
||||||
a.help-button(href="https://taiga.io/support/github-integration/", target="_blank")
|
a.help-button(href="https://taiga.io/support/github-integration/", target="_blank")
|
||||||
span.icon.icon-help
|
span.icon.icon-help
|
||||||
|
|
|
@ -31,8 +31,7 @@ block content
|
||||||
label(for="valid-origin-ips") Valid origin ips (separated by ,)
|
label(for="valid-origin-ips") Valid origin ips (separated by ,)
|
||||||
input(type="text", name="valid-origin-ips", tg-valid-origin-ips, ng-model="gitlab.valid_origin_ips", placeholder="Gitlab requests are not signed so the best way of verifying the origin is by IP. If the field is empty there will be no IP validation.", id="valid-origin-ips")
|
input(type="text", name="valid-origin-ips", tg-valid-origin-ips, ng-model="gitlab.valid_origin_ips", placeholder="Gitlab requests are not signed so the best way of verifying the origin is by IP. If the field is empty there will be no IP validation.", id="valid-origin-ips")
|
||||||
|
|
||||||
button(type="submit", class="hidden")
|
button.button-green.submit-button(type="submit", title="Save") Save
|
||||||
a.button.button-green.submit-button(href="", title="Save") Save
|
|
||||||
|
|
||||||
a.help-button(href="https://taiga.io/support/gitlab-integration/", target="_blank")
|
a.help-button(href="https://taiga.io/support/gitlab-integration/", target="_blank")
|
||||||
span.icon.icon-help
|
span.icon.icon-help
|
||||||
|
|
|
@ -14,7 +14,7 @@ block content
|
||||||
|
|
||||||
p.admin-subtitle Webhooks notify external services about events in Taiga, like comments, user stories....
|
p.admin-subtitle Webhooks notify external services about events in Taiga, like comments, user stories....
|
||||||
div.webhooks-options
|
div.webhooks-options
|
||||||
a.button.button-green.hidden.add-webhook(href="",title="Add a New Webhook") Add Webhook
|
a.button-green.hidden.add-webhook(href="",title="Add a New Webhook") Add Webhook
|
||||||
|
|
||||||
section.webhooks-table.basic-table
|
section.webhooks-table.basic-table
|
||||||
div.table-header
|
div.table-header
|
||||||
|
|
|
@ -11,7 +11,7 @@ section.attachments
|
||||||
input(id="add-attach", type="file", multiple="multiple")
|
input(id="add-attach", type="file", multiple="multiple")
|
||||||
|
|
||||||
.attachment-body.sortable
|
.attachment-body.sortable
|
||||||
.single-attachment(ng-repeat="attach in ctrl.attachments|filter:ctrl.filterAttachments track by attach.id" tg-attachment="attach")
|
.single-attachment(ng-repeat="attach in ctrl.attachments|filter:ctrl.filterAttachments track by attach.id" tg-attachment="attach", tg-bind-scope)
|
||||||
|
|
||||||
.single-attachment(ng-repeat="file in ctrl.uploadingAttachments")
|
.single-attachment(ng-repeat="file in ctrl.uploadingAttachments")
|
||||||
.attachment-name
|
.attachment-name
|
||||||
|
|
|
@ -9,6 +9,7 @@ div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl",
|
||||||
div.burndown(tg-gm-backlog-graph)
|
div.burndown(tg-gm-backlog-graph)
|
||||||
include ../includes/modules/burndown
|
include ../includes/modules/burndown
|
||||||
div.backlog-menu
|
div.backlog-menu
|
||||||
|
div.backlog-table-options
|
||||||
a.trans-button.move-to-current-sprint(href="", title="Move to Current Sprint",
|
a.trans-button.move-to-current-sprint(href="", title="Move to Current Sprint",
|
||||||
id="move-to-current-sprint")
|
id="move-to-current-sprint")
|
||||||
span.icon.icon-move
|
span.icon.icon-move
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
ul.popover.pop-points-open
|
|
||||||
<% _.each(points, function(point) { %>
|
|
||||||
li
|
|
||||||
<% if (point.selected) { %>
|
|
||||||
a.point(href="", title!="<%- point.name %>", data-point-id!="<%- point.id %>")
|
|
||||||
| <%- point.name %>
|
|
||||||
<% } else { %>
|
|
||||||
a.point.active(href="", title!="<%- point.name %>", data-point-id!="<%- point.id %>")
|
|
||||||
| <%- point.name %>
|
|
||||||
<% } %>
|
|
||||||
<% }); %>
|
|
|
@ -1,2 +1,4 @@
|
||||||
a(href="#", class="button button-gray item-block") Block
|
a(href="#", class="button button-gray item-block")
|
||||||
a(href="#", class="button button-red item-unblock") Unblock
|
span Block
|
||||||
|
a(href="#", class="button button-red item-unblock")
|
||||||
|
span Unblock
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
a(href="", class="button button-red") Delete
|
a(href="", class="button button-red")
|
||||||
|
span Delete
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
<% if(watcher) { %>
|
<% if(watcher) { %>
|
||||||
.watcher-single
|
.watcher-single
|
||||||
.watcher-avatar
|
.watcher-avatar
|
||||||
span.avatar(title!="<%- watcher.full_name_display %>")
|
|
||||||
img(src!="<%- watcher.photo %>" alt!="<%- watcher.full_name_display %>")
|
img(src!="<%- watcher.photo %>" alt!="<%- watcher.full_name_display %>")
|
||||||
.watcher-name
|
.watcher-name
|
||||||
span <%- watcher.full_name_display %>
|
span <%- watcher.full_name_display %>
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
ul.points-per-role
|
|
||||||
li.total
|
|
||||||
span.points <%- totalPoints %>
|
|
||||||
span.role total
|
|
||||||
<% _.each(roles, function(role) { %>
|
|
||||||
li.total.clickable(data-role-id!="<%- role.id %>")
|
|
||||||
span.points <%- role.points %>
|
|
||||||
span.role <%- role.name %>
|
|
||||||
<% }); %>
|
|
|
@ -1,13 +0,0 @@
|
||||||
ul.popover.pop-points-open
|
|
||||||
<% _.each(points, function(point) { %>
|
|
||||||
li
|
|
||||||
<% if (point.selected) { %>
|
|
||||||
a(href="", class="point", title!="<%- point.name %>",
|
|
||||||
data-point-id!="<%- point.id %>", data-role-id!="<%- roleId %>")
|
|
||||||
| <%- point.name %>
|
|
||||||
<% } else { %>
|
|
||||||
a(href="", class="point active", title!="<%- point.name %>",
|
|
||||||
data-point-id!="<%- point.id %>" data-role-id!="<%- roleId %>")
|
|
||||||
| <%- point.name %>
|
|
||||||
<% } %>
|
|
||||||
<% }); %>
|
|
|
@ -3,7 +3,7 @@ ul.points-per-role
|
||||||
span.points <%- totalPoints %>
|
span.points <%- totalPoints %>
|
||||||
span.role total
|
span.role total
|
||||||
<% _.each(roles, function(role) { %>
|
<% _.each(roles, function(role) { %>
|
||||||
li(class!="total <% if(editable){ %>clickable<% } %>", data-role-id!="<%- role.id %>")
|
li(class!="total <% if(editable){ %>clickable<% } %>", data-role-id!="<%- role.id %>", title!="<%- role.name %>")
|
||||||
span.points <%- role.points %>
|
span.points <%- role.points %>
|
||||||
span.role <%- role.name %>
|
span.role <%- role.name %>
|
||||||
<% }); %>
|
<% }); %>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
ul.popover.pop-points-open
|
ul.popover.pop-points-open(class!="<% if (horizontal) { %>horizontal<% }; %>")
|
||||||
<% _.each(points, function(point) { %>
|
<% _.each(points, function(point) { %>
|
||||||
li
|
li
|
||||||
<% if (point.selected) { %>
|
<% if (point.selected) { %>
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
a.us-points(href="", title!="<%= title %>", class!="<% if (!editable) { %>not-clickable<% } %>")
|
||||||
|
span.points-value <%= text %>
|
||||||
|
<% if (editable) { %>
|
||||||
|
span.icon.icon-arrow-bottom(tg-check-permission="modify_us")
|
||||||
|
<% } %>
|
|
@ -11,13 +11,13 @@ section.history
|
||||||
section.history-comments
|
section.history-comments
|
||||||
.comments-list
|
.comments-list
|
||||||
div(tg-check-permission!="modify_<%- type %>", tg-toggle-comment, class="add-comment")
|
div(tg-check-permission!="modify_<%- type %>", tg-toggle-comment, class="add-comment")
|
||||||
textarea(placeholder="Type a new comment here",
|
textarea(placeholder="Type a new comment here", ng-model!="<%- ngmodel %>.comment", tg-markitup="tg-markitup")
|
||||||
ng-model!="<%- ngmodel %>.comment", tg-markitup="tg-markitup")
|
|
||||||
<% if (mode !== "edit") { %>
|
<% if (mode !== "edit") { %>
|
||||||
a(class="help-markdown", href="https://taiga.io/support/taiga-markdown-syntax/", target="_blank", title="Mardown syntax help")
|
a(class="help-markdown", href="https://taiga.io/support/taiga-markdown-syntax/", target="_blank", title="Mardown syntax help")
|
||||||
span.icon.icon-help
|
span.icon.icon-help
|
||||||
span Markdown syntax help
|
span Markdown syntax help
|
||||||
a(href="", title="Comment", class="button button-green save-comment") Comment
|
a(href="", title="Comment", class="button button-green save-comment")
|
||||||
|
span Comment
|
||||||
<% } %>
|
<% } %>
|
||||||
section.history-activity.hidden
|
section.history-activity.hidden
|
||||||
.changes-list
|
.changes-list
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
.change-entry
|
||||||
|
.activity-changed
|
||||||
|
span <%- name %>
|
||||||
|
.activity-fromto
|
||||||
|
<% if (removed.length > 0) { %>
|
||||||
|
p
|
||||||
|
strong removed
|
||||||
|
br
|
||||||
|
span <%- removed %>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
|
<% if (added.length > 0) { %>
|
||||||
|
p
|
||||||
|
strong added
|
||||||
|
br
|
||||||
|
span <%- added %>
|
||||||
|
<% } %>
|
|
@ -5,5 +5,5 @@ div.form
|
||||||
fieldset
|
fieldset
|
||||||
textarea.reason(placeholder="Please explain the reason")
|
textarea.reason(placeholder="Please explain the reason")
|
||||||
|
|
||||||
a.button.button-green(href="")
|
a.button-green(href="")
|
||||||
span Save
|
span Save
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
form.custom-field-single.editable
|
||||||
|
div.custom-field-data
|
||||||
|
label.custom-field-name(for="custom-field-description")
|
||||||
|
<%- name %>
|
||||||
|
<% if (description){ %>
|
||||||
|
span.custom-field-description
|
||||||
|
<%- description %>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
|
div.custom-field-value
|
||||||
|
input#custom-field-description(name="description", type="text", value!="<%- value %>")
|
||||||
|
|
||||||
|
div.custom-field-options
|
||||||
|
a.icon.icon-floppy(href="", title="Save Custom Field")
|
|
@ -0,0 +1,17 @@
|
||||||
|
div.custom-field-single
|
||||||
|
div.custom-field-data
|
||||||
|
span.custom-field-name
|
||||||
|
<%- name %>
|
||||||
|
<% if (description){ %>
|
||||||
|
span.custom-field-description
|
||||||
|
<%- description %>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
|
div.custom-field-value.read-mode
|
||||||
|
span
|
||||||
|
<%- value %>
|
||||||
|
|
||||||
|
<% if (isEditable) { %>
|
||||||
|
div.custom-field-options
|
||||||
|
a.icon.icon-edit(href="", title="Edit Custom Field")
|
||||||
|
<% } %>
|
|
@ -0,0 +1,7 @@
|
||||||
|
section.duty-custom-fields(ng-show="ctrl.customAttributes.length")
|
||||||
|
div.custom-fields-header
|
||||||
|
span Custom Fields
|
||||||
|
// Remove .open class on click on this button in both .icon and .custom-fields-body to close
|
||||||
|
a.icon.icon-arrow-bottom(class!="<% if (!collapsed) { %>open<% } %>")
|
||||||
|
div.custom-fields-body(class!="<% if (!collapsed) { %>open<% } %>")
|
||||||
|
div(ng-repeat="att in ctrl.customAttributes", tg-custom-attribute-value="ctrl.getAttributeValue(att)", required-edition-perm!="<%- requiredEditionPerm %>")
|
|
@ -1,9 +1,9 @@
|
||||||
div.new-us
|
div.new-us
|
||||||
a.button.button-green(href="", title="Add a new User Story",
|
a.button-green(href="", title="Add a new User Story",
|
||||||
ng-click="ctrl.addNewUs('standard')",
|
ng-click="ctrl.addNewUs('standard')",
|
||||||
tg-check-permission="add_us")
|
tg-check-permission="add_us")
|
||||||
span.text + Add a new User Story
|
span.text + Add a new User Story
|
||||||
a.button.button-bulk(href="", title="Add some new User Stories in bulk",
|
a.button-bulk(href="", title="Add some new User Stories in bulk",
|
||||||
ng-click="ctrl.addNewUs('bulk')",
|
ng-click="ctrl.addNewUs('bulk')",
|
||||||
tg-check-permission="add_us")
|
tg-check-permission="add_us")
|
||||||
span.icon.icon-bulk
|
span.icon.icon-bulk
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
div.row.us-item-row(ng-repeat="us in visibleUserstories track by us.id", tg-draggable, ng-class="{blocked: us.is_blocked}")
|
div.row.us-item-row(ng-repeat="us in visibleUserstories track by us.id", tg-bind-scope, ng-class="{blocked: us.is_blocked}", tg-class-permission="{'readonly': '!modify_us'}")
|
||||||
div.user-stories
|
div.user-stories
|
||||||
div.tags-block(tg-colorize-tags="us.tags", tg-colorize-tags-type="backlog")
|
div.tags-block(tg-colorize-tags="us.tags", tg-colorize-tags-type="backlog")
|
||||||
div.user-story-name
|
div.user-story-name
|
||||||
|
@ -20,7 +20,5 @@ div.row.us-item-row(ng-repeat="us in visibleUserstories track by us.id", tg-drag
|
||||||
|
|
||||||
div.points(tg-backlog-us-points="us")
|
div.points(tg-backlog-us-points="us")
|
||||||
a.us-points(href="", title="Points")
|
a.us-points(href="", title="Points")
|
||||||
span.points-value 0
|
|
||||||
span.icon.icon-arrow-bottom(tg-check-permission="modify_us")
|
|
||||||
|
|
||||||
a.icon.icon-drag-v(tg-check-permission="modify_us", href="", title="Drag")
|
a.icon.icon-drag-v(tg-check-permission="modify_us", href="", title="Drag")
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
//- NOTE: - Add a lightbox-notion at the end of parent template. Ex:
|
|
||||||
//-
|
|
||||||
//- div.hide.lightbox.lightbox-generic-notion(id="notion-admin-project-values-us-points",
|
|
||||||
//- tg-lb-notion)
|
|
||||||
//- include views/modules/help-notions/lightbox-notion-admin-project-values-us-points
|
|
||||||
//-
|
|
||||||
//- - Defined variable 'helpLightboxId' in parent template. Ex:
|
|
||||||
//-
|
|
||||||
//- - var helpLightboxId = "admin-project-values-us-points"
|
|
||||||
//- include views/components/help
|
|
||||||
|
|
||||||
a.icon.icon-idea.help(href="", title="You need some help?", tg-lb-notion-button=helpLightboxId)
|
|
|
@ -1,23 +0,0 @@
|
||||||
div.kanban-tagline(tg-colorize-tags="us.tags", tg-colorize-tags-type="kanban", ng-hide="us.isArchived")
|
|
||||||
div.kanban-task-inner(ng-class="{'task-archived': us.isArchived}")
|
|
||||||
div.avatar-wrapper(tg-kanban-user-avatar="us.assigned_to", ng-model="us", ng-hide="us.isArchived")
|
|
||||||
div.task-text(ng-hide="us.isArchived")
|
|
||||||
a.task-assigned(href="", title="Assign User Story")
|
|
||||||
span.task-num(tg-bo-ref="us.ref")
|
|
||||||
a.task-name(href="", title="See user story detail", ng-bind="us.subject",
|
|
||||||
tg-nav="project-userstories-detail:project=project.slug,ref=us.ref")
|
|
||||||
|
|
||||||
p.task-points(href="", title="Total Us points")
|
|
||||||
span(ng-if="us.total_points !== null", ng-bind="us.total_points")
|
|
||||||
span(ng-if="us.total_points !== null") points
|
|
||||||
span(ng-if="us.total_points === null") Not estimated
|
|
||||||
|
|
||||||
div.task-archived-text(ng-show="us.isArchived")
|
|
||||||
p You have archived
|
|
||||||
p
|
|
||||||
span.task-num(tg-bo-ref="us.ref")
|
|
||||||
span.task-name(ng-bind="us.subject")
|
|
||||||
p Drag & drop again to undo
|
|
||||||
|
|
||||||
a.icon.icon-edit(tg-check-permission="modify_us", href="", title="Edit", ng-hide="us.isArchived")
|
|
||||||
a.icon.icon-drag-h(tg-check-permission="modify_us", href="", title="Drag&Drop", ng-hide="us.isArchived")
|
|
|
@ -1,37 +1,35 @@
|
||||||
div.summary.large-summary
|
div.summary.large-summary
|
||||||
|
div.large-summary-wrapper
|
||||||
div
|
div
|
||||||
div.summary-progress-bar
|
div.summary-progress-bar
|
||||||
div.current-progress
|
div.current-progress
|
||||||
div.data
|
div.data
|
||||||
span.number 30%
|
span.number 30%
|
||||||
span.description completed
|
span.description completed
|
||||||
ul
|
div.summary-stats
|
||||||
li
|
|
||||||
span.number 12
|
span.number 12
|
||||||
span.description project<br />points
|
span.description project<br />points
|
||||||
li
|
div.summary-stats
|
||||||
span.number 23
|
span.number 23
|
||||||
span.description defined<br />points
|
span.description defined<br />points
|
||||||
li
|
div.summary-stats
|
||||||
span.number 12
|
span.number 12
|
||||||
span.description assigned<br />points
|
span.description assigned<br />points
|
||||||
li
|
div.summary-stats.summary-stats-divider
|
||||||
span.number 23
|
span.number 23
|
||||||
span.description closed<br />points
|
span.description closed<br />points
|
||||||
|
|
||||||
ul
|
div.summary-stats
|
||||||
li
|
|
||||||
span.icon.icon-bulk
|
span.icon.icon-bulk
|
||||||
span.number 73
|
span.number 73
|
||||||
span.description created<br />tasks
|
span.description created<br />tasks
|
||||||
li
|
div.summary-stats
|
||||||
span.number 72
|
span.number 72
|
||||||
span.description closed<br />tasks
|
span.description closed<br />tasks
|
||||||
li
|
div.summary-stats
|
||||||
span.number 18
|
span.number 18
|
||||||
span.description remaining<br />tasks
|
span.description remaining<br />tasks
|
||||||
ul
|
div.summary-stats
|
||||||
li
|
|
||||||
span.icon.icon-iocaine
|
span.icon.icon-iocaine
|
||||||
span.number 10
|
span.number 10
|
||||||
span.description iocanie<br />doses
|
span.description iocanie<br />doses
|
||||||
|
|
|
@ -1,28 +1,26 @@
|
||||||
div.summary.large-summary
|
div.summary.large-summary
|
||||||
div
|
div.large-summary-wrapper
|
||||||
|
div.summary-progress-wrapper
|
||||||
div.summary-progress-bar(tg-progress-bar="stats.completedPercentage")
|
div.summary-progress-bar(tg-progress-bar="stats.completedPercentage")
|
||||||
div.data
|
div.data
|
||||||
span.number(ng-bind="stats.completedPercentage + '%'")
|
span.number(ng-bind="stats.completedPercentage + '%'")
|
||||||
|
|
||||||
ul
|
div.summary-stats
|
||||||
li
|
|
||||||
span.number(ng-bind="stats.totalPointsSum|default:'--'")
|
span.number(ng-bind="stats.totalPointsSum|default:'--'")
|
||||||
span.description total<br />points
|
span.description total<br />points
|
||||||
li
|
div.summary-stats
|
||||||
span.number(ng-bind="stats.completedPointsSum|default:'--'")
|
span.number(ng-bind="stats.completedPointsSum|default:'--'")
|
||||||
span.description completed<br />points
|
span.description completed<br />points
|
||||||
|
|
||||||
ul
|
div.summary-stats
|
||||||
li
|
|
||||||
span.icon.icon-bulk
|
span.icon.icon-bulk
|
||||||
span.number(ng-bind="stats.openTasks|default:'--'")
|
span.number(ng-bind="stats.openTasks|default:'--'")
|
||||||
span.description open<br />tasks
|
span.description open<br />tasks
|
||||||
li
|
div.summary-stats
|
||||||
span.number(ng-bind="stats.completed_tasks|default:'--'")
|
span.number(ng-bind="stats.completed_tasks|default:'--'")
|
||||||
span.description closed<br />tasks
|
span.description closed<br />tasks
|
||||||
|
|
||||||
ul
|
div.summary-stats(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!")
|
||||||
li(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!")
|
|
||||||
span.icon.icon-iocaine
|
span.icon.icon-iocaine
|
||||||
span.number(ng-bind="stats.iocaine_doses|default:'--'")
|
span.number(ng-bind="stats.iocaine_doses|default:'--'")
|
||||||
span.description iocaine<br />doses
|
span.description iocaine<br />doses
|
||||||
|
|
|
@ -3,16 +3,16 @@ div.summary
|
||||||
|
|
||||||
div.data
|
div.data
|
||||||
span.number(ng-bind="stats.completedPercentage + '%'")
|
span.number(ng-bind="stats.completedPercentage + '%'")
|
||||||
ul
|
|
||||||
li
|
div.summary-stats
|
||||||
span.number(ng-bind="stats.total_points") --
|
span.number(ng-bind="stats.total_points") --
|
||||||
span.description project<br />points
|
span.description project<br />points
|
||||||
li
|
div.summary-stats
|
||||||
span.number(ng-bind="stats.defined_points") --
|
span.number(ng-bind="stats.defined_points") --
|
||||||
span.description defined<br />points
|
span.description defined<br />points
|
||||||
li
|
div.summary-stats
|
||||||
span.number(ng-bind="stats.closed_points") --
|
span.number(ng-bind="stats.closed_points") --
|
||||||
span.description closed<br />points
|
span.description closed<br />points
|
||||||
li
|
div.summary-stats
|
||||||
span.number(ng-bind="stats.speed | number:0") --
|
span.number(ng-bind="stats.speed | number:0") --
|
||||||
span.description points /<br />sprint
|
span.description points /<br />sprint
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue