Fixing merge
commit
bfcab13dfb
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -1,11 +1,28 @@
|
|||
# Changelog #
|
||||
|
||||
## 1.4.0 Unknown (Unreleased)
|
||||
|
||||
### Features
|
||||
- Gitlab integration:
|
||||
+ Create Admin Panel with the Gitlab webhooks settings.
|
||||
- Bitbucket integration:
|
||||
+ Create Admin Panel with the Bitbucket webhooks settings.
|
||||
- Added team members section.
|
||||
- Taskboard enhancements: Collapse of columns (task statuses) and rows (user stories).
|
||||
- Use enter to submit lightboxes forms.
|
||||
|
||||
### Misc
|
||||
- Upgrade to AngularJS 1.3.
|
||||
- Lots of small and not so small bugfixes.
|
||||
|
||||
|
||||
## 1.3.0 Dryas hookeriana (2014-11-18)
|
||||
|
||||
### Features
|
||||
- GitHub integration (Phase I):
|
||||
+ Add button to login/singin with a GitHub account.
|
||||
+ Create Admin Panel with the GitHub webhooks settings.
|
||||
- Show/Hide columns in the Kanban view.
|
||||
- Differentiate blocked user stories on a milestone.
|
||||
|
||||
### Misc
|
||||
|
|
|
@ -63,6 +63,10 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
|||
$routeProvider.when("/project/:pslug/wiki/:slug",
|
||||
{templateUrl: "/partials/wiki.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||
|
||||
# Team
|
||||
$routeProvider.when("/project/:pslug/team",
|
||||
{templateUrl: "/partials/views/team/team.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||
|
||||
# Issues
|
||||
$routeProvider.when("/project/:pslug/issues",
|
||||
{templateUrl: "/partials/issues.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||
|
@ -96,6 +100,10 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
|||
{templateUrl: "/partials/admin-roles.html"})
|
||||
$routeProvider.when("/project/:pslug/admin/third-parties/github",
|
||||
{templateUrl: "/partials/admin-third-parties-github.html"})
|
||||
$routeProvider.when("/project/:pslug/admin/third-parties/gitlab",
|
||||
{templateUrl: "/partials/admin-third-parties-gitlab.html"})
|
||||
$routeProvider.when("/project/:pslug/admin/third-parties/bitbucket",
|
||||
{templateUrl: "/partials/admin-third-parties-bitbucket.html"})
|
||||
|
||||
# User settings
|
||||
$routeProvider.when("/project/:pslug/user-settings/user-profile",
|
||||
|
@ -134,7 +142,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
|||
{templateUrl: "/partials/permission-denied.html"})
|
||||
|
||||
$routeProvider.otherwise({redirectTo: '/not-found'})
|
||||
$locationProvider.html5Mode(true)
|
||||
$locationProvider.html5Mode({enabled: true, requireBase: false})
|
||||
|
||||
defaultHeaders = {
|
||||
"Content-Type": "application/json"
|
||||
|
@ -153,21 +161,45 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
|||
$tgEventsProvider.setSessionId(taiga.sessionId)
|
||||
|
||||
# Add next param when user try to access to a secction need auth permissions.
|
||||
authHttpIntercept = ($q, $location, $confirm, $navUrls, $lightboxService) ->
|
||||
return (promise) ->
|
||||
return promise.then null, (response) ->
|
||||
if response.status == 0
|
||||
$lightboxService.closeAll()
|
||||
$location.path($navUrls.resolve("error"))
|
||||
$location.replace()
|
||||
else if response.status == 401
|
||||
nextPath = $location.path()
|
||||
$location.url($navUrls.resolve("login")).search("next=#{nextPath}")
|
||||
authHttpIntercept = ($q, $location, $navUrls, $lightboxService) ->
|
||||
httpResponseError = (response) ->
|
||||
if response.status == 0
|
||||
$lightboxService.closeAll()
|
||||
$location.path($navUrls.resolve("error"))
|
||||
$location.replace()
|
||||
else if response.status == 401
|
||||
nextPath = $location.path()
|
||||
$location.url($navUrls.resolve("login")).search("next=#{nextPath}")
|
||||
|
||||
return $q.reject(response)
|
||||
|
||||
return {
|
||||
responseError: httpResponseError
|
||||
}
|
||||
|
||||
$provide.factory("authHttpIntercept", ["$q", "$location", "$tgNavUrls", "lightboxService", authHttpIntercept])
|
||||
|
||||
$httpProvider.interceptors.push('authHttpIntercept');
|
||||
|
||||
# If there is an error in the version throw a notify error
|
||||
versionCheckHttpIntercept = ($q, $confirm) ->
|
||||
versionErrorMsg = "Someone inside Taiga has changed this before and our Oompa Loompas cannot apply your changes. Please reload and apply your changes again (they will be lost)." #TODO: i18n
|
||||
|
||||
httpResponseError = (response) ->
|
||||
if response.status == 400 && response.data.version
|
||||
$confirm.notify("error", versionErrorMsg, null, 10000)
|
||||
|
||||
return $q.reject(response)
|
||||
|
||||
$provide.factory("authHttpIntercept", ["$q", "$location", "$tgConfirm", "$tgNavUrls",
|
||||
"lightboxService", authHttpIntercept])
|
||||
$httpProvider.responseInterceptors.push('authHttpIntercept')
|
||||
return $q.reject(response)
|
||||
|
||||
return {
|
||||
responseError: httpResponseError
|
||||
}
|
||||
|
||||
$provide.factory("versionCheckHttpIntercept", ["$q", "$tgConfirm", versionCheckHttpIntercept])
|
||||
|
||||
$httpProvider.interceptors.push('versionCheckHttpIntercept');
|
||||
|
||||
window.checksley.updateValidators({
|
||||
linewidth: (val, width) ->
|
||||
|
@ -210,6 +242,7 @@ modules = [
|
|||
"taigaIssues",
|
||||
"taigaUserStories",
|
||||
"taigaTasks",
|
||||
"taigaTeam",
|
||||
"taigaWiki",
|
||||
"taigaSearch",
|
||||
"taigaAdmin",
|
||||
|
|
|
@ -30,7 +30,7 @@ MAX_MEMBERSHIP_FIELDSETS = 4
|
|||
## Create Members Lightbox Directive
|
||||
#############################################################################
|
||||
|
||||
CreateMembersDirective = ($rs, $rootScope, $confirm, lightboxService) ->
|
||||
CreateMembersDirective = ($rs, $rootScope, $confirm, $loading ,lightboxService) ->
|
||||
extraTextTemplate = """
|
||||
<fieldset class="extra-text">
|
||||
<textarea placeholder="(Optional) Add a personalized text to the invitation. Tell something lovely to your new members ;-)"></textarea>
|
||||
|
@ -103,15 +103,19 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, lightboxService) ->
|
|||
$el.find(".add-member-wrapper fieldset:last > a").removeClass("icon-plus add-fieldset")
|
||||
.addClass("icon-delete delete-fieldset")
|
||||
|
||||
$el.on "click", ".button-green", debounce 2000, (event) ->
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
$loading.start(submitButton)
|
||||
|
||||
onSuccess = (data) ->
|
||||
$loading.finish(submitButton)
|
||||
lightboxService.close($el)
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("membersform:new:success")
|
||||
|
||||
onError = (data) ->
|
||||
$loading.finish(submitButton)
|
||||
lightboxService.close($el)
|
||||
$confirm.notify("error")
|
||||
$rootScope.$broadcast("membersform:new:error")
|
||||
|
@ -143,7 +147,12 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, lightboxService) ->
|
|||
|
||||
$rs.memberships.bulkCreateMemberships($scope.project.id, invitations, invitation_extra_text).then(onSuccess, onError)
|
||||
|
||||
submitButton = $el.find(".submit-button")
|
||||
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
return {link: link}
|
||||
|
||||
module.directive("tgLbCreateMembers", ["$tgResources", "$rootScope", "$tgConfirm", "lightboxService",
|
||||
module.directive("tgLbCreateMembers", ["$tgResources", "$rootScope", "$tgConfirm", "$tgLoading", "lightboxService",
|
||||
CreateMembersDirective])
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
taiga = @.taiga
|
||||
|
||||
mixOf = @.taiga.mixOf
|
||||
bindMethods = @.taiga.bindMethods
|
||||
|
||||
module = angular.module("taigaAdmin")
|
||||
|
||||
|
@ -47,7 +48,7 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai
|
|||
|
||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q,
|
||||
@location, @navUrls, @analytics, @appTitle) ->
|
||||
_.bindAll(@)
|
||||
bindMethods(@)
|
||||
|
||||
@scope.sectionName = "Manage Members" #i18n
|
||||
@scope.project = {}
|
||||
|
@ -301,8 +302,10 @@ MembershipsRowAdminCheckboxDirective = ($log, $repo, $confirm) ->
|
|||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
onError = (data) ->
|
||||
member.revert()
|
||||
$el.find(":checkbox").prop("checked", member.is_owner)
|
||||
$confirm.notify("error", data.is_owner[0])
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
member.is_owner = target.prop("checked")
|
||||
|
|
|
@ -27,6 +27,7 @@ toString = @.taiga.toString
|
|||
joinStr = @.taiga.joinStr
|
||||
groupBy = @.taiga.groupBy
|
||||
bindOnce = @.taiga.bindOnce
|
||||
debounce = @.taiga.debounce
|
||||
|
||||
module = angular.module("taigaAdmin")
|
||||
|
||||
|
@ -95,14 +96,16 @@ module.controller("ProjectProfileController", ProjectProfileController)
|
|||
ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
form = $el.find("form").checksley({"onlyOneErrorElement": true})
|
||||
submit = (target) =>
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
return if not form.validate()
|
||||
|
||||
$loading.start(target)
|
||||
$loading.start(submitButton)
|
||||
|
||||
promise = $repo.save($scope.project)
|
||||
promise.then ->
|
||||
$loading.finish(target)
|
||||
$loading.finish(submitButton)
|
||||
$confirm.notify("success")
|
||||
newUrl = $navurls.resolve("project-admin-project-profile-details", {project: $scope.project.slug})
|
||||
$location.path(newUrl)
|
||||
|
@ -114,24 +117,51 @@ ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location) ->
|
|||
if data._error_message
|
||||
$confirm.notify("error", data._error_message)
|
||||
|
||||
$el.on "submit", "form", (event) ->
|
||||
event.preventDefault()
|
||||
submit()
|
||||
submitButton = $el.find(".submit-button");
|
||||
|
||||
$el.on "click", ".default-values a.button-green", (event) ->
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
submit(target)
|
||||
|
||||
$el.on "click", ".project-details a.button-green", (event) ->
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
submit(target)
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgProjectProfile", ["$tgRepo", "$tgConfirm", "$tgLoading", "$tgNavUrls", "$tgLocation", ProjectProfileDirective])
|
||||
|
||||
#############################################################################
|
||||
## Project Default Values Directive
|
||||
#############################################################################
|
||||
|
||||
ProjectDefaultValuesDirective = ($repo, $confirm, $loading) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
form = $el.find("form").checksley({"onlyOneErrorElement": true})
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
return if not form.validate()
|
||||
|
||||
$loading.start(submitButton)
|
||||
|
||||
promise = $repo.save($scope.project)
|
||||
promise.then ->
|
||||
$loading.finish(submitButton)
|
||||
$confirm.notify("success")
|
||||
|
||||
promise.then null, (data) ->
|
||||
$loading.finish(target)
|
||||
form.setErrors(data)
|
||||
if data._error_message
|
||||
$confirm.notify("error", data._error_message)
|
||||
|
||||
submitButton = $el.find(".submit-button")
|
||||
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgProjectDefaultValues", ["$tgRepo", "$tgConfirm", "$tgLoading", ProjectDefaultValuesDirective])
|
||||
|
||||
#############################################################################
|
||||
## Project Modules Directive
|
||||
|
|
|
@ -208,7 +208,7 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame) ->
|
|||
animationFrame.add () ->
|
||||
goToBottomList()
|
||||
|
||||
$el.find(".new-value").hide()
|
||||
$el.find(".new-value").addClass("hidden")
|
||||
initializeNewValue()
|
||||
|
||||
promise.then null, (data) ->
|
||||
|
|
|
@ -24,6 +24,7 @@ taiga = @.taiga
|
|||
mixOf = @.taiga.mixOf
|
||||
bindOnce = @.taiga.bindOnce
|
||||
debounce = @.taiga.debounce
|
||||
bindMethods = @.taiga.bindMethods
|
||||
|
||||
module = angular.module("taigaAdmin")
|
||||
|
||||
|
@ -47,7 +48,7 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil
|
|||
]
|
||||
|
||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @appTitle) ->
|
||||
_.bindAll(@)
|
||||
bindMethods(@)
|
||||
|
||||
@scope.sectionName = "Permissions" #i18n
|
||||
@scope.project = {}
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
taiga = @.taiga
|
||||
|
||||
mixOf = @.taiga.mixOf
|
||||
bindMethods = @.taiga.bindMethods
|
||||
debounce = @.taiga.debounce
|
||||
|
||||
module = angular.module("taigaAdmin")
|
||||
|
||||
|
@ -40,11 +42,10 @@ class GithubController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
|||
]
|
||||
|
||||
constructor: (@scope, @repo, @rs, @params, @appTitle) ->
|
||||
_.bindAll(@)
|
||||
bindMethods(@)
|
||||
|
||||
@scope.sectionName = "Github" #i18n
|
||||
@scope.project = {}
|
||||
@scope.anyComputableRole = true
|
||||
|
||||
promise = @.loadInitialData()
|
||||
|
||||
|
@ -61,8 +62,6 @@ class GithubController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
|||
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||
@scope.project = project
|
||||
@scope.$emit('project:loaded', project)
|
||||
@scope.anyComputableRole = _.some(_.map(project.roles, (point) -> point.computable))
|
||||
|
||||
return project
|
||||
|
||||
loadInitialData: ->
|
||||
|
@ -76,6 +75,106 @@ class GithubController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
|||
|
||||
module.controller("GithubController", GithubController)
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Gitlab Controller
|
||||
#############################################################################
|
||||
|
||||
class GitlabController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin)
|
||||
@.$inject = [
|
||||
"$scope",
|
||||
"$tgRepo",
|
||||
"$tgResources",
|
||||
"$routeParams",
|
||||
"$appTitle"
|
||||
]
|
||||
|
||||
constructor: (@scope, @repo, @rs, @params, @appTitle) ->
|
||||
bindMethods(@)
|
||||
|
||||
@scope.sectionName = "Gitlab" #i18n
|
||||
@scope.project = {}
|
||||
promise = @.loadInitialData()
|
||||
|
||||
promise.then () =>
|
||||
@appTitle.set("Gitlab - " + @scope.project.name)
|
||||
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
@scope.$on "project:modules:reload", =>
|
||||
@.loadModules()
|
||||
|
||||
loadModules: ->
|
||||
return @rs.modules.list(@scope.projectId, "gitlab").then (gitlab) =>
|
||||
@scope.gitlab = gitlab
|
||||
|
||||
loadProject: ->
|
||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||
@scope.project = project
|
||||
@scope.$emit('project:loaded', project)
|
||||
return project
|
||||
|
||||
loadInitialData: ->
|
||||
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
|
||||
@scope.projectId = data.project
|
||||
return data
|
||||
|
||||
return promise.then(=> @.loadProject())
|
||||
.then(=> @.loadModules())
|
||||
|
||||
|
||||
module.controller("GitlabController", GitlabController)
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Bitbucket Controller
|
||||
#############################################################################
|
||||
|
||||
class BitbucketController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin)
|
||||
@.$inject = [
|
||||
"$scope",
|
||||
"$tgRepo",
|
||||
"$tgResources",
|
||||
"$routeParams",
|
||||
"$appTitle"
|
||||
]
|
||||
|
||||
constructor: (@scope, @repo, @rs, @params, @appTitle) ->
|
||||
bindMethods(@)
|
||||
|
||||
@scope.sectionName = "Bitbucket" #i18n
|
||||
@scope.project = {}
|
||||
promise = @.loadInitialData()
|
||||
|
||||
promise.then () =>
|
||||
@appTitle.set("Bitbucket - " + @scope.project.name)
|
||||
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
@scope.$on "project:modules:reload", =>
|
||||
@.loadModules()
|
||||
|
||||
loadModules: ->
|
||||
return @rs.modules.list(@scope.projectId, "bitbucket").then (bitbucket) =>
|
||||
@scope.bitbucket = bitbucket
|
||||
|
||||
loadProject: ->
|
||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||
@scope.project = project
|
||||
@scope.$emit('project:loaded', project)
|
||||
return project
|
||||
|
||||
loadInitialData: ->
|
||||
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
|
||||
@scope.projectId = data.project
|
||||
return data
|
||||
|
||||
return promise.then(=> @.loadProject())
|
||||
.then(=> @.loadModules())
|
||||
|
||||
module.controller("BitbucketController", BitbucketController)
|
||||
|
||||
|
||||
SelectInputText = ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$el.on "click", ".select-input-content", () ->
|
||||
|
@ -86,6 +185,7 @@ SelectInputText = ->
|
|||
|
||||
module.directive("tgSelectInputText", SelectInputText)
|
||||
|
||||
|
||||
#############################################################################
|
||||
## GithubWebhooks Directive
|
||||
#############################################################################
|
||||
|
@ -93,31 +193,122 @@ module.directive("tgSelectInputText", SelectInputText)
|
|||
GithubWebhooksDirective = ($repo, $confirm, $loading) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
form = $el.find("form").checksley({"onlyOneErrorElement": true})
|
||||
submit = (target) =>
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
return if not form.validate()
|
||||
|
||||
$loading.start(target)
|
||||
$loading.start(submitButton)
|
||||
|
||||
promise = $repo.saveAttribute($scope.github, "github")
|
||||
promise.then ->
|
||||
$loading.finish(target)
|
||||
$loading.finish(submitButton)
|
||||
$confirm.notify("success")
|
||||
|
||||
promise.then null, (data) ->
|
||||
$loading.finish(target)
|
||||
$loading.finish(submitButton)
|
||||
form.setErrors(data)
|
||||
if data._error_message
|
||||
$confirm.notify("error", data._error_message)
|
||||
|
||||
$el.on "click", "a.button-green", (event) ->
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
submit(target)
|
||||
submitButton = $el.find(".submit-button")
|
||||
|
||||
$el.on "submit", "form", (event) ->
|
||||
event.preventDefault()
|
||||
submit()
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgGithubWebhooks", ["$tgRepo", "$tgConfirm", "$tgLoading", GithubWebhooksDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## GitlabWebhooks Directive
|
||||
#############################################################################
|
||||
|
||||
GitlabWebhooksDirective = ($repo, $confirm, $loading) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
form = $el.find("form").checksley({"onlyOneErrorElement": true})
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
return if not form.validate()
|
||||
|
||||
$loading.start(submitButton)
|
||||
|
||||
promise = $repo.saveAttribute($scope.gitlab, "gitlab")
|
||||
promise.then ->
|
||||
$loading.finish(submitButton)
|
||||
$confirm.notify("success")
|
||||
$scope.$emit("project:modules:reload")
|
||||
|
||||
promise.then null, (data) ->
|
||||
$loading.finish(submitButton)
|
||||
form.setErrors(data)
|
||||
if data._error_message
|
||||
$confirm.notify("error", data._error_message)
|
||||
|
||||
submitButton = $el.find(".submit-button")
|
||||
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgGitlabWebhooks", ["$tgRepo", "$tgConfirm", "$tgLoading", GitlabWebhooksDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## BitbucketWebhooks Directive
|
||||
#############################################################################
|
||||
|
||||
BitbucketWebhooksDirective = ($repo, $confirm, $loading) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
form = $el.find("form").checksley({"onlyOneErrorElement": true})
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
return if not form.validate()
|
||||
|
||||
$loading.start(submitButton)
|
||||
|
||||
promise = $repo.saveAttribute($scope.bitbucket, "bitbucket")
|
||||
promise.then ->
|
||||
$loading.finish(submitButton)
|
||||
$confirm.notify("success")
|
||||
$scope.$emit("project:modules:reload")
|
||||
|
||||
promise.then null, (data) ->
|
||||
$loading.finish(submitButton)
|
||||
form.setErrors(data)
|
||||
if data._error_message
|
||||
$confirm.notify("error", data._error_message)
|
||||
|
||||
submitButton = $el.find(".submit-button")
|
||||
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgBitbucketWebhooks", ["$tgRepo", "$tgConfirm", "$tgLoading", BitbucketWebhooksDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Valid Origin IP's Directive
|
||||
#############################################################################
|
||||
ValidOriginIpsDirective = ->
|
||||
link = ($scope, $el, $attrs, $ngModel) ->
|
||||
$ngModel.$parsers.push (value) ->
|
||||
value = $.trim(value)
|
||||
if value == ""
|
||||
return []
|
||||
|
||||
return value.split(",")
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgValidOriginIps", ValidOriginIpsDirective)
|
||||
|
|
|
@ -189,7 +189,9 @@ LoginDirective = ($auth, $confirm, $location, $config, $routeParams, $navUrls, $
|
|||
onError = (response) ->
|
||||
$confirm.notify("light-error", "According to our Oompa Loompas, your username/email
|
||||
or password are incorrect.") #TODO: i18n
|
||||
submit = ->
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
form = new checksley.Form($el.find("form.login-form"))
|
||||
if not form.validate()
|
||||
return
|
||||
|
@ -202,13 +204,8 @@ LoginDirective = ($auth, $confirm, $location, $config, $routeParams, $navUrls, $
|
|||
promise = $auth.login(data)
|
||||
return promise.then(onSuccess, onError)
|
||||
|
||||
$el.on "click", "a.button-login", (event) ->
|
||||
event.preventDefault()
|
||||
submit()
|
||||
|
||||
$el.on "submit", "form", (event) ->
|
||||
event.preventDefault()
|
||||
submit()
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
return {link:link}
|
||||
|
||||
|
@ -239,20 +236,17 @@ RegisterDirective = ($auth, $confirm, $location, $navUrls, $config, $analytics)
|
|||
|
||||
form.setErrors(response.data)
|
||||
|
||||
submit = debounce 2000, =>
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
promise = $auth.register($scope.data)
|
||||
promise.then(onSuccessSubmit, onErrorSubmit)
|
||||
|
||||
$el.on "submit", (event) ->
|
||||
event.preventDefault()
|
||||
submit()
|
||||
|
||||
$el.on "click", "a.button-register", (event) ->
|
||||
event.preventDefault()
|
||||
submit()
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
return {link:link}
|
||||
|
||||
|
@ -279,20 +273,17 @@ ForgotPasswordDirective = ($auth, $confirm, $location, $navUrls) ->
|
|||
$confirm.notify("light-error", "According to our Oompa Loompas,
|
||||
your are not registered yet.") #TODO: i18n
|
||||
|
||||
submit = debounce 2000, =>
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
promise = $auth.forgotPassword($scope.data)
|
||||
promise.then(onSuccessSubmit, onErrorSubmit)
|
||||
|
||||
$el.on "submit", (event) ->
|
||||
event.preventDefault()
|
||||
submit()
|
||||
|
||||
$el.on "click", "a.button-forgot", (event) ->
|
||||
event.preventDefault()
|
||||
submit()
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
return {link:link}
|
||||
|
||||
|
@ -324,20 +315,17 @@ ChangePasswordFromRecoveryDirective = ($auth, $confirm, $location, $params, $nav
|
|||
$confirm.notify("light-error", "One of our Oompa Loompas say
|
||||
'#{response.data._error_message}'.") #TODO: i18n
|
||||
|
||||
submit = debounce 2000, =>
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
promise = $auth.changePasswordFromRecovery($scope.data)
|
||||
promise.then(onSuccessSubmit, onErrorSubmit)
|
||||
|
||||
$el.on "submit", (event) ->
|
||||
event.preventDefault()
|
||||
submit()
|
||||
|
||||
$el.on "click", "a.button-change-password", (event) ->
|
||||
event.preventDefault()
|
||||
submit()
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
return {link:link}
|
||||
|
||||
|
@ -375,20 +363,17 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics
|
|||
$confirm.notify("light-error", "According to our Oompa Loompas, your are not registered yet or
|
||||
typed an invalid password.") #TODO: i18n
|
||||
|
||||
submitLogin = debounce 2000, =>
|
||||
submitLogin = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
if not loginForm.validate()
|
||||
return
|
||||
|
||||
promise = $auth.acceptInvitiationWithExistingUser($scope.dataLogin)
|
||||
promise.then(onSuccessSubmitLogin, onErrorSubmitLogin)
|
||||
|
||||
$el.on "submit", "form.login-form", (event) ->
|
||||
event.preventDefault()
|
||||
submitLogin()
|
||||
|
||||
$el.on "click", "a.button-login", (event) ->
|
||||
event.preventDefault()
|
||||
submitLogin()
|
||||
$el.on "submit", "form.login-form", submitLogin
|
||||
$el.on "click", ".button-login", submitLogin
|
||||
|
||||
# Register form
|
||||
$scope.dataRegister = {token: token}
|
||||
|
@ -404,20 +389,17 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics
|
|||
$confirm.notify("light-error", "According to our Oompa Loompas, that
|
||||
username or email is already in use.") #TODO: i18n
|
||||
|
||||
submitRegister = debounce 2000, =>
|
||||
submitRegister = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
if not registerForm.validate()
|
||||
return
|
||||
|
||||
promise = $auth.acceptInvitiationWithNewUser($scope.dataRegister)
|
||||
promise.then(onSuccessSubmitRegister, onErrorSubmitRegister)
|
||||
|
||||
$el.on "submit", "form.register-form", (event) ->
|
||||
event.preventDefault()
|
||||
submitRegister
|
||||
|
||||
$el.on "click", "a.button-register", (event) ->
|
||||
event.preventDefault()
|
||||
submitRegister()
|
||||
$el.on "submit", "form.register-form", submitRegister
|
||||
$el.on "click", ".button-register", submitRegister
|
||||
|
||||
return {link:link}
|
||||
|
||||
|
@ -483,20 +465,17 @@ CancelAccountDirective = ($repo, $model, $auth, $confirm, $location, $params, $n
|
|||
$confirm.notify("error", "One of our Oompa Loompas says
|
||||
'#{response.data._error_message}'.") #TODO: i18n
|
||||
|
||||
submit = ->
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
promise = $auth.cancelAccount($scope.data)
|
||||
promise.then(onSuccessSubmit, onErrorSubmit)
|
||||
|
||||
$el.on "submit", (event) ->
|
||||
event.preventDefault()
|
||||
submit()
|
||||
|
||||
$el.on "click", "a.button-cancel-account", (event) ->
|
||||
event.preventDefault()
|
||||
submit()
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
return {link:link}
|
||||
|
||||
|
|
|
@ -41,7 +41,9 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading)
|
|||
estimated_finish: null
|
||||
}
|
||||
|
||||
submit = (event) ->
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
form = $el.find("form").checksley()
|
||||
|
||||
|
@ -65,17 +67,17 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading)
|
|||
promise = $repo.save(newSprint)
|
||||
broadcastEvent = "sprintform:edit:success"
|
||||
|
||||
$loading.start(target)
|
||||
$loading.start(submitButton)
|
||||
|
||||
promise.then (data) ->
|
||||
$loading.finish(target)
|
||||
$loading.finish(submitButton)
|
||||
$scope.sprintsCounter += 1 if createSprint
|
||||
$rootscope.$broadcast(broadcastEvent, data)
|
||||
|
||||
lightboxService.close($el)
|
||||
|
||||
promise.then null, (data) ->
|
||||
$loading.finish(target)
|
||||
$loading.finish(submitButton)
|
||||
|
||||
form.setErrors(data)
|
||||
if data._error_message
|
||||
|
@ -152,9 +154,10 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading)
|
|||
else
|
||||
$el.find(".last-sprint-name").removeClass("disappear")
|
||||
|
||||
$el.on "click", ".button-green", debounce 2000, (event) ->
|
||||
event.preventDefault()
|
||||
submit(event)
|
||||
submitButton = $el.find(".submit-button")
|
||||
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
$el.on "click", ".delete-sprint .icon-delete", (event) ->
|
||||
event.preventDefault()
|
||||
|
|
|
@ -27,6 +27,7 @@ scopeDefer = @.taiga.scopeDefer
|
|||
bindOnce = @.taiga.bindOnce
|
||||
groupBy = @.taiga.groupBy
|
||||
timeout = @.taiga.timeout
|
||||
bindMethods = @.taiga.bindMethods
|
||||
|
||||
module = angular.module("taigaBacklog")
|
||||
|
||||
|
@ -53,7 +54,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
|||
|
||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q,
|
||||
@location, @appTitle, @navUrls, @events, @analytics, tgLoader) ->
|
||||
_.bindAll(@)
|
||||
bindMethods(@)
|
||||
|
||||
@scope.sectionName = "Backlog"
|
||||
@showTags = false
|
||||
|
@ -389,14 +390,14 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
|||
|
||||
# Rehash userstories order field
|
||||
# and persist in bulk all changes.
|
||||
promise = @q.all.apply(null, promises).then =>
|
||||
promise = @q.all(promises).then =>
|
||||
items = @.resortUserStories(newSprint.user_stories, "sprint_order")
|
||||
data = @.prepareBulkUpdateData(items, "sprint_order")
|
||||
|
||||
return @rs.userstories.bulkUpdateSprintOrder(project, data).then =>
|
||||
@rs.userstories.bulkUpdateSprintOrder(project, data).then =>
|
||||
@rootscope.$broadcast("sprint:us:moved", us, oldSprintId, newSprintId)
|
||||
|
||||
return @rs.userstories.bulkUpdateBacklogOrder(project, data).then =>
|
||||
@rs.userstories.bulkUpdateBacklogOrder(project, data).then =>
|
||||
for us in usList
|
||||
@rootscope.$broadcast("sprint:us:moved", us, oldSprintId, newSprintId)
|
||||
|
||||
|
@ -444,6 +445,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
|||
return obj
|
||||
|
||||
plainStatuses = _.map(@scope.userstories, "status")
|
||||
|
||||
plainStatuses = _.filter plainStatuses, (status) =>
|
||||
if status
|
||||
return status
|
||||
|
|
|
@ -58,9 +58,7 @@ BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm) ->
|
|||
containment: ".wrapper"
|
||||
dropOnEmpty: true
|
||||
placeholder: "row us-item-row us-item-drag sortable-placeholder"
|
||||
# With scroll activated, it has strange behavior
|
||||
# with not full screen browser window.
|
||||
scroll: false
|
||||
scroll: true
|
||||
# A consequence of length of backlog user story item
|
||||
# the default tolerance ("intersection") not works properly.
|
||||
tolerance: "pointer"
|
||||
|
@ -153,6 +151,7 @@ SprintSortableDirective = ($repo, $rs, $rootscope) ->
|
|||
# If the user has not enough permissions we don't enable the sortable
|
||||
if project.my_permissions.indexOf("modify_us") > -1
|
||||
$el.sortable({
|
||||
scroll: true
|
||||
dropOnEmpty: true
|
||||
connectWith: ".sprint-table,.backlog-table-body,.empty-backlog"
|
||||
})
|
||||
|
|
|
@ -29,23 +29,44 @@ module = angular.module("taigaBacklog")
|
|||
#############################################################################
|
||||
|
||||
BacklogSprintDirective = ($repo, $rootscope) ->
|
||||
sprintTableMinHeight = 50
|
||||
slideOptions = {
|
||||
duration: 500,
|
||||
easing: 'linear'
|
||||
}
|
||||
|
||||
refreshSprintTableHeight = (sprintTable) =>
|
||||
if !sprintTable.find(".row").length
|
||||
sprintTable.css("height", sprintTableMinHeight)
|
||||
else
|
||||
sprintTable.css("height", "auto")
|
||||
|
||||
toggleSprint = ($el) =>
|
||||
sprintTable = $el.find(".sprint-table")
|
||||
sprintArrow = $el.find(".icon-arrow-up")
|
||||
|
||||
sprintArrow.toggleClass('active')
|
||||
sprintTable.toggleClass('open')
|
||||
|
||||
refreshSprintTableHeight(sprintTable)
|
||||
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$scope.$watch $attrs.tgBacklogSprint, (sprint) ->
|
||||
sprint = $scope.$eval($attrs.tgBacklogSprint)
|
||||
|
||||
if $scope.$first
|
||||
$el.addClass("sprint-current")
|
||||
$el.find(".sprint-table").addClass('open')
|
||||
toggleSprint($el)
|
||||
else if sprint.closed
|
||||
$el.addClass("sprint-closed")
|
||||
else if not $scope.$first and not sprint.closed
|
||||
toggleSprint($el)
|
||||
$el.addClass("sprint-old-open")
|
||||
|
||||
# Event Handlers
|
||||
$el.on "click", ".sprint-name > .icon-arrow-up", (event) ->
|
||||
target = $(event.currentTarget)
|
||||
target.toggleClass('active')
|
||||
$el.find(".sprint-table").toggleClass('open')
|
||||
toggleSprint($el)
|
||||
|
||||
$el.find(".sprint-table").slideToggle(slideOptions)
|
||||
|
||||
$el.on "click", ".sprint-name > .icon-edit", (event) ->
|
||||
sprint = $scope.$eval($attrs.tgBacklogSprint)
|
||||
|
|
|
@ -72,8 +72,11 @@ urls = {
|
|||
|
||||
"project-issues-detail": "/project/:project/issue/:ref"
|
||||
|
||||
"project-wiki": "/project/:project/wiki",
|
||||
"project-wiki-page": "/project/:project/wiki/:slug",
|
||||
"project-wiki": "/project/:project/wiki"
|
||||
"project-wiki-page": "/project/:project/wiki/:slug"
|
||||
|
||||
# Team
|
||||
"project-team": "/project/:project/team"
|
||||
|
||||
# Admin
|
||||
"project-admin-home": "/project/:project/admin/project-profile/details"
|
||||
|
@ -90,6 +93,8 @@ urls = {
|
|||
"project-admin-memberships": "/project/:project/admin/memberships"
|
||||
"project-admin-roles": "/project/:project/admin/roles"
|
||||
"project-admin-third-parties-github": "/project/:project/admin/third-parties/github"
|
||||
"project-admin-third-parties-gitlab": "/project/:project/admin/third-parties/gitlab"
|
||||
"project-admin-third-parties-bitbucket": "/project/:project/admin/third-parties/bitbucket"
|
||||
|
||||
# User settings
|
||||
"user-settings-user-profile": "/project/:project/user-settings/user-profile"
|
||||
|
|
|
@ -62,7 +62,7 @@ class RepositoryService extends taiga.Service
|
|||
|
||||
saveAll: (models, patch=true) ->
|
||||
promises = _.map(models, (x) => @.save(x, true))
|
||||
return @q.all.apply(@q, promises)
|
||||
return @q.all(promises)
|
||||
|
||||
save: (model, patch=true) ->
|
||||
defered = @q.defer()
|
||||
|
@ -143,6 +143,7 @@ class RepositoryService extends taiga.Service
|
|||
queryMany: (name, params, options={}) ->
|
||||
url = @urls.resolve(name)
|
||||
httpOptions = {headers: {}}
|
||||
|
||||
if not options.enablePagination
|
||||
httpOptions.headers["x-disable-pagination"] = "1"
|
||||
|
||||
|
|
|
@ -23,6 +23,21 @@ taiga = @.taiga
|
|||
|
||||
module = angular.module("taigaCommon", [])
|
||||
|
||||
#############################################################################
|
||||
## Get the selected text
|
||||
#############################################################################
|
||||
SelectedText = ($window, $document) ->
|
||||
get = () ->
|
||||
if $window.getSelection
|
||||
return $window.getSelection().toString()
|
||||
else if $document.selection
|
||||
return $document.selection.createRange().text
|
||||
return ""
|
||||
|
||||
return {get: get}
|
||||
|
||||
module.factory("$selectedText", ["$window", "$document", SelectedText])
|
||||
|
||||
#############################################################################
|
||||
## Permission directive, hide elements when necessary
|
||||
#############################################################################
|
||||
|
@ -143,3 +158,32 @@ LimitLineLengthDirective = () ->
|
|||
return {link:link}
|
||||
|
||||
module.directive("tgLimitLineLength", LimitLineLengthDirective)
|
||||
|
||||
#############################################################################
|
||||
## Queue Q promises
|
||||
#############################################################################
|
||||
|
||||
Qqueue = ($q) ->
|
||||
deferred = $q.defer()
|
||||
deferred.resolve()
|
||||
|
||||
lastPromise = deferred.promise
|
||||
|
||||
qqueue = {
|
||||
bindAdd: (fn) =>
|
||||
return (args...) =>
|
||||
lastPromise = lastPromise.then () => fn.apply(@, args)
|
||||
|
||||
return qqueue
|
||||
add: (fn) =>
|
||||
if !lastPromise
|
||||
lastPromise = fn()
|
||||
else
|
||||
lastPromise = lastPromise.then(fn)
|
||||
|
||||
return qqueue
|
||||
}
|
||||
|
||||
return qqueue
|
||||
|
||||
module.factory("$tgQqueue", ["$q", Qqueue])
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
taiga = @.taiga
|
||||
sizeFormat = @.taiga.sizeFormat
|
||||
bindOnce = @.taiga.bindOnce
|
||||
bindMethods = @.taiga.bindMethods
|
||||
|
||||
module = angular.module("taigaCommon")
|
||||
|
||||
|
@ -30,7 +31,7 @@ class AttachmentsController extends taiga.Controller
|
|||
@.$inject = ["$scope", "$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", "$q"]
|
||||
|
||||
constructor: (@scope, @rootscope, @repo, @rs, @confirm, @q) ->
|
||||
_.bindAll(@)
|
||||
bindMethods(@)
|
||||
@.type = null
|
||||
@.objectId = null
|
||||
@.projectId = null
|
||||
|
@ -85,7 +86,7 @@ class AttachmentsController extends taiga.Controller
|
|||
# Create attachments in bulk
|
||||
createAttachments: (attachments) ->
|
||||
promises = _.map(attachments, (x) => @._createAttachment(x))
|
||||
return @q.all.apply(null, promises).then =>
|
||||
return @q.all(promises).then =>
|
||||
@.updateCounters()
|
||||
|
||||
# Add uploading attachment tracking.
|
||||
|
|
|
@ -161,7 +161,7 @@ module.directive("tgCreatedByDisplay", CreatedByDisplayDirective)
|
|||
## Watchers directive
|
||||
#############################################################################
|
||||
|
||||
WatchersDirective = ($rootscope, $confirm, $repo) ->
|
||||
WatchersDirective = ($rootscope, $confirm, $repo, $qqueue) ->
|
||||
# You have to include a div with the tg-lb-watchers directive in the page
|
||||
# where use this directive
|
||||
#
|
||||
|
@ -204,17 +204,37 @@ WatchersDirective = ($rootscope, $confirm, $repo) ->
|
|||
isEditable = ->
|
||||
return $scope.project?.my_permissions?.indexOf($attrs.requiredPerm) != -1
|
||||
|
||||
save = (model) ->
|
||||
save = $qqueue.bindAdd (watchers) =>
|
||||
item = $model.$modelValue.clone()
|
||||
item.watchers = watchers
|
||||
$model.$setViewValue(item)
|
||||
|
||||
promise = $repo.save($model.$modelValue)
|
||||
promise.then ->
|
||||
$confirm.notify("success")
|
||||
watchers = _.map(model.watchers, (watcherId) -> $scope.usersById[watcherId])
|
||||
watchers = _.map(watchers, (watcherId) -> $scope.usersById[watcherId])
|
||||
renderWatchers(watchers)
|
||||
$rootscope.$broadcast("history:reload")
|
||||
|
||||
promise.then null, ->
|
||||
$model.$modelValue.revert()
|
||||
|
||||
deleteWatcher = $qqueue.bindAdd (watcherIds) =>
|
||||
item = $model.$modelValue.clone()
|
||||
item.watchers = watcherIds
|
||||
$model.$setViewValue(item)
|
||||
|
||||
promise = $repo.save($model.$modelValue)
|
||||
promise.then ->
|
||||
$confirm.notify("success")
|
||||
watchers = _.map(item.watchers, (watcherId) -> $scope.usersById[watcherId])
|
||||
renderWatchers(watchers)
|
||||
$rootscope.$broadcast("history:reload")
|
||||
promise.then null, ->
|
||||
model.revert()
|
||||
item.revert()
|
||||
$confirm.notify("error")
|
||||
|
||||
|
||||
renderWatchers = (watchers) ->
|
||||
ctx = {
|
||||
watchers: watchers
|
||||
|
@ -239,13 +259,11 @@ WatchersDirective = ($rootscope, $confirm, $repo) ->
|
|||
|
||||
$confirm.askOnDelete(title, message).then (finish) =>
|
||||
finish()
|
||||
|
||||
watcherIds = _.clone($model.$modelValue.watchers, false)
|
||||
watcherIds = _.pull(watcherIds, watcherId)
|
||||
|
||||
item = $model.$modelValue.clone()
|
||||
item.watchers = watcherIds
|
||||
$model.$setViewValue(item)
|
||||
save(item)
|
||||
deleteWatcher(watcherIds)
|
||||
|
||||
$el.on "click", ".add-watcher", (event) ->
|
||||
event.preventDefault()
|
||||
|
@ -258,10 +276,7 @@ WatchersDirective = ($rootscope, $confirm, $repo) ->
|
|||
watchers.push(watcherId)
|
||||
watchers = _.uniq(watchers)
|
||||
|
||||
item = $model.$modelValue.clone()
|
||||
item.watchers = watchers
|
||||
$model.$setViewValue(item)
|
||||
save(item)
|
||||
save(watchers)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (item) ->
|
||||
return if not item?
|
||||
|
@ -273,14 +288,14 @@ WatchersDirective = ($rootscope, $confirm, $repo) ->
|
|||
|
||||
return {link:link, require:"ngModel"}
|
||||
|
||||
module.directive("tgWatchers", ["$rootScope", "$tgConfirm", "$tgRepo", WatchersDirective])
|
||||
module.directive("tgWatchers", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgQqueue", WatchersDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Assigned to directive
|
||||
#############################################################################
|
||||
|
||||
AssignedToDirective = ($rootscope, $confirm, $repo, $loading) ->
|
||||
AssignedToDirective = ($rootscope, $confirm, $repo, $loading, $qqueue) ->
|
||||
# You have to include a div with the tg-lb-assignedto directive in the page
|
||||
# where use this directive
|
||||
#
|
||||
|
@ -315,20 +330,24 @@ AssignedToDirective = ($rootscope, $confirm, $repo, $loading) ->
|
|||
isEditable = ->
|
||||
return $scope.project?.my_permissions?.indexOf($attrs.requiredPerm) != -1
|
||||
|
||||
save = (model) ->
|
||||
save = $qqueue.bindAdd (userId) =>
|
||||
$model.$modelValue.assigned_to = userId
|
||||
|
||||
$loading.start($el)
|
||||
|
||||
promise = $repo.save($model.$modelValue)
|
||||
promise.then ->
|
||||
$loading.finish($el)
|
||||
$confirm.notify("success")
|
||||
renderAssignedTo(model)
|
||||
renderAssignedTo($model.$modelValue)
|
||||
$rootscope.$broadcast("history:reload")
|
||||
promise.then null, ->
|
||||
model.revert()
|
||||
$model.$modelValue.revert()
|
||||
$confirm.notify("error")
|
||||
$loading.finish($el)
|
||||
|
||||
return promise
|
||||
|
||||
renderAssignedTo = (issue) ->
|
||||
assignedToId = issue?.assigned_to
|
||||
assignedTo = if assignedToId? then $scope.usersById[assignedToId] else null
|
||||
|
@ -354,12 +373,12 @@ AssignedToDirective = ($rootscope, $confirm, $repo, $loading) ->
|
|||
$confirm.ask(title).then (finish) =>
|
||||
finish()
|
||||
$model.$modelValue.assigned_to = null
|
||||
save($model.$modelValue)
|
||||
save(null)
|
||||
|
||||
$scope.$on "assigned-to:added", (ctx, userId, item) ->
|
||||
return if item.id != $model.$modelValue.id
|
||||
$model.$modelValue.assigned_to = userId
|
||||
save($model.$modelValue)
|
||||
|
||||
save(userId)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (instance) ->
|
||||
renderAssignedTo(instance)
|
||||
|
@ -372,7 +391,7 @@ AssignedToDirective = ($rootscope, $confirm, $repo, $loading) ->
|
|||
require:"ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgAssignedTo", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgLoading", AssignedToDirective])
|
||||
module.directive("tgAssignedTo", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgLoading", "$tgQqueue", AssignedToDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
|
@ -473,7 +492,7 @@ module.directive("tgDeleteButton", ["$log", "$tgRepo", "$tgConfirm", "$tgLocatio
|
|||
## Editable subject directive
|
||||
#############################################################################
|
||||
|
||||
EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading) ->
|
||||
EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $qqueue) ->
|
||||
template = """
|
||||
<div class="view-subject">
|
||||
{{ item.subject }}
|
||||
|
@ -492,9 +511,11 @@ EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading) ->
|
|||
isEditable = ->
|
||||
return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1
|
||||
|
||||
save = ->
|
||||
$model.$modelValue.subject = $scope.item.subject
|
||||
save = $qqueue.bindAdd (subject) =>
|
||||
$model.$modelValue.subject = subject
|
||||
|
||||
$loading.start($el.find('.save-container'))
|
||||
|
||||
promise = $repo.save($model.$modelValue)
|
||||
promise.then ->
|
||||
$confirm.notify("success")
|
||||
|
@ -506,6 +527,8 @@ EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading) ->
|
|||
promise.finally ->
|
||||
$loading.finish($el.find('.save-container'))
|
||||
|
||||
return promise
|
||||
|
||||
$el.click ->
|
||||
return if not isEditable()
|
||||
$el.find('.edit-subject').show()
|
||||
|
@ -513,11 +536,13 @@ EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading) ->
|
|||
$el.find('input').focus()
|
||||
|
||||
$el.on "click", ".save", ->
|
||||
save()
|
||||
subject = $scope.item.subject
|
||||
save(subject)
|
||||
|
||||
$el.on "keyup", "input", (event) ->
|
||||
if event.keyCode == 13
|
||||
save()
|
||||
subject = $scope.item.subject
|
||||
save(subject)
|
||||
else if event.keyCode == 27
|
||||
$model.$modelValue.revert()
|
||||
$el.find('div.edit-subject').hide()
|
||||
|
@ -545,7 +570,7 @@ EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading) ->
|
|||
template: template
|
||||
}
|
||||
|
||||
module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading",
|
||||
module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue",
|
||||
EditableSubjectDirective])
|
||||
|
||||
|
||||
|
@ -553,7 +578,7 @@ module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$
|
|||
## Editable subject directive
|
||||
#############################################################################
|
||||
|
||||
EditableDescriptionDirective = ($window, $document, $rootscope, $repo, $confirm, $compile, $loading) ->
|
||||
EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading, $selectedText, $qqueue) ->
|
||||
template = """
|
||||
<div class="view-description">
|
||||
<section class="us-content wysiwyg"
|
||||
|
@ -564,6 +589,10 @@ EditableDescriptionDirective = ($window, $document, $rootscope, $repo, $confirm,
|
|||
<textarea placeholder="Empty space is so boring... go on be descriptive... A rose by any other name would smell as sweet..."
|
||||
ng-model="item.description"
|
||||
tg-markitup="tg-markitup"></textarea>
|
||||
<a class="help-markdown" href="https://taiga.io/support/taiga-markdown-syntax/" target="_blank" title="Mardown syntax help">
|
||||
<span class="icon icon-help"></span>
|
||||
<span>Markdown syntax help</span>
|
||||
</a>
|
||||
<span class="save-container">
|
||||
<a class="save icon icon-floppy" href="" title="Save" />
|
||||
</span>
|
||||
|
@ -589,27 +618,8 @@ EditableDescriptionDirective = ($window, $document, $rootscope, $repo, $confirm,
|
|||
isEditable = ->
|
||||
return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1
|
||||
|
||||
getSelectedText = ->
|
||||
if $window.getSelection
|
||||
return $window.getSelection().toString()
|
||||
else if $document.selection
|
||||
return $document.selection.createRange().text
|
||||
return null
|
||||
|
||||
$el.on "mouseup", ".view-description", (event) ->
|
||||
# We want to dettect the a inside the div so we use the target and
|
||||
# not the currentTarget
|
||||
target = angular.element(event.target)
|
||||
return if not isEditable()
|
||||
return if target.is('a')
|
||||
return if getSelectedText()
|
||||
|
||||
$el.find('.edit-description').show()
|
||||
$el.find('.view-description').hide()
|
||||
$el.find('textarea').focus()
|
||||
|
||||
$el.on "click", ".save", ->
|
||||
$model.$modelValue.description = $scope.item.description
|
||||
save = $qqueue.bindAdd (description) =>
|
||||
$model.$modelValue.description = description
|
||||
|
||||
$loading.start($el.find('.save-container'))
|
||||
promise = $repo.save($model.$modelValue)
|
||||
|
@ -623,6 +633,22 @@ EditableDescriptionDirective = ($window, $document, $rootscope, $repo, $confirm,
|
|||
promise.finally ->
|
||||
$loading.finish($el.find('.save-container'))
|
||||
|
||||
$el.on "mouseup", ".view-description", (event) ->
|
||||
# We want to dettect the a inside the div so we use the target and
|
||||
# not the currentTarget
|
||||
target = angular.element(event.target)
|
||||
return if not isEditable()
|
||||
return if target.is('a')
|
||||
return if $selectedText.get().length
|
||||
|
||||
$el.find('.edit-description').show()
|
||||
$el.find('.view-description').hide()
|
||||
$el.find('textarea').focus()
|
||||
|
||||
$el.on "click", ".save", ->
|
||||
description = $scope.item.description
|
||||
save(description)
|
||||
|
||||
$el.on "keyup", "textarea", (event) ->
|
||||
if event.keyCode == 27
|
||||
$scope.item.revert()
|
||||
|
@ -650,8 +676,8 @@ EditableDescriptionDirective = ($window, $document, $rootscope, $repo, $confirm,
|
|||
template: template
|
||||
}
|
||||
|
||||
module.directive("tgEditableDescription", ["$window", "$document", "$rootScope", "$tgRepo", "$tgConfirm",
|
||||
"$compile", "$tgLoading", EditableDescriptionDirective])
|
||||
module.directive("tgEditableDescription", ["$rootScope", "$tgRepo", "$tgConfirm",
|
||||
"$compile", "$tgLoading", "$selectedText", "$tgQqueue", EditableDescriptionDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
|
|
|
@ -23,7 +23,7 @@ taiga = @.taiga
|
|||
timeout = @.taiga.timeout
|
||||
cancelTimeout = @.taiga.cancelTimeout
|
||||
debounce = @.taiga.debounce
|
||||
|
||||
bindMethods = @.taiga.bindMethods
|
||||
|
||||
NOTIFICATION_MSG = {
|
||||
"success":
|
||||
|
@ -42,7 +42,7 @@ class ConfirmService extends taiga.Service
|
|||
@.$inject = ["$q", "lightboxService", "$tgLoading"]
|
||||
|
||||
constructor: (@q, @lightboxService, @loading) ->
|
||||
_.bindAll(@)
|
||||
bindMethods(@)
|
||||
|
||||
hide: (el)->
|
||||
if el
|
||||
|
@ -170,7 +170,7 @@ class ConfirmService extends taiga.Service
|
|||
|
||||
return defered.promise
|
||||
|
||||
notify: (type, message, title) ->
|
||||
notify: (type, message, title, time) ->
|
||||
# NOTE: Typesi are: error, success, light-error
|
||||
# See partials/components/notification-message.jade)
|
||||
# Add default texts to NOTIFICATION_MSG for new notification types
|
||||
|
@ -178,6 +178,8 @@ class ConfirmService extends taiga.Service
|
|||
selector = ".notification-message-#{type}"
|
||||
el = angular.element(selector)
|
||||
|
||||
return if el.hasClass("active")
|
||||
|
||||
if title
|
||||
el.find("h4").html(title)
|
||||
else
|
||||
|
@ -200,7 +202,8 @@ class ConfirmService extends taiga.Service
|
|||
if @.tsem
|
||||
cancelTimeout(@.tsem)
|
||||
|
||||
time = if type == 'error' or type == 'light-error' then 3500 else 1500
|
||||
if !time
|
||||
time = if type == 'error' or type == 'light-error' then 3500 else 1500
|
||||
|
||||
@.tsem = timeout time, =>
|
||||
body.find(selector)
|
||||
|
|
|
@ -165,7 +165,7 @@ module.directive("tgLbUsEstimation", ["$rootScope", "$tgRepo", "$tgConfirm", LbU
|
|||
## User story estimation directive
|
||||
#############################################################################
|
||||
|
||||
UsEstimationDirective = ($rootScope, $repo, $confirm) ->
|
||||
UsEstimationDirective = ($rootScope, $repo, $confirm, $qqueue) ->
|
||||
# Display the points of a US and you can edit it.
|
||||
#
|
||||
# Example:
|
||||
|
@ -264,6 +264,29 @@ UsEstimationDirective = ($rootScope, $repo, $confirm) ->
|
|||
|
||||
return _.reduce(notNullValues, (acc, num) -> acc + num)
|
||||
|
||||
save = $qqueue.bindAdd (roleId, pointId) =>
|
||||
$el.find(".popover").popover().close()
|
||||
|
||||
# Hell starts here
|
||||
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 = ->
|
||||
$confirm.notify("success")
|
||||
$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.stopPropagation()
|
||||
|
@ -287,26 +310,7 @@ UsEstimationDirective = ($rootScope, $repo, $confirm) ->
|
|||
roleId = target.data("role-id")
|
||||
pointId = target.data("point-id")
|
||||
|
||||
$el.find(".popover").popover().close()
|
||||
|
||||
# Hell starts here
|
||||
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 = ->
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("history:reload")
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
us.revert()
|
||||
$model.$setViewValue(us)
|
||||
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||
save(roleId, pointId)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (us) ->
|
||||
render(us) if us
|
||||
|
@ -320,4 +324,4 @@ UsEstimationDirective = ($rootScope, $repo, $confirm) ->
|
|||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgUsEstimation", ["$rootScope", "$tgRepo", "$tgConfirm", UsEstimationDirective])
|
||||
module.directive("tgUsEstimation", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgQqueue", UsEstimationDirective])
|
||||
|
|
|
@ -61,7 +61,7 @@ class HistoryController extends taiga.Controller
|
|||
return @rs.history.undeleteComment(type, objectId, activityId).then => @.loadHistory(type, objectId)
|
||||
|
||||
|
||||
HistoryDirective = ($log, $loading) ->
|
||||
HistoryDirective = ($log, $loading, $qqueue) ->
|
||||
templateChangeDiff = _.template("""
|
||||
<div class="change-entry">
|
||||
<div class="activity-changed">
|
||||
|
@ -233,6 +233,10 @@ HistoryDirective = ($log, $loading) ->
|
|||
ng-model="<%- ngmodel %>.comment" tg-markitup="tg-markitup">
|
||||
</textarea>
|
||||
<% if (mode !== "edit") { %>
|
||||
<a class="help-markdown" href="https://taiga.io/support/taiga-markdown-syntax/" target="_blank" title="Mardown syntax help">
|
||||
<span class="icon icon-help"></span>
|
||||
<span>Markdown syntax help</span>
|
||||
</a>
|
||||
<a href="" title="Comment" class="button button-green save-comment">Comment</a>
|
||||
<% } %>
|
||||
</div>
|
||||
|
@ -432,6 +436,24 @@ HistoryDirective = ($log, $loading) ->
|
|||
html = renderHistory(changes, totalChanges)
|
||||
$el.find(".changes-list").html(html)
|
||||
|
||||
save = $qqueue.bindAdd (target) =>
|
||||
$scope.$broadcast("markdown-editor:submit")
|
||||
|
||||
$el.find(".comment-list").addClass("activeanimation")
|
||||
|
||||
onSuccess = ->
|
||||
$ctrl.loadHistory(type, objectId).finally ->
|
||||
$loading.finish(target)
|
||||
|
||||
onError = ->
|
||||
$loading.finish(target)
|
||||
$confirm.notify("error")
|
||||
|
||||
model = $scope.$eval($attrs.ngModel)
|
||||
$loading.start(target)
|
||||
|
||||
$ctrl.repo.save(model).then(onSuccess, onError)
|
||||
|
||||
# Watchers
|
||||
|
||||
$scope.$watch("comments", renderComments)
|
||||
|
@ -443,22 +465,10 @@ HistoryDirective = ($log, $loading) ->
|
|||
|
||||
$el.on "click", ".add-comment a.button-green", debounce 2000, (event) ->
|
||||
event.preventDefault()
|
||||
$scope.$broadcast("markdown-editor:submit")
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
|
||||
$el.find(".comment-list").addClass("activeanimation")
|
||||
onSuccess = ->
|
||||
$ctrl.loadHistory(type, objectId).finally ->
|
||||
$loading.finish(target)
|
||||
|
||||
onError = ->
|
||||
$loading.finish(target)
|
||||
$confirm.notify("error")
|
||||
|
||||
model = $scope.$eval($attrs.ngModel)
|
||||
$loading.start(target)
|
||||
$ctrl.repo.save(model).then(onSuccess, onError)
|
||||
save(target)
|
||||
|
||||
$el.on "click", ".show-more", (event) ->
|
||||
event.preventDefault()
|
||||
|
@ -522,4 +532,4 @@ HistoryDirective = ($log, $loading) ->
|
|||
}
|
||||
|
||||
|
||||
module.directive("tgHistory", ["$log", "$tgLoading", HistoryDirective])
|
||||
module.directive("tgHistory", ["$log", "$tgLoading", "$tgQqueue", HistoryDirective])
|
||||
|
|
|
@ -126,19 +126,11 @@ module.directive("lightbox", ["lightboxService", LightboxDirective])
|
|||
|
||||
# Issue/Userstory blocking message lightbox directive.
|
||||
|
||||
BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loading) ->
|
||||
BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loading, $qqueue) ->
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
$el.find("h2.title").text($attrs.title)
|
||||
|
||||
$scope.$on "block", ->
|
||||
$el.find(".reason").val($model.$modelValue.blocked_note)
|
||||
lightboxService.open($el)
|
||||
|
||||
$scope.$on "unblock", (event, model, finishCallback) ->
|
||||
item = $model.$modelValue.clone()
|
||||
item.is_blocked = false
|
||||
item.blocked_note = ""
|
||||
|
||||
unblock = $qqueue.bindAdd (item, finishCallback) =>
|
||||
promise = $tgrepo.save(item)
|
||||
promise.then ->
|
||||
$confirm.notify("success")
|
||||
|
@ -154,15 +146,9 @@ BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loadi
|
|||
promise.finally ->
|
||||
finishCallback()
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
return promise
|
||||
|
||||
$el.on "click", ".button-green", (event) ->
|
||||
event.preventDefault()
|
||||
|
||||
item = $model.$modelValue.clone()
|
||||
item.is_blocked = true
|
||||
item.blocked_note = $el.find(".reason").val()
|
||||
block = $qqueue.bindAdd (item) =>
|
||||
$model.$setViewValue(item)
|
||||
|
||||
$loading.start($el.find(".button-green"))
|
||||
|
@ -181,13 +167,36 @@ BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loadi
|
|||
$loading.finish($el.find(".button-green"))
|
||||
lightboxService.close($el)
|
||||
|
||||
$scope.$on "block", ->
|
||||
$el.find(".reason").val($model.$modelValue.blocked_note)
|
||||
lightboxService.open($el)
|
||||
|
||||
$scope.$on "unblock", (event, model, finishCallback) =>
|
||||
item = $model.$modelValue.clone()
|
||||
item.is_blocked = false
|
||||
item.blocked_note = ""
|
||||
|
||||
unblock(item, finishCallback)
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
$el.on "click", ".button-green", (event) ->
|
||||
event.preventDefault()
|
||||
|
||||
item = $model.$modelValue.clone()
|
||||
item.is_blocked = true
|
||||
item.blocked_note = $el.find(".reason").val()
|
||||
|
||||
block(item)
|
||||
|
||||
return {
|
||||
templateUrl: "/partials/views/modules/lightbox-block.html"
|
||||
link: link
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgLbBlock", ["$rootScope", "$tgRepo", "$tgConfirm", "lightboxService", "$tgLoading", BlockLightboxDirective])
|
||||
module.directive("tgLbBlock", ["$rootScope", "$tgRepo", "$tgConfirm", "lightboxService", "$tgLoading", "$tgQqueue", BlockLightboxDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
|
@ -285,15 +294,14 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService,
|
|||
|
||||
lightboxService.open($el)
|
||||
|
||||
$el.on "click", ".button-green", debounce 2000, (event) ->
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
form = $el.find("form").checksley()
|
||||
target = angular.element(event.currentTarget)
|
||||
|
||||
form = $el.find("form").checksley()
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
$loading.start(target)
|
||||
$loading.start(submitButton)
|
||||
|
||||
if $scope.isNew
|
||||
promise = $repo.create("userstories", $scope.us)
|
||||
|
@ -303,16 +311,21 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService,
|
|||
broadcastEvent = "usform:edit:success"
|
||||
|
||||
promise.then (data) ->
|
||||
$loading.finish(target)
|
||||
$loading.finish(submitButton)
|
||||
lightboxService.close($el)
|
||||
$rootScope.$broadcast(broadcastEvent, data)
|
||||
|
||||
promise.then null, (data) ->
|
||||
$loading.finish(target)
|
||||
$loading.finish(submitButton)
|
||||
form.setErrors(data)
|
||||
if data._error_message
|
||||
$confirm.notify("error", data._error_message)
|
||||
|
||||
submitButton = $el.find(".submit-button")
|
||||
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
$el.on "click", ".close", (event) ->
|
||||
event.preventDefault()
|
||||
$scope.$apply ->
|
||||
|
@ -356,28 +369,32 @@ CreateBulkUserstoriesDirective = ($repo, $rs, $rootscope, lightboxService, $load
|
|||
}
|
||||
lightboxService.open($el)
|
||||
|
||||
$el.on "click", ".button-green", debounce 2000, (event) ->
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
|
||||
form = $el.find("form").checksley({onlyOneErrorElement: true})
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
$loading.start(target)
|
||||
$loading.start(submitButton)
|
||||
|
||||
promise = $rs.userstories.bulkCreate($scope.new.projectId, $scope.new.statusId, $scope.new.bulk)
|
||||
promise.then (result) ->
|
||||
$loading.finish(target)
|
||||
$loading.finish(submitButton)
|
||||
$rootscope.$broadcast("usform:bulk:success", result)
|
||||
lightboxService.close($el)
|
||||
|
||||
promise.then null, (data) ->
|
||||
$loading.finish(target)
|
||||
$loading.finish(submitButton)
|
||||
form.setErrors(data)
|
||||
if data._error_message
|
||||
$confirm.notify("error", data._error_message)
|
||||
|
||||
submitButton = $el.find(".submit-button")
|
||||
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
|
|
|
@ -228,7 +228,7 @@ module.directive("tgLbTagLine", ["$tgResources", LbTagLineDirective])
|
|||
## TagLine Directive (for detail pages)
|
||||
#############################################################################
|
||||
|
||||
TagLineDirective = ($rootScope, $repo, $rs, $confirm) ->
|
||||
TagLineDirective = ($rootScope, $repo, $rs, $confirm, $qqueue) ->
|
||||
ENTER_KEY = 13
|
||||
ESC_KEY = 27
|
||||
|
||||
|
@ -288,7 +288,7 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm) ->
|
|||
$el.find("input").autocomplete("close")
|
||||
|
||||
## Aux methods
|
||||
addValue = (value) ->
|
||||
addValue = $qqueue.bindAdd (value) ->
|
||||
value = trim(value.toLowerCase())
|
||||
return if value.length == 0
|
||||
|
||||
|
@ -308,7 +308,7 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm) ->
|
|||
$model.$setViewValue(model)
|
||||
$repo.save(model).then(onSuccess, onError)
|
||||
|
||||
deleteValue = (value) ->
|
||||
deleteValue = $qqueue.bindAdd (value) ->
|
||||
value = trim(value.toLowerCase())
|
||||
return if value.length == 0
|
||||
|
||||
|
@ -325,7 +325,8 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm) ->
|
|||
$confirm.notify("error")
|
||||
model.revert()
|
||||
$model.$setViewValue(model)
|
||||
$repo.save(model).then(onSuccess, onError)
|
||||
|
||||
return $repo.save(model).then(onSuccess, onError)
|
||||
|
||||
saveInputTag = () ->
|
||||
value = $el.find("input").val()
|
||||
|
@ -369,6 +370,7 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm) ->
|
|||
target = angular.element(event.currentTarget)
|
||||
|
||||
value = target.siblings(".tag-name").text()
|
||||
|
||||
deleteValue(value)
|
||||
|
||||
bindOnce $scope, "project", (project) ->
|
||||
|
@ -415,4 +417,4 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm) ->
|
|||
template: template
|
||||
}
|
||||
|
||||
module.directive("tgTagLine", ["$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", TagLineDirective])
|
||||
module.directive("tgTagLine", ["$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", "$tgQqueue", TagLineDirective])
|
||||
|
|
|
@ -28,7 +28,7 @@ module = angular.module("taigaCommon")
|
|||
#############################################################################
|
||||
## WYSIWYG markitup editor directive
|
||||
#############################################################################
|
||||
tgMarkitupDirective = ($rootscope, $rs, $tr) ->
|
||||
tgMarkitupDirective = ($rootscope, $rs, $tr, $selectedText) ->
|
||||
previewTemplate = _.template("""
|
||||
<div class="preview">
|
||||
<div class="actions">
|
||||
|
@ -61,10 +61,16 @@ tgMarkitupDirective = ($rootscope, $rs, $tr) ->
|
|||
markdownDomNode.append(previewTemplate({data: data.data}))
|
||||
markItUpDomNode.hide()
|
||||
|
||||
# FIXME: Really `.parents()` is need? seems `.closest`
|
||||
# function is better aproach for it
|
||||
element.parents(".markdown").one "click", ".preview", (event) ->
|
||||
markdown = element.closest(".markdown")
|
||||
|
||||
markdown.on "mouseup.preview", ".preview", (event) ->
|
||||
event.preventDefault()
|
||||
target = angular.element(event.target)
|
||||
|
||||
if !target.is('a') and $selectedText.get().length
|
||||
return
|
||||
|
||||
markdown.off(".preview")
|
||||
closePreviewMode()
|
||||
|
||||
markdownCaretPositon = false
|
||||
|
@ -277,4 +283,4 @@ tgMarkitupDirective = ($rootscope, $rs, $tr) ->
|
|||
|
||||
return {link:link, require:"ngModel"}
|
||||
|
||||
module.directive("tgMarkitup", ["$rootScope", "$tgResources", "$tgI18n", tgMarkitupDirective])
|
||||
module.directive("tgMarkitup", ["$rootScope", "$tgResources", "$tgI18n", "$selectedText", tgMarkitupDirective])
|
||||
|
|
|
@ -20,13 +20,15 @@
|
|||
###
|
||||
|
||||
taiga = @.taiga
|
||||
startswith = @.taiga.startswith
|
||||
bindMethods = @.taiga.bindMethods
|
||||
|
||||
module = angular.module("taigaEvents", [])
|
||||
|
||||
|
||||
class EventsService
|
||||
constructor: (@win, @log, @config, @auth) ->
|
||||
_.bindAll(@)
|
||||
bindMethods(@)
|
||||
|
||||
initialize: (sessionId) ->
|
||||
@.sessionId = sessionId
|
||||
|
@ -41,7 +43,18 @@ class EventsService
|
|||
setupConnection: ->
|
||||
@.stopExistingConnection()
|
||||
|
||||
url = @config.get("eventsUrl", "ws://localhost:8888/events")
|
||||
url = @config.get("eventsUrl")
|
||||
|
||||
# This allows disable events in case
|
||||
# url is not found on the configuration.
|
||||
return if not url
|
||||
|
||||
# This allows relative urls in configuration.
|
||||
if not startswith(url, "ws:") and not startswith(url, "wss:")
|
||||
loc = @win.location
|
||||
scheme = if loc.protocol == "https:" then "wss:" else "ws:"
|
||||
path = _.str.ltrim(url, "/")
|
||||
url = "#{scheme}//#{loc.host}/#{path}"
|
||||
|
||||
@.ws = new @win.WebSocket(url)
|
||||
@.ws.addEventListener("open", @.onOpen)
|
||||
|
|
|
@ -29,29 +29,33 @@ trim = @.taiga.trim
|
|||
|
||||
module = angular.module("taigaFeedback", [])
|
||||
|
||||
FeedbackDirective = ($lightboxService, $repo, $confirm)->
|
||||
FeedbackDirective = ($lightboxService, $repo, $confirm, $loading)->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
form = $el.find("form").checksley()
|
||||
|
||||
submit = debounce 2000, ->
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
$loading.start(submitButton)
|
||||
|
||||
promise = $repo.create("feedback", $scope.feedback)
|
||||
|
||||
promise.then (data) ->
|
||||
$loading.finish(submitButton)
|
||||
$lightboxService.close($el)
|
||||
$confirm.notify("success", "\\o/ we'll be happy to read your")
|
||||
|
||||
promise.then null, ->
|
||||
$loading.finish(submitButton)
|
||||
$confirm.notify("error")
|
||||
|
||||
$el.on "submit", (event) ->
|
||||
submit()
|
||||
submitButton = $el.find(".submit-button")
|
||||
|
||||
$el.on "click", ".button-green", (event) ->
|
||||
event.preventDefault()
|
||||
submit()
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
$scope.$on "feedback:show", ->
|
||||
$scope.$apply ->
|
||||
|
@ -65,4 +69,4 @@ FeedbackDirective = ($lightboxService, $repo, $confirm)->
|
|||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgLbFeedback", ["lightboxService", "$tgRepo", "$tgConfirm", FeedbackDirective])
|
||||
module.directive("tgLbFeedback", ["lightboxService", "$tgRepo", "$tgConfirm", "$tgLoading", FeedbackDirective])
|
||||
|
|
|
@ -195,7 +195,7 @@ module.directive("tgIssueStatusDisplay", IssueStatusDisplayDirective)
|
|||
## Issue status button directive
|
||||
#############################################################################
|
||||
|
||||
IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
||||
IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue) ->
|
||||
# Display the status of Issue and you can edit it.
|
||||
#
|
||||
# Example:
|
||||
|
@ -236,6 +236,21 @@ IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
|||
})
|
||||
$el.html(html)
|
||||
|
||||
save = $qqueue.bindAdd (value, issue) =>
|
||||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("history:reload")
|
||||
$loading.finish($el.find(".level-name"))
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
issue.revert()
|
||||
$model.$setViewValue(issue)
|
||||
$loading.finish($el.find(".level-name"))
|
||||
|
||||
$loading.start($el.find(".level-name"))
|
||||
|
||||
$repo.save(value).then(onSuccess, onError)
|
||||
|
||||
$el.on "click", ".status-data", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
@ -256,20 +271,7 @@ IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
|||
issue.status = target.data("status-id")
|
||||
$model.$setViewValue(issue)
|
||||
|
||||
$scope.$apply()
|
||||
|
||||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("history:reload")
|
||||
$loading.finish($el.find(".level-name"))
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
issue.revert()
|
||||
$model.$setViewValue(issue)
|
||||
$loading.finish($el.find(".level-name"))
|
||||
|
||||
$loading.start($el.find(".level-name"))
|
||||
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||
save($model.$modelValue, issue)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (issue) ->
|
||||
render(issue) if issue
|
||||
|
@ -283,13 +285,13 @@ IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
|||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgIssueStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", IssueStatusButtonDirective])
|
||||
module.directive("tgIssueStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", IssueStatusButtonDirective])
|
||||
|
||||
#############################################################################
|
||||
## Issue type button directive
|
||||
#############################################################################
|
||||
|
||||
IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
||||
IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue) ->
|
||||
# Display the type of Issue and you can edit it.
|
||||
#
|
||||
# Example:
|
||||
|
@ -330,6 +332,26 @@ IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
|||
})
|
||||
$el.html(html)
|
||||
|
||||
save = $qqueue.bindAdd (type) =>
|
||||
$.fn.popover().closeAll()
|
||||
issue = $model.$modelValue.clone()
|
||||
issue.type = type
|
||||
|
||||
$model.$setViewValue(issue)
|
||||
|
||||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("history:reload")
|
||||
$loading.finish($el.find(".level-name"))
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
issue.revert()
|
||||
$model.$setViewValue(issue)
|
||||
$loading.finish($el.find(".level-name"))
|
||||
$loading.start($el.find(".level-name"))
|
||||
|
||||
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||
|
||||
$el.on "click", ".type-data", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
@ -343,26 +365,8 @@ IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
|||
return if not isEditable()
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
|
||||
$.fn.popover().closeAll()
|
||||
|
||||
issue = $model.$modelValue.clone()
|
||||
issue.type = target.data("type-id")
|
||||
$model.$setViewValue(issue)
|
||||
|
||||
$scope.$apply()
|
||||
|
||||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("history:reload")
|
||||
$loading.finish($el.find(".level-name"))
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
issue.revert()
|
||||
$model.$setViewValue(issue)
|
||||
$loading.finish($el.find(".level-name"))
|
||||
$loading.start($el.find(".level-name"))
|
||||
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||
type = target.data("type-id")
|
||||
save(type)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (issue) ->
|
||||
render(issue) if issue
|
||||
|
@ -376,14 +380,14 @@ IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
|||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgIssueTypeButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", IssueTypeButtonDirective])
|
||||
module.directive("tgIssueTypeButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", IssueTypeButtonDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Issue severity button directive
|
||||
#############################################################################
|
||||
|
||||
IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
||||
IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue) ->
|
||||
# Display the severity of Issue and you can edit it.
|
||||
#
|
||||
# Example:
|
||||
|
@ -424,6 +428,26 @@ IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
|||
})
|
||||
$el.html(html)
|
||||
|
||||
save = $qqueue.bindAdd (severity) =>
|
||||
$.fn.popover().closeAll()
|
||||
|
||||
issue = $model.$modelValue.clone()
|
||||
issue.severity = severity
|
||||
$model.$setViewValue(issue)
|
||||
|
||||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("history:reload")
|
||||
$loading.finish($el.find(".level-name"))
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
issue.revert()
|
||||
$model.$setViewValue(issue)
|
||||
$loading.finish($el.find(".level-name"))
|
||||
$loading.start($el.find(".level-name"))
|
||||
|
||||
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||
|
||||
$el.on "click", ".severity-data", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
@ -437,26 +461,9 @@ IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
|||
return if not isEditable()
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
severity = target.data("severity-id")
|
||||
|
||||
$.fn.popover().closeAll()
|
||||
|
||||
issue = $model.$modelValue.clone()
|
||||
issue.severity = target.data("severity-id")
|
||||
$model.$setViewValue(issue)
|
||||
|
||||
$scope.$apply()
|
||||
|
||||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("history:reload")
|
||||
$loading.finish($el.find(".level-name"))
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
issue.revert()
|
||||
$model.$setViewValue(issue)
|
||||
$loading.finish($el.find(".level-name"))
|
||||
$loading.start($el.find(".level-name"))
|
||||
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||
save(severity)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (issue) ->
|
||||
render(issue) if issue
|
||||
|
@ -470,14 +477,14 @@ IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
|||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgIssueSeverityButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", IssueSeverityButtonDirective])
|
||||
module.directive("tgIssueSeverityButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", IssueSeverityButtonDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Issue priority button directive
|
||||
#############################################################################
|
||||
|
||||
IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
||||
IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue) ->
|
||||
# Display the priority of Issue and you can edit it.
|
||||
#
|
||||
# Example:
|
||||
|
@ -518,6 +525,26 @@ IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
|||
})
|
||||
$el.html(html)
|
||||
|
||||
save = $qqueue.bindAdd (priority) =>
|
||||
$.fn.popover().closeAll()
|
||||
|
||||
issue = $model.$modelValue.clone()
|
||||
issue.priority = priority
|
||||
$model.$setViewValue(issue)
|
||||
|
||||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("history:reload")
|
||||
$loading.finish($el.find(".level-name"))
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
issue.revert()
|
||||
$model.$setViewValue(issue)
|
||||
$loading.finish($el.find(".level-name"))
|
||||
$loading.start($el.find(".level-name"))
|
||||
|
||||
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||
|
||||
$el.on "click", ".priority-data", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
@ -531,26 +558,9 @@ IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
|||
return if not isEditable()
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
priority = target.data("priority-id")
|
||||
|
||||
$.fn.popover().closeAll()
|
||||
|
||||
issue = $model.$modelValue.clone()
|
||||
issue.priority = target.data("priority-id")
|
||||
$model.$setViewValue(issue)
|
||||
|
||||
$scope.$apply()
|
||||
|
||||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("history:reload")
|
||||
$loading.finish($el.find(".level-name"))
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
issue.revert()
|
||||
$model.$setViewValue(issue)
|
||||
$loading.finish($el.find(".level-name"))
|
||||
$loading.start($el.find(".level-name"))
|
||||
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||
save(priority)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (issue) ->
|
||||
render(issue) if issue
|
||||
|
@ -564,14 +574,14 @@ IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
|||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgIssuePriorityButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", IssuePriorityButtonDirective])
|
||||
module.directive("tgIssuePriorityButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", IssuePriorityButtonDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Promote Issue to US button directive
|
||||
#############################################################################
|
||||
|
||||
PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm) ->
|
||||
PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm, $qqueue) ->
|
||||
template = _.template("""
|
||||
<a class="button button-gray editable" tg-check-permission="add_us">
|
||||
Promote to User Story
|
||||
|
@ -579,6 +589,30 @@ PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm) ->
|
|||
""") # TODO: i18n
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
|
||||
save = $qqueue.bindAdd (issue, finish) =>
|
||||
data = {
|
||||
generated_from_issue: issue.id
|
||||
project: issue.project,
|
||||
subject: issue.subject
|
||||
description: issue.description
|
||||
tags: issue.tags
|
||||
is_blocked: issue.is_blocked
|
||||
blocked_note: issue.blocked_note
|
||||
}
|
||||
|
||||
onSuccess = ->
|
||||
finish()
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("promote-issue-to-us:success")
|
||||
|
||||
onError = ->
|
||||
finish(false)
|
||||
$confirm.notify("error")
|
||||
|
||||
$repo.create("userstories", data).then(onSuccess, onError)
|
||||
|
||||
|
||||
$el.on "click", "a", (event) ->
|
||||
event.preventDefault()
|
||||
issue = $model.$modelValue
|
||||
|
@ -588,26 +622,8 @@ PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm) ->
|
|||
subtitle = issue.subject
|
||||
|
||||
$confirm.ask(title, subtitle, message).then (finish) =>
|
||||
data = {
|
||||
generated_from_issue: issue.id
|
||||
project: issue.project,
|
||||
subject: issue.subject
|
||||
description: issue.description
|
||||
tags: issue.tags
|
||||
is_blocked: issue.is_blocked
|
||||
blocked_note: issue.blocked_note
|
||||
}
|
||||
save(issue, finish)
|
||||
|
||||
onSuccess = ->
|
||||
finish()
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("promote-issue-to-us:success")
|
||||
|
||||
onError = ->
|
||||
finish(false)
|
||||
$confirm.notify("error")
|
||||
|
||||
$repo.create("userstories", data).then(onSuccess, onError)
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
@ -619,5 +635,5 @@ PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm) ->
|
|||
link: link
|
||||
}
|
||||
|
||||
module.directive("tgPromoteIssueToUsButton", ["$rootScope", "$tgRepo", "$tgConfirm",
|
||||
module.directive("tgPromoteIssueToUsButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgQqueue",
|
||||
PromoteIssueToUsButtonDirective])
|
||||
|
|
|
@ -29,7 +29,7 @@ module = angular.module("taigaIssues")
|
|||
## Issue Create Lightbox Directive
|
||||
#############################################################################
|
||||
|
||||
CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService) ->
|
||||
CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService, $loading) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
form = $el.find("form").checksley()
|
||||
$scope.issue = {}
|
||||
|
@ -50,31 +50,35 @@ CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService) ->
|
|||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
submit = debounce 2000, ->
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
$loading.start(submitButton)
|
||||
promise = $repo.create("issues", $scope.issue)
|
||||
|
||||
promise.then (data) ->
|
||||
$loading.finish(submitButton)
|
||||
$rootscope.$broadcast("issueform:new:success", data)
|
||||
lightboxService.close($el)
|
||||
$confirm.notify("success")
|
||||
|
||||
promise.then null, ->
|
||||
$loading.finish(submitButton)
|
||||
$confirm.notify("error")
|
||||
|
||||
$el.on "click", ".button-green", (event) ->
|
||||
event.preventDefault()
|
||||
submit()
|
||||
|
||||
$el.on "submit", "form", (event) ->
|
||||
event.preventDefault()
|
||||
submit()
|
||||
submitButton = $el.find(".submit-button")
|
||||
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgLbCreateIssue", ["$tgRepo", "$tgConfirm", "$rootScope", "lightboxService",
|
||||
module.directive("tgLbCreateIssue", ["$tgRepo", "$tgConfirm", "$rootScope", "lightboxService", "$tgLoading",
|
||||
CreateIssueDirective])
|
||||
|
||||
|
||||
|
@ -82,7 +86,7 @@ module.directive("tgLbCreateIssue", ["$tgRepo", "$tgConfirm", "$rootScope", "lig
|
|||
## Issue Bulk Create Lightbox Directive
|
||||
#############################################################################
|
||||
|
||||
CreateBulkIssuesDirective = ($repo, $rs, $confirm, $rootscope, lightboxService) ->
|
||||
CreateBulkIssuesDirective = ($repo, $rs, $confirm, $rootscope, $loading, lightboxService) ->
|
||||
link = ($scope, $el, attrs) ->
|
||||
$scope.$on "issueform:bulk", (ctx, projectId, status)->
|
||||
lightboxService.open($el)
|
||||
|
@ -91,29 +95,38 @@ CreateBulkIssuesDirective = ($repo, $rs, $confirm, $rootscope, lightboxService)
|
|||
bulk: ""
|
||||
}
|
||||
|
||||
$el.on "click", ".button-green", debounce 2000, (event) ->
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
form = $el.find("form").checksley()
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
$loading.start(submitButton)
|
||||
|
||||
data = $scope.new.bulk
|
||||
projectId = $scope.new.projectId
|
||||
|
||||
promise = $rs.issues.bulkCreate(projectId, data)
|
||||
promise.then (result) ->
|
||||
$loading.finish(submitButton)
|
||||
$rootscope.$broadcast("issueform:new:success", result)
|
||||
lightboxService.close($el)
|
||||
$confirm.notify("success")
|
||||
|
||||
promise.then null, ->
|
||||
$loading.finish(submitButton)
|
||||
$confirm.notify("error")
|
||||
|
||||
submitButton = $el.find(".submit-button")
|
||||
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {link: link}
|
||||
|
||||
module.directive("tgLbCreateBulkIssues", ["$tgRepo", "$tgResources", "$tgConfirm", "$rootScope",
|
||||
module.directive("tgLbCreateBulkIssues", ["$tgRepo", "$tgResources", "$tgConfirm", "$rootScope", "$tgLoading",
|
||||
"lightboxService", CreateBulkIssuesDirective])
|
||||
|
|
|
@ -27,6 +27,7 @@ scopeDefer = @.taiga.scopeDefer
|
|||
bindOnce = @.taiga.bindOnce
|
||||
groupBy = @.taiga.groupBy
|
||||
timeout = @.taiga.timeout
|
||||
bindMethods = @.taiga.bindMethods
|
||||
|
||||
module = angular.module("taigaKanban")
|
||||
|
||||
|
@ -66,7 +67,9 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
|||
|
||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
|
||||
@appTitle, @navUrls, @events, @analytics, tgLoader) ->
|
||||
_.bindAll(@)
|
||||
|
||||
bindMethods(@)
|
||||
|
||||
@scope.sectionName = "Kanban"
|
||||
@scope.statusViewModes = {}
|
||||
@.initializeEventHandlers()
|
||||
|
|
|
@ -121,7 +121,10 @@ ProjectsNavigationDirective = ($rootscope, animationFrame, $timeout, tgLoader, $
|
|||
|
||||
timeout timeoutValue, ->
|
||||
overlay.one 'transitionend', () ->
|
||||
$(document.body).removeClass("loading-project open-projects-nav closed-projects-nav")
|
||||
$(document.body)
|
||||
.removeClass("loading-project open-projects-nav closed-projects-nav")
|
||||
.css("overflow-x", "visible")
|
||||
|
||||
overlay.hide()
|
||||
|
||||
$(document.body).addClass("closed-projects-nav")
|
||||
|
@ -153,11 +156,12 @@ ProjectsNavigationDirective = ($rootscope, animationFrame, $timeout, tgLoader, $
|
|||
|
||||
$scope.$on "nav:projects-list:open", ->
|
||||
if !$(document.body).hasClass("open-projects-nav")
|
||||
animationFrame.add () ->
|
||||
overlay.show()
|
||||
animationFrame.add () => overlay.show()
|
||||
|
||||
animationFrame.add () ->
|
||||
$(document.body).toggleClass("open-projects-nav")
|
||||
animationFrame.add(
|
||||
() => $(document.body).css("overflow-x", "hidden")
|
||||
() => $(document.body).toggleClass("open-projects-nav")
|
||||
)
|
||||
|
||||
$el.on "click", ".projects-list > li > a", (event) ->
|
||||
# HACK: to solve a problem with the loader when the next url
|
||||
|
@ -243,6 +247,12 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $
|
|||
</a>
|
||||
</li>
|
||||
<% } %>
|
||||
<li id="nav-team">
|
||||
<a href="" title="Team" tg-nav="project-team:project=project.slug">
|
||||
<span class="icon icon-team"></span>
|
||||
<span class="item">Team</span>
|
||||
</a>
|
||||
</li>
|
||||
<% if (project.videoconferences) { %>
|
||||
<li id="nav-video">
|
||||
<a href="<%- project.videoconferenceUrl %>" target="_blank" title="Meet Up">
|
||||
|
@ -309,6 +319,22 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $
|
|||
<div class="menu-container"></div>
|
||||
""")
|
||||
|
||||
# If the last page was kanban or backlog and
|
||||
# the new one is the task detail or the us details
|
||||
# this method preserve the last section name.
|
||||
getSectionName = ($el, sectionName, project) ->
|
||||
oldSectionName = $el.find("a.active").parent().attr("id")?.replace("nav-", "")
|
||||
|
||||
if sectionName == "backlog-kanban"
|
||||
if oldSectionName in ["backlog", "kanban"]
|
||||
sectionName = oldSectionName
|
||||
else if project.is_backlog_activated && !project.is_kanban_activated
|
||||
sectionName = "backlog"
|
||||
else if !project.is_backlog_activated && project.is_kanban_activated
|
||||
sectionName = "kanban"
|
||||
|
||||
return sectionName
|
||||
|
||||
renderMainMenu = ($el) ->
|
||||
html = mainTemplate({})
|
||||
$el.html(html)
|
||||
|
@ -318,7 +344,7 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $
|
|||
# content loaded signal is raised using inner scope.
|
||||
renderMenuEntries = ($el, targetScope, project={}) ->
|
||||
container = $el.find(".menu-container")
|
||||
sectionName = targetScope.section
|
||||
sectionName = getSectionName($el, targetScope.section, project)
|
||||
|
||||
ctx = {
|
||||
user: $auth.getUser(),
|
||||
|
|
|
@ -26,7 +26,7 @@ debounce = @.taiga.debounce
|
|||
|
||||
module = angular.module("taigaProject")
|
||||
|
||||
CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $projectUrl, lightboxService, $cacheFactory) ->
|
||||
CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $projectUrl, $loading, lightboxService, $cacheFactory) ->
|
||||
link = ($scope, $el, attrs) ->
|
||||
$scope.data = {}
|
||||
$scope.templates = []
|
||||
|
@ -39,12 +39,14 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
|
|||
# than another deleted in the same session
|
||||
$cacheFactory.get('$http').removeAll()
|
||||
|
||||
$loading.finish(submitButton)
|
||||
$rootscope.$broadcast("projects:reload")
|
||||
$confirm.notify("success", "Success") #TODO: i18n
|
||||
$location.url($projectUrl.get(response))
|
||||
lightboxService.close($el)
|
||||
|
||||
onErrorSubmit = (response) ->
|
||||
$loading.finish(submitButton)
|
||||
form.setErrors(response)
|
||||
selectors = []
|
||||
for error_field in _.keys(response)
|
||||
|
@ -54,10 +56,14 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
|
|||
error_step.addClass("active")
|
||||
$el.find('.progress-bar').removeClass().addClass('progress-bar').addClass(error_step.data("step"))
|
||||
|
||||
submit = ->
|
||||
submit = (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
$loading.start(submitButton)
|
||||
|
||||
promise = $repo.create("projects", $scope.data)
|
||||
promise.then(onSuccessSubmit, onErrorSubmit)
|
||||
|
||||
|
@ -109,10 +115,10 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
|
|||
step = prev.data('step')
|
||||
$el.find('.progress-bar').removeClass().addClass('progress-bar').addClass(step)
|
||||
|
||||
submitButton = $el.find(".submit-button")
|
||||
|
||||
$el.on "click", ".button-submit", debounce 2000, (event) ->
|
||||
event.preventDefault()
|
||||
submit()
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
$el.on "click", ".close", (event) ->
|
||||
event.preventDefault()
|
||||
|
@ -121,7 +127,7 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
|
|||
return {link:link}
|
||||
|
||||
module.directive("tgLbCreateProject", ["$rootScope", "$tgRepo", "$tgConfirm", "$location", "$tgNavUrls",
|
||||
"$tgResources", "$projectUrl", "lightboxService", "$cacheFactory", CreateProject])
|
||||
"$tgResources", "$projectUrl", "$tgLoading", "lightboxService", "$cacheFactory", CreateProject])
|
||||
|
||||
|
||||
#############################################################################
|
||||
|
|
|
@ -42,6 +42,7 @@ urls = {
|
|||
"userstories-restore": "/userstories/%s/restore"
|
||||
"tasks": "/tasks"
|
||||
"bulk-create-tasks": "/tasks/bulk_create"
|
||||
"bulk-update-task-taskboard-order": "/tasks/bulk_update_taskboard_order"
|
||||
"tasks-restore": "/tasks/%s/restore"
|
||||
"issues": "/issues"
|
||||
"bulk-create-issues": "/issues/bulk_create"
|
||||
|
|
|
@ -28,10 +28,13 @@ resourceProvider = ($repo, $http, $urls) ->
|
|||
service.get = (id) ->
|
||||
return $repo.queryOne("memberships", id)
|
||||
|
||||
service.list = (projectId, filters) ->
|
||||
service.list = (projectId, filters, enablePagination=true) ->
|
||||
params = {project: projectId}
|
||||
params = _.extend({}, params, filters or {})
|
||||
return $repo.queryPaginated("memberships", params)
|
||||
if enablePagination
|
||||
return $repo.queryPaginated("memberships", params)
|
||||
|
||||
return $repo.queryMany("memberships", params, options={enablePagination:enablePagination})
|
||||
|
||||
service.listByUser = (userId, filters) ->
|
||||
params = {user: userId}
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
taiga = @.taiga
|
||||
|
||||
resourceProvider = ($repo) ->
|
||||
resourceProvider = ($repo, $http, $urls) ->
|
||||
service = {}
|
||||
|
||||
service.get = (id) ->
|
||||
|
@ -45,6 +45,13 @@ resourceProvider = ($repo) ->
|
|||
service.stats = (projectId) ->
|
||||
return $repo.queryOneRaw("projects", "#{projectId}/stats")
|
||||
|
||||
service.leave = (projectId) ->
|
||||
url = "#{$urls.resolve("projects")}/#{projectId}/leave"
|
||||
return $http.post(url)
|
||||
|
||||
service.memberStats = (projectId) ->
|
||||
return $repo.queryOneRaw("projects", "#{projectId}/member_stats")
|
||||
|
||||
service.tagsColors = (id) ->
|
||||
return $repo.queryOne("projects", "#{id}/tags_colors")
|
||||
|
||||
|
@ -53,4 +60,4 @@ resourceProvider = ($repo) ->
|
|||
|
||||
|
||||
module = angular.module("taigaResources")
|
||||
module.factory("$tgProjectsResourcesProvider", ["$tgRepo", resourceProvider])
|
||||
module.factory("$tgProjectsResourcesProvider", ["$tgRepo", "$tgHttp", "$tgUrls", resourceProvider])
|
||||
|
|
|
@ -27,6 +27,8 @@ generateHash = taiga.generateHash
|
|||
resourceProvider = ($repo, $http, $urls, $storage) ->
|
||||
service = {}
|
||||
hashSuffix = "tasks-queryparams"
|
||||
hashSuffixStatusColumnModes = "tasks-statuscolumnmodels"
|
||||
hashSuffixUsRowModes = "tasks-usrowmodels"
|
||||
|
||||
service.get = (projectId, taskId) ->
|
||||
params = service.getQueryParams(projectId)
|
||||
|
@ -46,6 +48,11 @@ resourceProvider = ($repo, $http, $urls, $storage) ->
|
|||
return $http.post(url, params).then (result) ->
|
||||
return result.data
|
||||
|
||||
service.bulkUpdateTaskTaskboardOrder = (projectId, data) ->
|
||||
url = $urls.resolve("bulk-update-task-taskboard-order")
|
||||
params = {project_id: projectId, bulk_tasks: data}
|
||||
return $http.post(url, params)
|
||||
|
||||
service.listValues = (projectId, type) ->
|
||||
params = {"project": projectId}
|
||||
return $repo.queryMany(type, params)
|
||||
|
@ -60,6 +67,28 @@ resourceProvider = ($repo, $http, $urls, $storage) ->
|
|||
hash = generateHash([projectId, ns])
|
||||
return $storage.get(hash) or {}
|
||||
|
||||
service.storeStatusColumnModes = (projectId, params) ->
|
||||
ns = "#{projectId}:#{hashSuffixStatusColumnModes}"
|
||||
hash = generateHash([projectId, ns])
|
||||
$storage.set(hash, params)
|
||||
|
||||
service.getStatusColumnModes = (projectId) ->
|
||||
ns = "#{projectId}:#{hashSuffixStatusColumnModes}"
|
||||
hash = generateHash([projectId, ns])
|
||||
return $storage.get(hash) or {}
|
||||
|
||||
service.storeUsRowModes = (projectId, sprintId, params) ->
|
||||
ns = "#{projectId}:#{hashSuffixUsRowModes}"
|
||||
hash = generateHash([projectId, sprintId, ns])
|
||||
|
||||
$storage.set(hash, params)
|
||||
|
||||
service.getUsRowModes = (projectId, sprintId) ->
|
||||
ns = "#{projectId}:#{hashSuffixUsRowModes}"
|
||||
hash = generateHash([projectId, sprintId, ns])
|
||||
|
||||
return $storage.get(hash) or {}
|
||||
|
||||
return (instance) ->
|
||||
instance.tasks = service
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ bindOnce = @.taiga.bindOnce
|
|||
mixOf = @.taiga.mixOf
|
||||
debounceLeading = @.taiga.debounceLeading
|
||||
trim = @.taiga.trim
|
||||
debounce = @.taiga.debounce
|
||||
|
||||
module = angular.module("taigaSearch", [])
|
||||
|
||||
|
@ -111,7 +112,9 @@ SearchBoxDirective = ($lightboxService, $navurls, $location, $route)->
|
|||
link = ($scope, $el, $attrs) ->
|
||||
project = null
|
||||
|
||||
submit = ->
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
form = $el.find("form").checksley()
|
||||
if not form.validate()
|
||||
return
|
||||
|
@ -131,12 +134,8 @@ SearchBoxDirective = ($lightboxService, $navurls, $location, $route)->
|
|||
$lightboxService.open($el)
|
||||
$el.find("#search-text").val("")
|
||||
|
||||
$el.on "submit", (event) ->
|
||||
submit()
|
||||
|
||||
$el.on "click", ".button-green", (event) ->
|
||||
event.preventDefault()
|
||||
submit()
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
return {link:link}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ taiga = @.taiga
|
|||
bindOnce = @.taiga.bindOnce
|
||||
debounce = @.taiga.debounce
|
||||
|
||||
CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, lightboxService) ->
|
||||
CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxService) ->
|
||||
link = ($scope, $el, attrs) ->
|
||||
$scope.isNew = true
|
||||
|
||||
|
@ -53,7 +53,10 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, lightboxService) ->
|
|||
$el.find(".title").html("Edit task ") #TODO: i18n
|
||||
lightboxService.open($el)
|
||||
|
||||
$el.on "click", ".button-green", debounce 2000, (event) ->
|
||||
|
||||
submitButton = $el.find(".submit-button")
|
||||
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
form = $el.find("form").checksley()
|
||||
|
@ -67,32 +70,36 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, lightboxService) ->
|
|||
promise = $repo.save($scope.task)
|
||||
broadcastEvent = "taskform:edit:success"
|
||||
|
||||
$loading.start(submitButton)
|
||||
|
||||
# FIXME: error handling?
|
||||
promise.then (data) ->
|
||||
$loading.finish(submitButton)
|
||||
lightboxService.close($el)
|
||||
$rootscope.$broadcast(broadcastEvent, data)
|
||||
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {link: link}
|
||||
|
||||
|
||||
CreateBulkTasksDirective = ($repo, $rs, $rootscope, lightboxService) ->
|
||||
CreateBulkTasksDirective = ($repo, $rs, $rootscope, $loading, lightboxService) ->
|
||||
link = ($scope, $el, attrs) ->
|
||||
$scope.form = {data: "", usId: null}
|
||||
|
||||
$scope.$on "taskform:bulk", (ctx, sprintId, usId)->
|
||||
lightboxService.open($el)
|
||||
$scope.form = {data: "", sprintId: sprintId, usId: usId}
|
||||
|
||||
$el.on "click", ".button-green", debounce 2000, (event) ->
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
form = $el.find("form").checksley()
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
$loading.start(submitButton)
|
||||
|
||||
data = $scope.form.data
|
||||
projectId = $scope.projectId
|
||||
sprintId = $scope.form.sprintId
|
||||
|
@ -100,13 +107,24 @@ CreateBulkTasksDirective = ($repo, $rs, $rootscope, lightboxService) ->
|
|||
|
||||
promise = $rs.tasks.bulkCreate(projectId, sprintId, usId, data)
|
||||
promise.then (result) ->
|
||||
$loading.finish(submitButton)
|
||||
$rootscope.$broadcast("taskform:bulk:success", result)
|
||||
lightboxService.close($el)
|
||||
|
||||
# TODO: error handling
|
||||
promise.then null, ->
|
||||
$loading.finish(submitButton)
|
||||
console.log "FAIL"
|
||||
|
||||
$scope.$on "taskform:bulk", (ctx, sprintId, usId)->
|
||||
lightboxService.open($el)
|
||||
$scope.form = {data: "", sprintId: sprintId, usId: usId}
|
||||
|
||||
submitButton = $el.find(".submit-button")
|
||||
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
|
@ -120,6 +138,7 @@ module.directive("tgLbCreateEditTask", [
|
|||
"$tgModel",
|
||||
"$tgResources",
|
||||
"$rootScope",
|
||||
"$tgLoading",
|
||||
"lightboxService",
|
||||
CreateEditTaskDirective
|
||||
])
|
||||
|
@ -128,6 +147,7 @@ module.directive("tgLbCreateBulkTasks", [
|
|||
"$tgRepo",
|
||||
"$tgResources",
|
||||
"$rootScope",
|
||||
"$tgLoading",
|
||||
"lightboxService",
|
||||
CreateBulkTasksDirective
|
||||
])
|
||||
|
|
|
@ -26,6 +26,7 @@ groupBy = @.taiga.groupBy
|
|||
bindOnce = @.taiga.bindOnce
|
||||
scopeDefer = @.taiga.scopeDefer
|
||||
timeout = @.taiga.timeout
|
||||
bindMethods = @.taiga.bindMethods
|
||||
|
||||
module = angular.module("taigaTaskboard")
|
||||
|
||||
|
@ -53,7 +54,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
|
||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @appTitle, @location, @navUrls,
|
||||
@events, @analytics, tgLoader) ->
|
||||
_.bindAll(@)
|
||||
bindMethods(@)
|
||||
|
||||
@scope.sectionName = "Taskboard"
|
||||
@.initializeEventHandlers()
|
||||
|
@ -102,7 +103,6 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
loadProject: ->
|
||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||
@scope.project = project
|
||||
@scope.$emit('project:loaded', project)
|
||||
# Not used at this momment
|
||||
@scope.pointsList = _.sortBy(project.points, "order")
|
||||
# @scope.roleList = _.sortBy(project.roles, "order")
|
||||
|
@ -111,6 +111,9 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
@scope.taskStatusList = _.sortBy(project.task_statuses, "order")
|
||||
@scope.usStatusList = _.sortBy(project.us_statuses, "order")
|
||||
@scope.usStatusById = groupBy(project.us_statuses, (e) -> e.id)
|
||||
|
||||
@scope.$emit('project:loaded', project)
|
||||
|
||||
return project
|
||||
|
||||
loadSprintStats: ->
|
||||
|
@ -144,7 +147,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
|
||||
loadTasks: ->
|
||||
return @rs.tasks.list(@scope.projectId, @scope.sprintId).then (tasks) =>
|
||||
@scope.tasks = tasks
|
||||
@scope.tasks = _.sortBy(tasks, 'taskboard_order')
|
||||
@scope.usTasks = {}
|
||||
|
||||
# Iterate over all userstories and
|
||||
|
@ -183,21 +186,46 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
.then(=> @.loadUsersAndRoles())
|
||||
.then(=> @.loadTaskboard())
|
||||
|
||||
refreshTasksOrder: (tasks) ->
|
||||
items = @.resortTasks(tasks)
|
||||
data = @.prepareBulkUpdateData(items)
|
||||
|
||||
return @rs.tasks.bulkUpdateTaskTaskboardOrder(@scope.project.id, data)
|
||||
|
||||
resortTasks: (tasks) ->
|
||||
items = []
|
||||
|
||||
for item, index in tasks
|
||||
item["taskboard_order"] = index
|
||||
if item.isModified()
|
||||
items.push(item)
|
||||
|
||||
return items
|
||||
|
||||
prepareBulkUpdateData: (uses) ->
|
||||
return _.map(uses, (x) -> {"task_id": x.id, "order": x["taskboard_order"]})
|
||||
|
||||
taskMove: (ctx, task, usId, statusId, order) ->
|
||||
# Remove task from old position
|
||||
r = @scope.usTasks[task.user_story][task.status].indexOf(task)
|
||||
@scope.usTasks[task.user_story][task.status].splice(r, 1)
|
||||
|
||||
# Add task to new position
|
||||
@scope.usTasks[usId][statusId].splice(order, 0, task)
|
||||
tasks = @scope.usTasks[usId][statusId]
|
||||
tasks.splice(order, 0, task)
|
||||
|
||||
task.user_story = usId
|
||||
task.status = statusId
|
||||
task.order = order
|
||||
task.taskboard_order = order
|
||||
|
||||
promise = @repo.save(task)
|
||||
|
||||
@rootscope.$broadcast("sprint:task:moved", task)
|
||||
|
||||
promise.then =>
|
||||
@.refreshTasksOrder(tasks)
|
||||
@.loadSprintStats()
|
||||
|
||||
promise.then null, =>
|
||||
console.log "FAIL TASK SAVE"
|
||||
|
||||
|
@ -267,22 +295,6 @@ TaskboardTaskDirective = ($rootscope) ->
|
|||
|
||||
module.directive("tgTaskboardTask", ["$rootScope", TaskboardTaskDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Taskboard Task Row Size Fixer Directive
|
||||
#############################################################################
|
||||
|
||||
TaskboardRowWidthFixerDirective = ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
bindOnce $scope, "taskStatusList", (statuses) ->
|
||||
itemSize = 300 + (10 * statuses.length)
|
||||
size = (1 + statuses.length) * itemSize
|
||||
$el.css("width", "#{size}px")
|
||||
|
||||
return {link: link}
|
||||
|
||||
module.directive("tgTaskboardRowWidthFixer", TaskboardRowWidthFixerDirective)
|
||||
|
||||
#############################################################################
|
||||
## Taskboard Table Height Fixer Directive
|
||||
#############################################################################
|
||||
|
@ -307,64 +319,156 @@ TaskboardTableHeightFixerDirective = ->
|
|||
|
||||
module.directive("tgTaskboardTableHeightFixer", TaskboardTableHeightFixerDirective)
|
||||
|
||||
#############################################################################
|
||||
## Taskboard Squish Column Directive
|
||||
#############################################################################
|
||||
|
||||
TaskboardSquishColumnDirective = (rs) ->
|
||||
avatarWidth = 40
|
||||
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$scope.$on "sprint:task:moved", () =>
|
||||
recalculateTaskboardWidth()
|
||||
|
||||
bindOnce $scope, "usTasks", (project) ->
|
||||
$scope.statusesFolded = rs.tasks.getStatusColumnModes($scope.project.id)
|
||||
$scope.usFolded = rs.tasks.getUsRowModes($scope.project.id, $scope.sprintId)
|
||||
|
||||
recalculateTaskboardWidth()
|
||||
|
||||
$scope.foldStatus = (status) ->
|
||||
$scope.statusesFolded[status.id] = !!!$scope.statusesFolded[status.id]
|
||||
rs.tasks.storeStatusColumnModes($scope.projectId, $scope.statusesFolded)
|
||||
|
||||
recalculateTaskboardWidth()
|
||||
|
||||
$scope.foldUs = (us) ->
|
||||
if !us
|
||||
$scope.usFolded["unassigned"] = !!!$scope.usFolded["unassigned"]
|
||||
else
|
||||
$scope.usFolded[us.id] = !!!$scope.usFolded[us.id]
|
||||
|
||||
rs.tasks.storeUsRowModes($scope.projectId, $scope.sprintId, $scope.usFolded)
|
||||
|
||||
recalculateTaskboardWidth()
|
||||
|
||||
getCeilWidth = (usId, statusId) =>
|
||||
tasks = $scope.usTasks[usId][statusId].length
|
||||
|
||||
if $scope.statusesFolded[statusId]
|
||||
if tasks and $scope.usFolded[usId]
|
||||
tasksMatrixSize = Math.round(Math.sqrt(tasks))
|
||||
width = avatarWidth * tasksMatrixSize
|
||||
else
|
||||
width = avatarWidth
|
||||
|
||||
return width
|
||||
|
||||
return 0
|
||||
|
||||
setStatusColumnWidth = (statusId, width) =>
|
||||
column = $el.find(".squish-status-#{statusId}")
|
||||
|
||||
if width
|
||||
column.css('max-width', width)
|
||||
else
|
||||
column.removeAttr("style")
|
||||
|
||||
refreshTaskboardTableWidth = () =>
|
||||
columnWidths = []
|
||||
|
||||
columns = $el.find(".task-colum-name")
|
||||
|
||||
columnWidths = _.map columns, (column) ->
|
||||
return $(column).outerWidth(true)
|
||||
|
||||
totalWidth = _.reduce columnWidths, (total, width) ->
|
||||
return total + width
|
||||
|
||||
$el.find('.taskboard-table-inner').css("width", totalWidth)
|
||||
|
||||
recalculateStatusColumnWidth = (statusId) =>
|
||||
statusFoldedWidth = 0
|
||||
|
||||
_.forEach $scope.userstories, (us) ->
|
||||
width = getCeilWidth(us.id, statusId)
|
||||
|
||||
statusFoldedWidth = width if width > statusFoldedWidth
|
||||
|
||||
setStatusColumnWidth(statusId, statusFoldedWidth)
|
||||
|
||||
recalculateTaskboardWidth = () =>
|
||||
_.forEach $scope.taskStatusList, (status) ->
|
||||
recalculateStatusColumnWidth(status.id)
|
||||
|
||||
refreshTaskboardTableWidth()
|
||||
|
||||
return
|
||||
|
||||
return {link: link}
|
||||
|
||||
module.directive("tgTaskboardSquishColumn", ["$tgResources", TaskboardSquishColumnDirective])
|
||||
|
||||
#############################################################################
|
||||
## Taskboard User Directive
|
||||
#############################################################################
|
||||
|
||||
TaskboardUserDirective = ($log) ->
|
||||
template = _.template("""
|
||||
<figure class="avatar">
|
||||
<a href="#" title="Assign task" <% if (!clickable) {%>class="not-clickable"<% } %>>
|
||||
<img src="<%- imgurl %>" alt="<%- name %>">
|
||||
template = """
|
||||
<figure class="avatar avatar-assigned-to">
|
||||
<a href="#" title="Assign task" ng-class="{'not-clickable': !clickable}">
|
||||
<img ng-src="{{imgurl}}">
|
||||
</a>
|
||||
</figure>
|
||||
""") # TODO: i18n
|
||||
|
||||
<figure class="avatar avatar-task-link">
|
||||
<a tg-nav="project-tasks-detail:project=project.slug,ref=task.ref" ng-attr-title="{{task.subject}}">
|
||||
<img ng-src="{{imgurl}}">
|
||||
</a>
|
||||
</figure>
|
||||
""" # TODO: i18n
|
||||
|
||||
clickable = false
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
if not $attrs.tgTaskboardUserAvatar?
|
||||
return $log.error "TaskboardUserDirective: no attr is defined"
|
||||
link = ($scope, $el, $attrs) ->
|
||||
username_label = $el.parent().find("a.task-assigned")
|
||||
username_label.on "click", (event) ->
|
||||
if $el.find('a').hasClass('noclick')
|
||||
return
|
||||
|
||||
wtid = $scope.$watch $attrs.tgTaskboardUserAvatar, (v) ->
|
||||
if not $scope.usersById?
|
||||
$log.error "TaskboardUserDirective requires userById set in scope."
|
||||
wtid()
|
||||
else
|
||||
user = $scope.usersById[v]
|
||||
render(user)
|
||||
$ctrl = $el.controller()
|
||||
$ctrl.editTaskAssignedTo($scope.task)
|
||||
|
||||
$scope.$watch 'task.assigned_to', (assigned_to) ->
|
||||
user = $scope.usersById[assigned_to]
|
||||
|
||||
render = (user) ->
|
||||
if user is undefined
|
||||
ctx = {name: "Unassigned", imgurl: "/images/unnamed.png", clickable: clickable}
|
||||
_.assign($scope, {name: "Unassigned", imgurl: "/images/unnamed.png", clickable: clickable})
|
||||
else
|
||||
ctx = {name: user.full_name_display, imgurl: user.photo, clickable: clickable}
|
||||
_.assign($scope, {name: user.full_name_display, imgurl: user.photo, clickable: clickable})
|
||||
|
||||
html = template(ctx)
|
||||
$el.html(html)
|
||||
username_label = $el.parent().find("a.task-assigned")
|
||||
username_label.html(ctx.name)
|
||||
username_label.on "click", (event) ->
|
||||
if $el.find('a').hasClass('noclick')
|
||||
return
|
||||
username_label.text($scope.name)
|
||||
|
||||
us = $model.$modelValue
|
||||
$ctrl = $el.controller()
|
||||
$ctrl.editTaskAssignedTo(us)
|
||||
|
||||
bindOnce $scope, "project", (project) ->
|
||||
if project.my_permissions.indexOf("modify_task") > -1
|
||||
clickable = true
|
||||
$el.on "click", (event) =>
|
||||
$el.find(".avatar-assigned-to").on "click", (event) =>
|
||||
if $el.find('a').hasClass('noclick')
|
||||
return
|
||||
|
||||
us = $model.$modelValue
|
||||
$ctrl = $el.controller()
|
||||
$ctrl.editTaskAssignedTo(us)
|
||||
$ctrl.editTaskAssignedTo($scope.task)
|
||||
|
||||
return {link: link, require:"ngModel"}
|
||||
return {
|
||||
link: link,
|
||||
template: template,
|
||||
scope: {
|
||||
"usersById": "=users",
|
||||
"project": "=",
|
||||
"task": "=",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.directive("tgTaskboardUserAvatar", ["$log", TaskboardUserDirective])
|
||||
|
|
|
@ -199,7 +199,7 @@ module.directive("tgTaskStatusDisplay", TaskStatusDisplayDirective)
|
|||
## Task status button directive
|
||||
#############################################################################
|
||||
|
||||
TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
||||
TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue) ->
|
||||
# Display the status of Task and you can edit it.
|
||||
#
|
||||
# Example:
|
||||
|
@ -240,6 +240,26 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
|||
})
|
||||
$el.html(html)
|
||||
|
||||
save = $qqueue.bindAdd (status) =>
|
||||
task = $model.$modelValue.clone()
|
||||
task.status = status
|
||||
|
||||
$model.$setViewValue(task)
|
||||
|
||||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("history:reload")
|
||||
$loading.finish($el.find(".level-name"))
|
||||
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
task.revert()
|
||||
$model.$setViewValue(task)
|
||||
$loading.finish($el.find(".level-name"))
|
||||
|
||||
$loading.start($el.find(".level-name"))
|
||||
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||
|
||||
$el.on "click", ".status-data", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
@ -256,25 +276,7 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
|||
|
||||
$.fn.popover().closeAll()
|
||||
|
||||
task = $model.$modelValue.clone()
|
||||
task.status = target.data("status-id")
|
||||
$model.$setViewValue(task)
|
||||
|
||||
$scope.$apply()
|
||||
|
||||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("history:reload")
|
||||
$loading.finish($el.find(".level-name"))
|
||||
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
task.revert()
|
||||
$model.$setViewValue(task)
|
||||
$loading.finish($el.find(".level-name"))
|
||||
|
||||
$loading.start($el.find(".level-name"))
|
||||
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||
save(target.data("status-id"))
|
||||
|
||||
$scope.$watch $attrs.ngModel, (task) ->
|
||||
render(task) if task
|
||||
|
@ -288,11 +290,11 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
|||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgTaskStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading",
|
||||
module.directive("tgTaskStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue",
|
||||
TaskStatusButtonDirective])
|
||||
|
||||
|
||||
TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
|
||||
TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue) ->
|
||||
template = _.template("""
|
||||
<fieldset title="Feeling a bit overwhelmed by a task? Make sure others know about it by clicking on Iocaine when editing a task. It's possible to become immune to this (fictional) deadly poison by consuming small amounts over time just as it's possible to get better at what you do by occasionally taking on extra challenges!">
|
||||
<label for="is-iocaine"
|
||||
|
@ -319,15 +321,15 @@ TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
|
|||
html = template(ctx)
|
||||
$el.html(html)
|
||||
|
||||
$el.on "click", ".is-iocaine", (event) ->
|
||||
return if not isEditable()
|
||||
|
||||
save = $qqueue.bindAdd (is_iocaine) =>
|
||||
task = $model.$modelValue.clone()
|
||||
task.is_iocaine = not task.is_iocaine
|
||||
task.is_iocaine = is_iocaine
|
||||
|
||||
$model.$setViewValue(task)
|
||||
$loading.start($el.find('label'))
|
||||
|
||||
promise = $tgrepo.save($model.$modelValue)
|
||||
promise = $tgrepo.save(task)
|
||||
|
||||
promise.then ->
|
||||
$confirm.notify("success")
|
||||
$rootscope.$broadcast("history:reload")
|
||||
|
@ -340,6 +342,12 @@ TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
|
|||
promise.finally ->
|
||||
$loading.finish($el.find('label'))
|
||||
|
||||
$el.on "click", ".is-iocaine", (event) ->
|
||||
return if not isEditable()
|
||||
|
||||
is_iocaine = not $model.$modelValue.is_iocaine
|
||||
save(is_iocaine)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (task) ->
|
||||
render(task) if task
|
||||
|
||||
|
@ -352,4 +360,4 @@ TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
|
|||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgTaskIsIocaineButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", TaskIsIocaineButtonDirective])
|
||||
module.directive("tgTaskIsIocaineButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", TaskIsIocaineButtonDirective])
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
###
|
||||
# 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/team.coffee
|
||||
###
|
||||
|
||||
module = angular.module("taigaTeam", [])
|
|
@ -0,0 +1,307 @@
|
|||
###
|
||||
# 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/team/main.coffee
|
||||
###
|
||||
|
||||
taiga = @.taiga
|
||||
|
||||
mixOf = @.taiga.mixOf
|
||||
|
||||
module = angular.module("taigaTeam")
|
||||
|
||||
#############################################################################
|
||||
## Team Controller
|
||||
#############################################################################
|
||||
|
||||
class TeamController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||
@.$inject = [
|
||||
"$scope",
|
||||
"$rootScope",
|
||||
"$tgRepo",
|
||||
"$tgResources",
|
||||
"$routeParams",
|
||||
"$q",
|
||||
"$location",
|
||||
"$tgNavUrls",
|
||||
"$appTitle",
|
||||
"$tgAuth",
|
||||
"tgLoader"
|
||||
]
|
||||
|
||||
constructor: (@scope, @rootscope, @repo, @rs, @params, @q, @location, @navUrls, @appTitle, @auth, tgLoader) ->
|
||||
@scope.sectionName = "Team"
|
||||
|
||||
promise = @.loadInitialData()
|
||||
|
||||
# On Success
|
||||
promise.then =>
|
||||
#TODO: i18n
|
||||
@appTitle.set("Team - " + @scope.project.name)
|
||||
tgLoader.pageLoaded()
|
||||
|
||||
# On Error
|
||||
promise.then null, @.onInitialDataError.bind(@)
|
||||
|
||||
setRole: (role) ->
|
||||
if role
|
||||
@scope.filtersRole = role
|
||||
else
|
||||
@scope.filtersRole = ""
|
||||
|
||||
loadMembers: ->
|
||||
return @rs.memberships.list(@scope.projectId, {}, false).then (data) =>
|
||||
currentUser = @auth.getUser()
|
||||
if not currentUser.photo?
|
||||
currentUser.photo = "/images/unnamed.png"
|
||||
|
||||
@scope.currentUser = _.find data, (membership) =>
|
||||
return membership.user == currentUser.id
|
||||
|
||||
@scope.totals = {}
|
||||
_.forEach data, (membership) =>
|
||||
@scope.totals[membership.user] = 0
|
||||
|
||||
@scope.memberships = _.filter data, (membership) =>
|
||||
if membership.user && membership.user != currentUser.id
|
||||
return membership
|
||||
|
||||
for membership in @scope.memberships
|
||||
if not membership.photo?
|
||||
membership.photo = "/images/unnamed.png"
|
||||
|
||||
return data
|
||||
|
||||
loadProject: ->
|
||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||
@scope.project = project
|
||||
@scope.$emit('project:loaded', project)
|
||||
|
||||
@scope.issuesEnabled = project.is_issues_activated
|
||||
@scope.tasksEnabled = project.is_kanban_activated or project.is_backlog_activated
|
||||
@scope.wikiEnabled = project.is_wiki_activated
|
||||
|
||||
return project
|
||||
|
||||
loadMemberStats: ->
|
||||
return @rs.projects.memberStats(@scope.projectId).then (stats) =>
|
||||
totals = {}
|
||||
_.forEach @scope.totals, (total, userId) =>
|
||||
vals = _.map(stats, (memberStats, statsKey) -> memberStats[userId])
|
||||
total = _.reduce(vals, (sum, el) -> sum + el)
|
||||
@scope.totals[userId] = total
|
||||
|
||||
@scope.stats = @.processStats(stats)
|
||||
@scope.stats.totals = @scope.totals
|
||||
|
||||
processStat: (stat) ->
|
||||
max = _.max(stat)
|
||||
min = _.min(stat)
|
||||
singleStat = _.map stat, (value, key) ->
|
||||
if value == min
|
||||
return [key, 0.1]
|
||||
if value == max
|
||||
return [key, 1]
|
||||
return [key, (value * 0.5) / max]
|
||||
singleStat = _.object(singleStat)
|
||||
return singleStat
|
||||
|
||||
processStats: (stats) ->
|
||||
for key,value of stats
|
||||
stats[key] = @.processStat(value)
|
||||
return stats
|
||||
|
||||
loadInitialData: ->
|
||||
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
|
||||
@scope.projectId = data.project
|
||||
return data
|
||||
|
||||
return promise.then(=> @.loadProject())
|
||||
.then(=> @.loadUsersAndRoles())
|
||||
.then(=> @.loadMembers())
|
||||
.then(=> @.loadMemberStats())
|
||||
|
||||
module.controller("TeamController", TeamController)
|
||||
|
||||
#############################################################################
|
||||
## Team Filters Directive
|
||||
#############################################################################
|
||||
|
||||
TeamFiltersDirective = () ->
|
||||
template = """
|
||||
<ul>
|
||||
<li>
|
||||
<a ng-class="{active: !filtersRole.id}" ng-click="ctrl.setRole()" href="">
|
||||
<span class="title">All</span>
|
||||
<span class="icon icon-arrow-right"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-repeat="role in roles">
|
||||
<a ng-class="{active: role.id == filtersRole.id}" ng-click="ctrl.setRole(role)" href="">
|
||||
<span class="title" tg-bo-bind="role.name"></span>
|
||||
<span class="icon icon-arrow-right"></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
"""
|
||||
|
||||
return {
|
||||
template: template
|
||||
}
|
||||
|
||||
module.directive("tgTeamFilters", [TeamFiltersDirective])
|
||||
|
||||
#############################################################################
|
||||
## Team Member Stats Directive
|
||||
#############################################################################
|
||||
|
||||
TeamMemberStatsDirective = () ->
|
||||
template = """
|
||||
<div class="attribute" ng-if="issuesEnabled">
|
||||
<span class="icon icon-briefcase" ng-style="{'opacity': stats.closed_bugs[userId]}" ng-class="{'top': stats.closed_bugs[userId] == 1}"></span>
|
||||
</div>
|
||||
<div class="attribute" ng-if="tasksEnabled">
|
||||
<span class="icon icon-iocaine" ng-style="{'opacity': stats.iocaine_tasks[userId]}" ng-class="{'top': stats.iocaine_tasks[userId] == 1}"></span>
|
||||
</div>
|
||||
<div class="attribute" ng-if="wikiEnabled">
|
||||
<span class="icon icon-writer" ng-style="{'opacity': stats.wiki_changes[userId]}" ng-class="{'top': stats.wiki_changes[userId] == 1}"></span>
|
||||
</div>
|
||||
<div class="attribute" ng-if="issuesEnabled">
|
||||
<span class="icon icon-bug" ng-style="{'opacity': stats.created_bugs[userId]}" ng-class="{'top': stats.created_bugs[userId] == 1}"></span>
|
||||
</div>
|
||||
<div class="attribute" ng-if="tasksEnabled">
|
||||
<span class="icon icon-tasks" ng-style="{'opacity': stats.closed_tasks[userId]}" ng-class="{'top': stats.closed_tasks[userId] == 1}"></span>
|
||||
</div>
|
||||
<div class="attribute">
|
||||
<span class="points" ng-bind="stats.totals[userId]"></span>
|
||||
</div>
|
||||
"""
|
||||
return {
|
||||
template: template,
|
||||
scope: {
|
||||
stats: "=",
|
||||
userId: "=user"
|
||||
issuesEnabled: "=issuesenabled"
|
||||
tasksEnabled: "=tasksenabled"
|
||||
wikiEnabled: "=wikienabled"
|
||||
}
|
||||
}
|
||||
|
||||
module.directive("tgTeamMemberStats", TeamMemberStatsDirective)
|
||||
|
||||
#############################################################################
|
||||
## Team Current User Directive
|
||||
#############################################################################
|
||||
|
||||
TeamMemberCurrentUserDirective = () ->
|
||||
template = """
|
||||
<div class="row">
|
||||
<div class="username">
|
||||
<figure class="avatar">
|
||||
<img tg-bo-src="currentUser.photo" tg-bo-alt="currentUser.full_name" />
|
||||
<figcaption>
|
||||
<span class="name" tg-bo-bind="currentUser.full_name"></span>
|
||||
<span class="position" tg-bo-bind="currentUser.role_name"></span>
|
||||
<div tg-leave-project projectid="{{projectId}}"></div>
|
||||
</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="member-stats" tg-team-member-stats stats="stats" user="currentUser.user" issuesEnabled="issuesEnabled", tasksenabled="tasksEnabled", wikienabled="wikiEnabled"></div>
|
||||
</div>
|
||||
"""
|
||||
return {
|
||||
template: template
|
||||
scope: {
|
||||
projectId: "=projectid",
|
||||
currentUser: "=currentuser",
|
||||
stats: "="
|
||||
issuesEnabled: "=issuesenabled"
|
||||
tasksEnabled: "=tasksenabled"
|
||||
wikiEnabled: "=wikienabled"
|
||||
}
|
||||
}
|
||||
|
||||
module.directive("tgTeamCurrentUser", TeamMemberCurrentUserDirective)
|
||||
|
||||
#############################################################################
|
||||
## Team Members Directive
|
||||
#############################################################################
|
||||
|
||||
TeamMembersDirective = () ->
|
||||
template = """
|
||||
<div class="row member" ng-repeat="user in memberships | filter:filtersQ | filter:{role: filtersRole.id}">
|
||||
<div class="username">
|
||||
<figure class="avatar">
|
||||
<img tg-bo-src="user.photo" tg-bo-alt="user.full_name" />
|
||||
<figcaption>
|
||||
<span class="name" tg-bo-bind="user.full_name"></span>
|
||||
<span class="position" tg-bo-bind="user.role_name"></span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="member-stats" tg-team-member-stats stats="stats" user="user.user" issuesEnabled="issuesEnabled", tasksenabled="tasksEnabled", wikienabled="wikiEnabled"></div>
|
||||
</div>
|
||||
"""
|
||||
return {
|
||||
template: template
|
||||
scope: {
|
||||
memberships: "=",
|
||||
filtersQ: "=filtersq",
|
||||
filtersRole: "=filtersrole",
|
||||
stats: "="
|
||||
issuesEnabled: "=issuesenabled"
|
||||
tasksEnabled: "=tasksenabled"
|
||||
wikiEnabled: "=wikienabled"
|
||||
}
|
||||
}
|
||||
|
||||
module.directive("tgTeamMembers", TeamMembersDirective)
|
||||
|
||||
#############################################################################
|
||||
## Leave project Directive
|
||||
#############################################################################
|
||||
|
||||
LeaveProjectDirective = ($repo, $confirm, $location, $rs, $navurls) ->
|
||||
template= """
|
||||
<a ng-click="leave()" href="" class="leave-project">
|
||||
<span class="icon icon-delete"></span>Leave this project
|
||||
</a>
|
||||
""" #TODO: i18n
|
||||
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$scope.leave = () ->
|
||||
#TODO: i18n
|
||||
$confirm.ask("Leave this project", "Are you sure you want to leave the project?").then (finish) =>
|
||||
promise = $rs.projects.leave($attrs.projectid)
|
||||
|
||||
promise.then =>
|
||||
finish()
|
||||
$confirm.notify("success")
|
||||
$location.path($navurls.resolve("home"))
|
||||
|
||||
promise.then null, (response) ->
|
||||
finish()
|
||||
$confirm.notify('error', response.data._error_message)
|
||||
|
||||
return {
|
||||
scope: {},
|
||||
template: template,
|
||||
link: link
|
||||
}
|
||||
|
||||
module.directive("tgLeaveProject", ["$tgRepo", "$tgConfirm", "$tgLocation", "$tgResources", "$tgNavUrls", LeaveProjectDirective])
|
|
@ -22,6 +22,7 @@
|
|||
taiga = @.taiga
|
||||
|
||||
mixOf = @.taiga.mixOf
|
||||
debounce = @.taiga.debounce
|
||||
|
||||
module = angular.module("taigaUserSettings")
|
||||
|
||||
|
@ -66,18 +67,6 @@ class UserChangePasswordController extends mixOf(taiga.Controller, taiga.PageMix
|
|||
|
||||
return promise.then(=> @.loadProject())
|
||||
|
||||
save: ->
|
||||
if @scope.newPassword1 != @scope.newPassword2
|
||||
@confirm.notify('error', "The passwords dosn't match")
|
||||
return
|
||||
|
||||
promise = @rs.userSettings.changePassword(@scope.currentPassword, @scope.newPassword1)
|
||||
promise.then =>
|
||||
@confirm.notify('success')
|
||||
promise.then null, (response) =>
|
||||
@confirm.notify('error', response.data._error_message)
|
||||
|
||||
|
||||
module.controller("UserChangePasswordController", UserChangePasswordController)
|
||||
|
||||
|
||||
|
@ -85,11 +74,36 @@ module.controller("UserChangePasswordController", UserChangePasswordController)
|
|||
## User ChangePassword Directive
|
||||
#############################################################################
|
||||
|
||||
UserChangePasswordDirective = () ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
UserChangePasswordDirective = ($rs, $confirm, $loading) ->
|
||||
link = ($scope, $el, $attrs, ctrl) ->
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
if $scope.newPassword1 != $scope.newPassword2
|
||||
$confirm.notify('error', "The passwords dosn't match")
|
||||
return
|
||||
|
||||
$loading.start(submitButton)
|
||||
|
||||
promise = $rs.userSettings.changePassword($scope.currentPassword, $scope.newPassword1)
|
||||
promise.then =>
|
||||
$loading.finish(submitButton)
|
||||
$confirm.notify('success')
|
||||
|
||||
promise.then null, (response) =>
|
||||
$loading.finish(submitButton)
|
||||
$confirm.notify('error', response.data._error_message)
|
||||
|
||||
submitButton = $el.find(".submit-button")
|
||||
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {link:link}
|
||||
return {
|
||||
link:link
|
||||
}
|
||||
|
||||
module.directive("tgUserChangePassword", UserChangePasswordDirective)
|
||||
module.directive("tgUserChangePassword", ["$tgResources", "$tgConfirm", "$tgLoading", UserChangePasswordDirective])
|
||||
|
|
|
@ -23,7 +23,7 @@ taiga = @.taiga
|
|||
mixOf = @.taiga.mixOf
|
||||
sizeFormat = @.taiga.sizeFormat
|
||||
module = angular.module("taigaUserSettings")
|
||||
|
||||
debounce = @.taiga.debounce
|
||||
|
||||
#############################################################################
|
||||
## User settings Controller
|
||||
|
@ -82,7 +82,9 @@ module.controller("UserSettingsController", UserSettingsController)
|
|||
|
||||
UserProfileDirective = ($confirm, $auth, $repo) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$el.on "click", ".user-profile form .save-profile", (event) ->
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
form = $el.find("form").checksley()
|
||||
return if not form.validate()
|
||||
|
||||
|
@ -103,6 +105,10 @@ UserProfileDirective = ($confirm, $auth, $repo) ->
|
|||
|
||||
$repo.save($scope.user).then(onSuccess, onError)
|
||||
|
||||
$el.on "submit", "form", submit
|
||||
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
|
|
|
@ -259,7 +259,7 @@ module.directive("tgUsTasksProgressDisplay", UsTasksProgressDisplayDirective)
|
|||
## User story status button directive
|
||||
#############################################################################
|
||||
|
||||
UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
||||
UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue) ->
|
||||
# Display the status of a US and you can edit it.
|
||||
#
|
||||
# Example:
|
||||
|
@ -300,28 +300,15 @@ UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
|||
})
|
||||
$el.html(html)
|
||||
|
||||
$el.on "click", ".status-data", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return if not isEditable()
|
||||
|
||||
$el.find(".pop-status").popover().open()
|
||||
|
||||
$el.on "click", ".status", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return if not isEditable()
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
save = $qqueue.bindAdd (status) =>
|
||||
us = $model.$modelValue.clone()
|
||||
us.status = status
|
||||
|
||||
$.fn.popover().closeAll()
|
||||
|
||||
us = $model.$modelValue.clone()
|
||||
us.status = target.data("status-id")
|
||||
$model.$setViewValue(us)
|
||||
|
||||
$scope.$apply()
|
||||
|
||||
onSuccess = ->
|
||||
$confirm.notify("success")
|
||||
$rootScope.$broadcast("history:reload")
|
||||
|
@ -334,8 +321,26 @@ UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
|||
$loading.finish($el.find(".level-name"))
|
||||
|
||||
$loading.start($el.find(".level-name"))
|
||||
|
||||
$repo.save($model.$modelValue).then(onSuccess, onError)
|
||||
|
||||
$el.on "click", ".status-data", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return if not isEditable()
|
||||
|
||||
$el.find(".pop-status").popover().open()
|
||||
|
||||
$el.on "click", ".status", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return if not isEditable()
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
status = target.data("status-id")
|
||||
|
||||
save(status)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (us) ->
|
||||
render(us) if us
|
||||
|
||||
|
@ -348,7 +353,7 @@ UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
|
|||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgUsStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading",
|
||||
module.directive("tgUsStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading","$tgQqueue",
|
||||
UsStatusButtonDirective])
|
||||
|
||||
|
||||
|
@ -356,7 +361,7 @@ module.directive("tgUsStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$t
|
|||
## User story team requirements button directive
|
||||
#############################################################################
|
||||
|
||||
UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
|
||||
UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue) ->
|
||||
template = _.template("""
|
||||
<label for="team-requirement"
|
||||
class="button button-gray team-requirement <% if(canEdit){ %>editable<% }; %> <% if(isRequired){ %>active<% }; %>">
|
||||
|
@ -381,24 +386,32 @@ UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
|
|||
html = template(ctx)
|
||||
$el.html(html)
|
||||
|
||||
$el.on "click", ".team-requirement", (event) ->
|
||||
return if not canEdit()
|
||||
|
||||
save = $qqueue.bindAdd (team_requirement) =>
|
||||
us = $model.$modelValue.clone()
|
||||
us.team_requirement = not us.team_requirement
|
||||
us.team_requirement = team_requirement
|
||||
|
||||
$model.$setViewValue(us)
|
||||
|
||||
$loading.start($el.find("label"))
|
||||
|
||||
promise = $tgrepo.save($model.$modelValue)
|
||||
promise.then =>
|
||||
$loading.finish($el.find("label"))
|
||||
$rootscope.$broadcast("history:reload")
|
||||
|
||||
promise.then null, ->
|
||||
$loading.finish($el.find("label"))
|
||||
$confirm.notify("error")
|
||||
us.revert()
|
||||
$model.$setViewValue(us)
|
||||
|
||||
$el.on "click", ".team-requirement", (event) ->
|
||||
return if not canEdit()
|
||||
|
||||
team_requirement = not $model.$modelValue.team_requirement
|
||||
|
||||
save(team_requirement)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (us) ->
|
||||
render(us) if us
|
||||
|
||||
|
@ -411,13 +424,13 @@ UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
|
|||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgUsTeamRequirementButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", UsTeamRequirementButtonDirective])
|
||||
module.directive("tgUsTeamRequirementButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", UsTeamRequirementButtonDirective])
|
||||
|
||||
#############################################################################
|
||||
## User story client requirements button directive
|
||||
#############################################################################
|
||||
|
||||
UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
|
||||
UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue) ->
|
||||
template = _.template("""
|
||||
<label for="client-requirement"
|
||||
class="button button-gray client-requirement <% if(canEdit){ %>editable<% }; %> <% if(isRequired){ %>active<% }; %>">
|
||||
|
@ -442,11 +455,10 @@ UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) -
|
|||
html = template(ctx)
|
||||
$el.html(html)
|
||||
|
||||
$el.on "click", ".client-requirement", (event) ->
|
||||
return if not canEdit()
|
||||
|
||||
save = $qqueue.bindAdd (client_requirement) =>
|
||||
us = $model.$modelValue.clone()
|
||||
us.client_requirement = not us.client_requirement
|
||||
us.client_requirement = client_requirement
|
||||
|
||||
$model.$setViewValue(us)
|
||||
|
||||
$loading.start($el.find("label"))
|
||||
|
@ -460,6 +472,12 @@ UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) -
|
|||
us.revert()
|
||||
$model.$setViewValue(us)
|
||||
|
||||
$el.on "click", ".client-requirement", (event) ->
|
||||
return if not canEdit()
|
||||
|
||||
client_requirement = not $model.$modelValue.client_requirement
|
||||
save(client_requirement)
|
||||
|
||||
$scope.$watch $attrs.ngModel, (us) ->
|
||||
render(us) if us
|
||||
|
||||
|
@ -472,5 +490,5 @@ UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) -
|
|||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgUsClientRequirementButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading",
|
||||
module.directive("tgUsClientRequirementButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue",
|
||||
UsClientRequirementButtonDirective])
|
||||
|
|
|
@ -212,17 +212,20 @@ module.directive("tgWikiSummary", ["$log", WikiSummaryDirective])
|
|||
#############################################################################
|
||||
|
||||
EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $location, $navUrls,
|
||||
$analytics) ->
|
||||
$analytics, $qqueue) ->
|
||||
template = """
|
||||
<div class="view-wiki-content">
|
||||
<section class="wysiwyg"
|
||||
tg-bind-html="wiki.html"></section>
|
||||
<section class="wysiwyg" tg-bind-html="wiki.html"></section>
|
||||
<span class="edit icon icon-edit" title="Edit"></span>
|
||||
</div>
|
||||
<div class="edit-wiki-content" style="display: none;">
|
||||
<textarea placeholder="Write your wiki page here"
|
||||
ng-model="wiki.content"
|
||||
tg-markitup="tg-markitup"></textarea>
|
||||
<a class="help-markdown" href="https://taiga.io/support/taiga-markdown-syntax/" target="_blank" title="Mardown syntax help">
|
||||
<span class="icon icon-help"></span>
|
||||
<span>Markdown syntax help</span>
|
||||
</a>
|
||||
<span class="action-container">
|
||||
<a class="save icon icon-floppy" href="" title="Save" />
|
||||
<a class="cancel icon icon-delete" href="" title="Cancel" />
|
||||
|
@ -262,6 +265,29 @@ EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $
|
|||
return $document.selection.createRange().text
|
||||
return null
|
||||
|
||||
save = $qqueue.bindAdd (wiki) ->
|
||||
onSuccess = (wikiPage) ->
|
||||
if not wiki.id?
|
||||
$analytics.trackEvent("wikipage", "create", "create wiki page", 1)
|
||||
|
||||
$scope.wiki = wikiPage
|
||||
$model.setModelValue = wiki
|
||||
$confirm.notify("success")
|
||||
switchToReadMode()
|
||||
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
|
||||
$loading.start($el.find('.save-container'))
|
||||
|
||||
if wiki.id?
|
||||
promise = $repo.save(wiki).then(onSuccess, onError)
|
||||
else
|
||||
promise = $repo.create("wiki", wiki).then(onSuccess, onError)
|
||||
|
||||
promise.finally ->
|
||||
$loading.finish($el.find('.save-container'))
|
||||
|
||||
$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
|
||||
|
@ -272,25 +298,7 @@ EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $
|
|||
switchToEditMode()
|
||||
|
||||
$el.on "click", ".save", debounce 2000, ->
|
||||
onSuccess = (wikiPage) ->
|
||||
if not $scope.wiki.id?
|
||||
$analytics.trackEvent("wikipage", "create", "create wiki page", 1)
|
||||
|
||||
$scope.wiki = wikiPage
|
||||
$model.setModelValue = $scope.wiki
|
||||
$confirm.notify("success")
|
||||
switchToReadMode()
|
||||
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
|
||||
$loading.start($el.find('.save-container'))
|
||||
if $scope.wiki.id?
|
||||
promise = $repo.save($scope.wiki).then(onSuccess, onError)
|
||||
else
|
||||
promise = $repo.create("wiki", $scope.wiki).then(onSuccess, onError)
|
||||
promise.finally ->
|
||||
$loading.finish($el.find('.save-container'))
|
||||
save($scope.wiki)
|
||||
|
||||
$el.on "click", ".cancel", ->
|
||||
cancelEdition()
|
||||
|
@ -321,5 +329,5 @@ EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $
|
|||
}
|
||||
|
||||
module.directive("tgEditableWikiContent", ["$window", "$document", "$tgRepo", "$tgConfirm", "$tgLoading",
|
||||
"$tgLocation", "$tgNavUrls", "$tgAnalytics",
|
||||
"$tgLocation", "$tgNavUrls", "$tgAnalytics", "$tgQqueue",
|
||||
EditableWikiContentDirective])
|
||||
|
|
|
@ -23,6 +23,17 @@ nl2br = (str) =>
|
|||
breakTag = '<br />'
|
||||
return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + breakTag + '$2')
|
||||
|
||||
bindMethods = (object) =>
|
||||
dependencies = _.keys(object)
|
||||
|
||||
methods = []
|
||||
|
||||
_.forIn object, (value, key) =>
|
||||
if key not in dependencies
|
||||
methods.push(key)
|
||||
|
||||
_.bindAll(object, methods)
|
||||
|
||||
bindOnce = (scope, attr, continuation) =>
|
||||
val = scope.$eval(attr)
|
||||
if val != undefined
|
||||
|
@ -106,9 +117,11 @@ joinStr = (str, coll) ->
|
|||
debounce = (wait, func) ->
|
||||
return _.debounce(func, wait, {leading: true, trailing: false})
|
||||
|
||||
|
||||
debounceLeading = (wait, func) ->
|
||||
return _.debounce(func, wait, {leading: false, trailing: true})
|
||||
|
||||
|
||||
startswith = (str1, str2) ->
|
||||
return _.str.startsWith(str1, str2)
|
||||
|
||||
|
@ -130,6 +143,7 @@ sizeFormat = (input, precision=1) ->
|
|||
|
||||
taiga = @.taiga
|
||||
taiga.nl2br = nl2br
|
||||
taiga.bindMethods = bindMethods
|
||||
taiga.bindOnce = bindOnce
|
||||
taiga.mixOf = mixOf
|
||||
taiga.trim = trim
|
||||
|
|
Binary file not shown.
|
@ -49,4 +49,9 @@
|
|||
<glyph unicode="N" d="M125 68l40-41 91 91 91-91 40 41-131 131z m262 376l-40 41-91-91-91 91-40-41 131-131z"/>
|
||||
<glyph unicode="O" d="M256 435l87-88 39 39-126 126-126-126 39-39z m0-358l-87 88-39-39 126-126 126 126-39 39z"/>
|
||||
<glyph unicode="P" d="M427 491l-363 0c-35 0-64-29-64-64l0-256c0-36 43-43 64-43l0-128 192 128 171 0c35 0 64 29 64 64l0 235c0 35-29 64-64 64z m21-299c0-12-10-21-21-21l-192 0-128-107 0 107-43 0c-12 0-21 9-21 21l0 235c0 11 9 21 21 21l363 0c11 0 21-10 21-21z"/>
|
||||
<glyph unicode="Q" d="M194 434c-26-29-19-74-19-74 0 0 27-32 80-32 53 0 80 32 80 32 0 0 7 45-19 73 17 9 26 22 22 31-4 10-24 11-44 2-7-3-14-7-18-12-7 1-13 2-21 2-7 0-13-1-19-2-5 5-11 9-18 12-20 9-40 8-44-2-4-9 5-21 20-30z m201-207c-4 1-8 1-12 2 0 1 0 2 0 3 0 17-2 33-6 48 8-1 19 1 29 6 20 9 33 24 28 34-4 10-24 11-44 2-9-4-17-10-22-15-4 9-8 18-13 25-15-12-44-31-84-35l0-161c0 0 0-16-16-16-16 0-16 16-16 16l0 161c-40 4-68 23-83 35-5-7-9-16-13-24-5 5-13 10-21 14-20 9-40 8-44-2-5-10 8-25 28-34 10-4 19-6 27-6-3-15-6-31-6-48 0-1 0-2 0-3-3-1-6-1-10-2-26-5-46-19-44-30 1-11 23-16 49-10 4 1 7 1 10 2 5-20 12-39 22-55-6-3-12-8-18-14-16-16-22-35-14-43 8-8 27-1 43 15 5 4 9 9 11 14 22-21 49-34 79-34 30 0 58 13 80 35 3-5 7-10 12-15 16-16 35-23 43-15 8 8 2 27-14 43-7 7-13 11-20 14 10 17 18 36 22 56 4-1 8-2 12-3 26-6 48-1 49 10 2 11-18 25-44 30z"/>
|
||||
<glyph unicode="R" d="M183 439l146 0 0 36-146 0z m329-183l0-137c0-13-4-23-13-32-9-9-20-14-33-14l-420 0c-13 0-24 5-33 14-9 9-13 19-13 32l0 137 192 0 0-46c0-5 2-9 5-13 4-3 8-5 13-5l92 0c5 0 9 2 13 5 3 4 5 8 5 13l0 46z m-219 0l0-37-74 0 0 37z m219 137l0-110-512 0 0 110c0 13 4 23 13 32 9 9 20 14 33 14l100 0 0 46c0 7 3 14 8 19 6 5 12 8 20 8l164 0c8 0 14-3 20-8 5-5 8-12 8-19l0-46 100 0c13 0 24-5 33-14 9-9 13-19 13-32z"/>
|
||||
<glyph unicode="S" d="M107 6c-2-7-7-8-14-4-6 3-8 9-8 17 2 35 10 73 26 116-34 53-43 107-27 162 4-11 9-24 17-40 7-16 15-29 22-41 8-12 13-17 17-15 2 1 2 15 0 42-3 27-5 55-6 85-1 30 3 57 13 81 7 15 21 31 41 48 19 17 37 29 53 36-8-16-14-32-17-49-3-16-4-29-2-40 2-10 5-15 11-16 4 0 18 21 43 62 24 40 42 61 54 62 16 1 35-4 58-15 24-11 38-22 42-33 4-8 4-22 0-41-4-18-11-33-20-42-15-15-40-26-75-32-35-6-54-10-58-12-6-4-4-9 6-18 18-16 48-19 90-10-19-27-42-47-70-58-27-12-49-18-67-20-18-1-27-3-28-5-1-8 7-17 25-27 18-11 36-13 52-8-10-19-21-33-32-43-12-9-21-15-28-17-7-3-20-5-39-6-19-1-33-3-43-4 0 0-36-115-36-115"/>
|
||||
<glyph unicode="T" d="M433 458l-106 0c-10 29-38 51-71 51-33 0-61-22-71-51l-106 0c-28 0-50-22-50-50l0-354c0-28 22-51 50-51l354 0c28 0 50 23 50 51l0 354c0 28-22 50-50 50z m-177 0c14 0 25-11 25-25 0-14-11-25-25-25-14 0-25 11-25 25 0 14 11 25 25 25z m-51-354l-101 101 36 36 65-65 167 166 36-35z"/>
|
||||
<glyph unicode="U" d="M293 119l0 55c0 2-1 5-3 6-2 2-4 3-7 3l-54 0c-3 0-5-1-7-3-2-1-3-4-3-6l0-55c0-3 1-5 3-7 2-1 4-2 7-2l54 0c3 0 5 1 7 2 2 2 3 4 3 7z m73 192c0 17-6 32-16 46-11 15-24 26-40 34-16 7-32 11-48 11-47 0-82-20-106-61-3-4-2-8 2-12l38-28c1-1 3-2 5-2 3 0 6 1 7 4 10 13 19 21 25 26 6 4 15 7 24 7 10 0 18-3 25-8 7-5 11-10 11-17 0-7-2-13-6-17-4-4-10-9-20-13-12-5-23-13-33-25-10-11-15-23-15-35l0-11c0-2 1-5 3-6 2-2 4-3 7-3l54 0c3 0 5 1 7 3 2 1 3 4 3 6 0 4 2 9 6 14 4 6 9 11 15 15 6 3 11 6 14 8 4 2 8 5 13 10 6 4 10 9 13 13 3 5 6 11 8 18 3 7 4 14 4 23z m109-55c0-40-9-77-29-110-20-34-46-60-80-80-33-20-70-29-110-29-40 0-77 9-110 29-34 20-60 46-80 80-20 33-29 70-29 110 0 40 9 77 29 110 20 34 46 60 80 80 33 20 70 29 110 29 40 0 77-9 110-29 34-20 60-46 80-80 20-33 29-70 29-110z"/>
|
||||
</font></defs></svg>
|
||||
|
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 20 KiB |
Binary file not shown.
Binary file not shown.
|
@ -4,7 +4,7 @@ block head
|
|||
title Taiga Your agile, free, and open source project management tool
|
||||
|
||||
block content
|
||||
div.wrapper(tg-project-profile, ng-controller="ProjectProfileController as ctrl",
|
||||
div.wrapper(tg-project-default-values, ng-controller="ProjectProfileController as ctrl",
|
||||
ng-init="section='admin'; sectionName='Default values'")
|
||||
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile")
|
||||
include views/modules/admin-menu
|
||||
|
|
|
@ -93,5 +93,5 @@ block content
|
|||
option(value="") Select a videoconference system
|
||||
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")
|
||||
input(type="submit", class="hidden")
|
||||
a.button.button-green(href="") Save
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-green.submit-button(href="", title="Save") Save
|
||||
|
|
|
@ -54,8 +54,8 @@ block content
|
|||
|
||||
p All projects are private during Taiga's beta period.
|
||||
|
||||
input(type="submit", class="hidden")
|
||||
a.button.button-green(href="") 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
|
||||
|
||||
div.lightbox.lightbox-delete-project(tg-lb-delete-project)
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
block head
|
||||
title Taiga Your agile, free, and open source project management tool
|
||||
|
||||
block content
|
||||
div.wrapper.roles(tg-bitbucket-webhooks, ng-controller="BitbucketController as ctrl",
|
||||
ng-init="section='admin'")
|
||||
sidebar.menu-secondary.sidebar(tg-admin-navigation="third-parties")
|
||||
include views/modules/admin-menu
|
||||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="third-parties-bitbucket")
|
||||
include views/modules/admin-submenu-third-parties
|
||||
|
||||
section.main.admin-common.admin-third-parties
|
||||
include views/components/mainTitle
|
||||
|
||||
form
|
||||
fieldset
|
||||
label(for="secret-key") Secret key
|
||||
input(type="text", name="secret-key", ng-model="bitbucket.secret", placeholder="Secret key", id="secret-key")
|
||||
|
||||
fieldset
|
||||
.select-input-text(tg-select-input-text)
|
||||
div
|
||||
label(for="payload-url") Payload URL
|
||||
.field-with-option
|
||||
input(type="text", ng-model="bitbucket.webhooks_url", name="payload-url", readonly="readonly", placeholder="Payload URL", id="payload-url")
|
||||
.option-wrapper.select-input-content
|
||||
.icon.icon-copy
|
||||
.help-copy Copy to clipboard: Ctrl+C
|
||||
|
||||
fieldset
|
||||
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")
|
||||
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-green.submit-button(href="", title="Save") Save
|
||||
|
||||
|
||||
a.help-button(href="https://taiga.io/support/bitbucket-integration/", target="_blank")
|
||||
span.icon.icon-help
|
||||
span Do you need help? Check out our support page!
|
|
@ -27,69 +27,10 @@ block content
|
|||
.icon.icon-copy
|
||||
.help-copy Copy to clipboard: Ctrl+C
|
||||
|
||||
input(type="submit", class="hidden")
|
||||
a.button.button-green(href="") Save
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-green.submit-button(href="", title="Save") Save
|
||||
|
||||
|
||||
.help
|
||||
h2 How to use it
|
||||
|
||||
h3 Configure Taiga
|
||||
ol
|
||||
li Fill
|
||||
span Secret key
|
||||
| or use the auto generated one
|
||||
|
||||
li Copy the
|
||||
span Payload URL field.
|
||||
|
||||
h3 Configure Github
|
||||
ol
|
||||
li Go to your github repository.
|
||||
li Click on
|
||||
span Settings
|
||||
| >
|
||||
span Webhooks & Services
|
||||
| >
|
||||
span Add webhook
|
||||
|
||||
li On that screen set the payload url with the payload url of this screen.
|
||||
li Secret must be filled with the same content as the secret field of this screen.
|
||||
li Content type must be
|
||||
span application/json.
|
||||
li Taiga currently listen for three different kind of events:
|
||||
ol
|
||||
li Push events: changing element status via commit message
|
||||
li Issues: issues created in github appear automatically in Taiga
|
||||
li Issue comment: issue comments created in github appear automatically in Taiga
|
||||
|
||||
p Just check "send me everything" or just the events you want Taiga to listen for.
|
||||
|
||||
.img
|
||||
.alt-image Github Webhooke page
|
||||
img(src="/images/github-help.png", alt="webhook")
|
||||
|
||||
h2 Changing elements status via commit message
|
||||
|
||||
p
|
||||
| The status of any issue, task or user story can be changed via commit message.
|
||||
| Just add to your commit message something like:
|
||||
|
||||
code
|
||||
| TG-REF #STATUS
|
||||
|
||||
ul.code-info
|
||||
li
|
||||
span REF:
|
||||
| US/Issue/Task reference of the element you want to modify
|
||||
li
|
||||
span STATUS:
|
||||
| New status slug to set, you can find all of them in:
|
||||
a(href="", tg-nav="project-admin-project-values-us-status:project=project.slug") US STATUSES.
|
||||
|
||||
h3 An example please!
|
||||
|
||||
code
|
||||
| TG-123 #closed
|
||||
|
||||
p In this example, 123 is an issue reference and with this command, the issue will change its status to closed.
|
||||
a.help-button(href="https://taiga.io/support/github-integration/", target="_blank")
|
||||
span.icon.icon-help
|
||||
span Do you need help? Check out our support page!
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
block head
|
||||
title Taiga Your agile, free, and open source project management tool
|
||||
|
||||
block content
|
||||
div.wrapper.roles(tg-gitlab-webhooks, ng-controller="GitlabController as ctrl",
|
||||
ng-init="section='admin'")
|
||||
sidebar.menu-secondary.sidebar(tg-admin-navigation="third-parties")
|
||||
include views/modules/admin-menu
|
||||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="third-parties-gitlab")
|
||||
include views/modules/admin-submenu-third-parties
|
||||
|
||||
section.main.admin-common.admin-third-parties
|
||||
include views/components/mainTitle
|
||||
|
||||
form
|
||||
fieldset
|
||||
label(for="secret-key") Secret key
|
||||
input(type="text", name="secret-key", ng-model="gitlab.secret", placeholder="Secret key", id="secret-key")
|
||||
|
||||
fieldset
|
||||
.select-input-text(tg-select-input-text)
|
||||
div
|
||||
label(for="payload-url") Payload URL
|
||||
.field-with-option
|
||||
input(type="text", ng-model="gitlab.webhooks_url", name="payload-url", readonly="readonly", placeholder="Payload URL", id="payload-url")
|
||||
.option-wrapper.select-input-content
|
||||
.icon.icon-copy
|
||||
.help-copy Copy to clipboard: Ctrl+C
|
||||
|
||||
fieldset
|
||||
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")
|
||||
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-green.submit-button(href="", title="Save") Save
|
||||
|
||||
a.help-button(href="https://taiga.io/support/gitlab-integration/", target="_blank")
|
||||
span.icon.icon-help
|
||||
span Do you need help? Check out our support page!
|
|
@ -16,7 +16,7 @@ block content
|
|||
span.us-number(tg-bo-ref="issue.ref")
|
||||
span.us-name(tg-editable-subject, ng-model="issue", required-perm="modify_issue")
|
||||
|
||||
p.us-related-task(ng-if="issue.generated_user_stories") This issue has been promoted to US:
|
||||
p.us-related-task(ng-if="issue.generated_user_stories.length") This issue has been promoted to US:
|
||||
a(ng-repeat="us in issue.generated_user_stories",
|
||||
tg-check-permission="view_us", href="",
|
||||
tg-bo-title="'#' + us.ref + ' ' + us.subject",
|
||||
|
|
|
@ -5,7 +5,7 @@ block head
|
|||
|
||||
block content
|
||||
div.wrapper(ng-controller="TaskDetailController as ctrl",
|
||||
ng-init="section='backlog'")
|
||||
ng-init="section='backlog-kanban'")
|
||||
div.main.us-detail
|
||||
div.us-detail-header.header-with-actions
|
||||
include views/components/mainTitle
|
||||
|
|
|
@ -11,7 +11,7 @@ block content
|
|||
span(tg-bo-bind="project.name", class="project-name-short")
|
||||
span.green(tg-bo-bind="sprint.name")
|
||||
span.date(tg-date-range="sprint.estimated_start,sprint.estimated_finish")
|
||||
include views/components/sprint-summary
|
||||
//- include views/components/sprint-summary
|
||||
|
||||
div.graphics-container
|
||||
div.burndown(tg-sprint-graph)
|
||||
|
|
|
@ -5,7 +5,7 @@ block head
|
|||
|
||||
block content
|
||||
div.wrapper(ng-controller="UserStoryDetailController as ctrl",
|
||||
ng-init="section='backlog'")
|
||||
ng-init="section='backlog-kanban'")
|
||||
div.main.us-detail
|
||||
div.us-detail-header.header-with-actions
|
||||
include views/components/mainTitle
|
||||
|
|
|
@ -25,5 +25,5 @@ block content
|
|||
label(for="retype-password") Retype Password
|
||||
input(type="password", placeholder="Retype Password", id="retype-password", ng-model="newPassword2")
|
||||
fieldset
|
||||
input(type="submit", class="hidden")
|
||||
a.button.button-green(href="", ng-click="ctrl.save()") Save
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-green.submit-button(href="", title="Save") Save
|
||||
|
|
|
@ -55,8 +55,8 @@ block content
|
|||
ng-model="user.bio")
|
||||
|
||||
fieldset.submit
|
||||
input(type="submit", class="hidden")
|
||||
a.button.button-green.save-profile(href="") Save
|
||||
button(type="submit", title="Save", class="hidden")
|
||||
a.button.button-green.save-profile.submit-button(href="") Save
|
||||
a.delete-account(href="", title="Delete Taiga account",
|
||||
ng-click="ctrl.openDeleteLightbox()") Delete Taiga account
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
div.taskboard-tagline(tg-colorize-tags="task.tags", tg-colorize-tags-type="taskboard")
|
||||
div.taskboard-task-inner
|
||||
div.taskboard-user-avatar(tg-taskboard-user-avatar="task.assigned_to", ng-model="task",
|
||||
ng-class="{iocaine: task.is_iocaine}")
|
||||
div.taskboard-user-avatar(tg-taskboard-user-avatar, users="usersById", task="task", project="project", ng-class="{iocaine: task.is_iocaine}")
|
||||
span.icon.icon-iocaine(ng-if="task.is_iocaine", title="Feeling a bit overwhelmed by a task? Make sure others know about it by clicking on Iocaine when editing a task. It's possible to become immune to this (fictional) deadly poison by consuming small amounts over time just as it's possible to get better at what you do by occasionally taking on extra challenges!")
|
||||
p.taskboard-text
|
||||
a.task-assigned(href="", title="Assign task")
|
||||
|
|
|
@ -8,3 +8,11 @@ section.admin-submenu
|
|||
a(href="", tg-nav="project-admin-third-parties-github:project=project.slug")
|
||||
span.title Github
|
||||
span.icon.icon-arrow-right
|
||||
li#adminmenu-third-parties-gitlab
|
||||
a(href="", tg-nav="project-admin-third-parties-gitlab:project=project.slug")
|
||||
span.title Gitlab
|
||||
span.icon.icon-arrow-right
|
||||
li#adminmenu-third-parties-bitbucket
|
||||
a(href="", tg-nav="project-admin-third-parties-bitbucket:project=project.slug")
|
||||
span.title Bitbucket
|
||||
span.icon.icon-arrow-right
|
||||
|
|
|
@ -36,5 +36,5 @@ section.default-values
|
|||
ng-options="s.id as s.name for s in issueStatusList")
|
||||
|
||||
fieldset
|
||||
input(type="submit", class="hidden")
|
||||
a.button.button-green(href="", title="Save") Save
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-green.submit-button(href="", title="Save") Save
|
||||
|
|
|
@ -3,10 +3,10 @@ div.change-email-form-container(tg-cancel-account)
|
|||
strong Cancel your account <br />
|
||||
span We're sorry you are leaving the taiga, we hope you enjoyed your stay :)
|
||||
|
||||
form(ng-submit="ctrl.submit()")
|
||||
form
|
||||
fieldset
|
||||
input(type="hidden", name="cancel_token", ng-model="data.cancel_token", data-required="true",
|
||||
placeholder="cancel account token")
|
||||
|
||||
a.button.button-cancel-account.button-gray(href="", title="Yes, I'm leaving") Yes, I'm leaving!
|
||||
input(type="submit", style="display:none")
|
||||
button(type="submit", class="hidden")
|
||||
|
|
|
@ -3,10 +3,10 @@ div.change-email-form-container(tg-change-email)
|
|||
strong Change your email <br />
|
||||
span One click more and your email will be updated!
|
||||
|
||||
form(ng-submit="ctrl.submit()")
|
||||
form
|
||||
fieldset
|
||||
input(type="hidden", name="email_token", ng-model="data.email_token", data-required="true",
|
||||
placeholder="change email token")
|
||||
|
||||
a.button.button-change-email.button-gray(href="", title="Change email") Change email
|
||||
input(type="submit", style="display:none")
|
||||
button(type="submit", class="hidden")
|
||||
|
|
|
@ -3,7 +3,7 @@ div.change-password-form-container(tg-change-password-from-recovery)
|
|||
strong Create a new Taiga pass <br />
|
||||
span And hey, you may want to eat some more iron-rich food, it's good for your brain :P
|
||||
|
||||
form(ng-submit="ctrl.submit()")
|
||||
form
|
||||
fieldset.token-change-password(ng-hide="tokenInParams")
|
||||
input(type="text", name="token", ng-model="data.token", data-required="true",
|
||||
placeholder="Recover password token")
|
||||
|
@ -16,5 +16,5 @@ div.change-password-form-container(tg-change-password-from-recovery)
|
|||
input(type="password", name="password2", id="password2", ng-model="data.password2",
|
||||
data-required="true", data-equalto="#password", placeholder="Re-type new password")
|
||||
fieldset
|
||||
a.button.button-change-password.button-gray(href="", title="Reset Password") Reset Password
|
||||
input(type="submit", style="display:none")
|
||||
a.button.button-change-password.button-gray.submit-button(href="", title="Reset Password") Reset Password
|
||||
button(type="submit", class="hidden")
|
||||
|
|
|
@ -10,7 +10,7 @@ div.forgot-form-container(tg-forgot-password)
|
|||
input(type="text", name="username", ng-model="data.username", data-required="true",
|
||||
placeholder="Username or email")
|
||||
fieldset
|
||||
a.button.button-forgot.button-gray(href="", title="Reset Password") Reset Password
|
||||
input(type="submit", style="display:none")
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-gray.submit-button.button-forgot(href="", title="Reset Password") Reset Password
|
||||
|
||||
a(href="", title="Login", tg-nav="login") Nah, take me back. I think I remember it.
|
||||
|
|
|
@ -8,7 +8,7 @@ form.login-form
|
|||
placeholder="Password")
|
||||
a.forgot-pass(href="", tg-nav="forgot-password", title="Did you forgot your password?") Forgot it?
|
||||
fieldset
|
||||
a.button.button-login.button-gray(href="", title="Log in") Enter
|
||||
input(type="submit", style="display:none")
|
||||
a.button.button-login.button-gray.submit-button(href="", title="Log in") Enter
|
||||
button(type="submit", class="hidden")
|
||||
|
||||
fieldset(tg-github-login-button)
|
||||
|
|
|
@ -20,7 +20,7 @@ form.register-form
|
|||
placeholder="Set a password")
|
||||
|
||||
fieldset
|
||||
a.button.button-register.button-gray(href="", title="Sign up") Sign up
|
||||
input(type="submit", style="display:none")
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-register.button-gray.submit-button(href="", title="Sign up") Sign up
|
||||
|
||||
tg-terms-notice
|
||||
|
|
|
@ -6,7 +6,9 @@ form
|
|||
//- Form is set in a directive
|
||||
.add-member-forms
|
||||
|
||||
a.button.button-green(href="", title="Save")
|
||||
span Create
|
||||
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-green.submit-button(href="", title="Create")
|
||||
span Create
|
||||
|
||||
p.help-text If users are already registered on Taiga, they will be added automatically. Otherwise they will receive an invitation.
|
||||
|
|
|
@ -20,6 +20,6 @@ form
|
|||
textarea.description(placeholder="Description", ng-model="issue.description")
|
||||
|
||||
// include lightbox-attachments
|
||||
input(type="submit", style="display:none")
|
||||
a.button.button-green(href="", title="Save")
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-green.submit-button(href="", title="Save")
|
||||
span Create
|
||||
|
|
|
@ -6,6 +6,6 @@ form
|
|||
textarea(ng-model="feedback.comment", data-required="true",
|
||||
placeholder="...a bug, some suggestions, something cool... or even your worst nightmare with Taiga")
|
||||
fieldset
|
||||
input.hidden(type="submit")
|
||||
a.button.button-green(href="", title="Send feedback")
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-green.submit-button(href="", title="Send feedback")
|
||||
span Send feedback
|
||||
|
|
|
@ -4,5 +4,6 @@ form
|
|||
h2.title(tr="common.new-bulk")
|
||||
fieldset
|
||||
textarea(cols="200", wrap="off", tg-limit-line-length, tr="placeholder:common.one-item-line", ng-model="new.bulk", data-required="true", data-linewidth="200")
|
||||
a.button.button-green(href="", tr="title:common.save")
|
||||
span(tr="common.save")
|
||||
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-green.submit-button(href="", title="Save") Save
|
||||
|
|
|
@ -5,6 +5,5 @@ form
|
|||
fieldset
|
||||
input(type="text", name="text", id="search-text", placeholder="What are you looking for?", data-required="true")
|
||||
fieldset
|
||||
input.hidden(type="submit")
|
||||
a.button.button-green(href="", title="Accept")
|
||||
span Search
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-green.submit-button(href="", title="Search") Search
|
|
@ -15,7 +15,8 @@ form
|
|||
input.date-end(type="text", name="estimated_finish", placeholder="Estimated End",
|
||||
ng-model="sprint.estimated_finish", data-required="true", tg-date-selector)
|
||||
|
||||
a.button.button-green(href="", title="Save")
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-green.submit-button(href="", title="Create")
|
||||
span Create
|
||||
|
||||
div(tg-check-permission="delete_milestone")
|
||||
|
|
|
@ -4,5 +4,6 @@ form
|
|||
h2.title(tr="common.new-bulk")
|
||||
fieldset
|
||||
textarea(cols="200", wrap="off", tg-limit-line-length, tr="placeholder:common.one-item-line", ng-model="form.data", data-required="true")
|
||||
a.button.button-green(href="", tr="title:common.save")
|
||||
span(tr="common.save")
|
||||
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-green.submit-button(href="", title="Save") Save
|
||||
|
|
|
@ -34,5 +34,6 @@ form
|
|||
|
||||
tg-blocking-message-input(watch="task.is_blocked", ng-model="task.blocked_note")
|
||||
|
||||
a.button.button-green(href="", title="Save")
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-green.submit-button(href="", title="Save")
|
||||
span Create
|
||||
|
|
|
@ -4,5 +4,6 @@ form
|
|||
h2.title(tr="common.new-bulk")
|
||||
fieldset
|
||||
textarea(cols="200", wrap="off", tg-limit-line-length, tr="placeholder:common.one-item-line", ng-model="new.bulk", data-required="true", data-linewidth="200")
|
||||
a.button.button-green(href="", tr="title:common.save")
|
||||
span(tr="common.save")
|
||||
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-green.submit-button(href="", title="Save") Save
|
|
@ -36,5 +36,5 @@ form
|
|||
|
||||
tg-blocking-message-input(watch="us.is_blocked", ng-model="us.blocked_note")
|
||||
|
||||
a.button.button-green(href="", title="Save")
|
||||
span Create
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-green.submit-button(href="", title="Submit") Create
|
||||
|
|
|
@ -10,8 +10,8 @@ div.login-form-container(tg-login)
|
|||
a.forgot-pass(href="", tg-nav="forgot-password", title="Did you forgot your password?") Forgot it?
|
||||
|
||||
fieldset
|
||||
a.button.button-login.button-gray(href="", title="Sign in") Sign in
|
||||
input(type="submit", style="display:none")
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-login.button-gray.submit-button(href="", title="Sign in") Sign in
|
||||
|
||||
fieldset(tg-github-login-button)
|
||||
|
||||
|
|
|
@ -21,8 +21,8 @@ div.register-form-container(tg-register)
|
|||
placeholder="Set a password (case sensitive)")
|
||||
|
||||
fieldset
|
||||
a.button.button-register.button-gray(href="", title="Sign up") Sign up
|
||||
input(type="submit", class="hidden")
|
||||
button(type="submit", class="hidden")
|
||||
a.button.button-register.button-gray.submit-button(href="", title="Sign up") Sign up
|
||||
|
||||
fieldset(tg-github-login-button)
|
||||
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
div.taskboard-table
|
||||
div.taskboard-table(tg-taskboard-squish-column)
|
||||
div.taskboard-table-header
|
||||
div.taskboard-table-inner(tg-taskboard-row-width-fixer)
|
||||
div.taskboard-table-inner
|
||||
h2.task-colum-name "User story"
|
||||
h2.task-colum-name(ng-repeat="s in taskStatusList track by s.id",
|
||||
ng-style="{'border-top-color':s.color}")
|
||||
h2.task-colum-name(ng-repeat="s in taskStatusList track by s.id", ng-style="{'border-top-color':s.color}", ng-class="{'column-fold':statusesFolded[s.id]}", class="squish-status-{{s.id}}", tg-bo-title="s.name")
|
||||
span(tg-bo-bind="s.name")
|
||||
a.icon.icon-vfold.hfold(href="", ng-click='foldStatus(s)', title="Fold Column", ng-class='{hidden:statusesFolded[s.id]}')
|
||||
a.icon.icon-vunfold.hunfold(href="", title="Unfold Column", ng-click='foldStatus(s)', ng-class='{hidden:!statusesFolded[s.id]}')
|
||||
|
||||
div.taskboard-table-body(tg-taskboard-table-height-fixer)
|
||||
div.taskboard-table-inner(tg-taskboard-row-width-fixer)
|
||||
div.task-row(ng-repeat="us in userstories track by us.id", ng-class="{blocked: us.is_blocked}")
|
||||
div.taskboard-table-inner
|
||||
div.task-row(ng-repeat="us in userstories track by us.id", ng-class="{blocked: us.is_blocked, 'row-fold':usFolded[us.id]}")
|
||||
div.taskboard-userstory-box.task-column(tg-bo-title="us.blocked_note")
|
||||
a.icon.icon-vfold.vfold(href="", title="Fold Row", ng-click='foldUs(us)', ng-class='{hidden:usFolded[us.id]}')
|
||||
a.icon.icon-vunfold.vunfold(href="", title="Unfold Row", ng-click='foldUs(us)', ng-class='{hidden:!usFolded[us.id]}')
|
||||
h3.us-title
|
||||
a(href="", tg-nav="project-userstories-detail:project=project.slug,ref=us.ref",
|
||||
tg-bo-title="'#' + us.ref + ' ' + us.subject")
|
||||
|
@ -18,22 +21,20 @@ div.taskboard-table
|
|||
p.points-value
|
||||
span(ng-bind="us.total_points")
|
||||
span points
|
||||
include ../components/addnewtask.jade
|
||||
|
||||
div.taskboard-tasks-box.task-column(ng-repeat="st in taskStatusList track by st.id",
|
||||
tg-taskboard-sortable)
|
||||
include ../components/addnewtask
|
||||
div.taskboard-tasks-box.task-column(ng-repeat="st in taskStatusList track by st.id", tg-taskboard-sortable, class="squish-status-{{st.id}}", ng-class="{'column-fold':statusesFolded[st.id]}")
|
||||
div.taskboard-task(ng-repeat="task in usTasks[us.id][st.id] track by task.id",
|
||||
tg-taskboard-task)
|
||||
include ../components/taskboard-task
|
||||
|
||||
div.task-row(ng-init="us = null")
|
||||
div.task-row(ng-init="us = null", ng-class="{'row-fold':usFolded['unassigned']}")
|
||||
div.taskboard-userstory-box.task-column
|
||||
a.icon.icon-vfold.vfold(href="", title="Fold Row", ng-click='foldUs()', ng-class="{hidden:usFolded['unassigned']}")
|
||||
a.icon.icon-vunfold.vunfold(href="", title="Unfold Row", ng-click='foldUs()', ng-class="{hidden:!usFolded['unassigned']}")
|
||||
h3.us-title
|
||||
span Unassigned tasks
|
||||
include ../components/addnewtask.jade
|
||||
|
||||
div.taskboard-tasks-box.task-column(ng-repeat="st in taskStatusList track by st.id",
|
||||
tg-taskboard-sortable)
|
||||
div.taskboard-tasks-box.task-column(ng-repeat="st in taskStatusList track by st.id", tg-taskboard-sortable, class="squish-status-{{st.id}}", ng-class="{'column-fold':statusesFolded[st.id]}")
|
||||
div.taskboard-task(ng-repeat="task in usTasks[null][st.id] track by task.id",
|
||||
tg-taskboard-task)
|
||||
include ../components/taskboard-task
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
section.team-filters
|
||||
div.team-filters-inner
|
||||
header
|
||||
h1 filters
|
||||
|
||||
form.search-in
|
||||
fieldset
|
||||
input(type="text", placeholder="Search by username or role...", ng-model="filtersQ")
|
||||
a.icon.icon-search(href="", title="search")
|
||||
|
||||
nav(tg-team-filters)
|
|
@ -0,0 +1,36 @@
|
|||
section.table-team.basic-table
|
||||
header.row.team-header
|
||||
div.username
|
||||
div.member-stats
|
||||
div.attribute.attribute-name(ng-if="issuesEnabled")
|
||||
span Mr. Wolf
|
||||
div.popover.attribute-explanation
|
||||
span I see, you solve issues!
|
||||
div.attribute(ng-if="tasksEnabled")
|
||||
span Poison Drinker
|
||||
div.popover.attribute-explanation
|
||||
span Hey, are you a iocaine-holic?
|
||||
div.attribute(ng-if="wikiEnabled")
|
||||
span Cervantes
|
||||
div.popover.attribute-explanation
|
||||
span You have no fear to the blank page!
|
||||
div.attribute(ng-if="issuesEnabled")
|
||||
Total Bug Hunter
|
||||
div.popover.attribute-explanation
|
||||
span Thaks to you, this project still alive.
|
||||
div.attribute(ng-if="tasksEnabled")
|
||||
span Night Shift
|
||||
div.popover.attribute-explanation
|
||||
span Poor Devil, you work too much.
|
||||
div.attribute
|
||||
Total Power
|
||||
div.popover.attribute-explanation
|
||||
span How far did you go into this Taiga?
|
||||
|
||||
div.hero(tg-team-current-user, stats="stats", currentuser="currentUser", projectid="projectId", issuesEnabled="issuesEnabled", tasksenabled="tasksEnabled", wikienabled="wikiEnabled")
|
||||
|
||||
h2(ng-show="memberships")
|
||||
span Team >
|
||||
span {{filtersRole.name || "All"}}
|
||||
|
||||
section.table-team.basic-table(tg-team-members, memberships="memberships", stats="stats", filtersq="filtersQ", filtersrole="filtersRole", issuesEnabled="issuesEnabled", tasksenabled="tasksEnabled", wikienabled="wikiEnabled")
|
|
@ -28,7 +28,9 @@ form
|
|||
fieldset.wizard-action
|
||||
div
|
||||
a.button-prev.button.button-gray(href="", title="Prev") Prev
|
||||
a.button-submit.button.button-green(href="", title="Create") Create
|
||||
a.submit-button.button.button-green(href="", title="Create") Create
|
||||
|
||||
button(type="submit", class="hidden")
|
||||
|
||||
div.progress-bar
|
||||
div.progress-state
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
extends ../../dummy-layout
|
||||
|
||||
block head
|
||||
title Taiga Your agile, free, and open source project management tool
|
||||
|
||||
block content
|
||||
div.wrapper(ng-controller="TeamController as ctrl", ng-init="section='team'")
|
||||
sidebar.menu-secondary
|
||||
include ../modules/team/team-filters
|
||||
section.main.team
|
||||
include ../components/mainTitle
|
||||
include ../modules/team/team-table
|
|
@ -1,7 +1,7 @@
|
|||
.single-filter {
|
||||
@extend %large;
|
||||
@include clearfix;
|
||||
@extend %title;
|
||||
@include clearfix;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
height: 32px;
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
}
|
||||
}
|
||||
&.ui-sortable-helper {
|
||||
box-shadow: 1px 1px 15px rgba($black, .4);
|
||||
@include transition(box-shadow .3s linear);
|
||||
box-shadow: 1px 1px 15px rgba($black, .4);
|
||||
}
|
||||
&.blocked {
|
||||
background: $red;
|
||||
|
@ -68,8 +68,8 @@
|
|||
display: block;
|
||||
}
|
||||
.task-text {
|
||||
@include table-flex-child($flex-grow: 10, $flex-basis: 50px);
|
||||
@extend %small;
|
||||
@include table-flex-child($flex-grow: 10, $flex-basis: 50px);
|
||||
padding: 0 .5rem 0 .8rem;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
@ -79,12 +79,11 @@
|
|||
}
|
||||
.task-name {
|
||||
@extend %bold;
|
||||
color: $grayer;
|
||||
}
|
||||
.icon-edit,
|
||||
.icon-drag-h {
|
||||
@include transition(opacity .2s linear);
|
||||
@extend %large;
|
||||
@include transition(opacity .2s linear);
|
||||
color: $postit-hover;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
a.help-markdown,
|
||||
a.help-button {
|
||||
@extend %small;
|
||||
color: $gray-light;
|
||||
&:hover {
|
||||
span {
|
||||
@include transition(color .2s linear);
|
||||
color: $grayer;
|
||||
}
|
||||
.icon {
|
||||
@include transition(color .2s linear);
|
||||
color: $fresh-taiga;
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
color: $gray-light;
|
||||
margin-right: .2rem;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
.taskboard-task {
|
||||
@include transition (all .4s linear);
|
||||
background: $postit;
|
||||
border: 1px solid $postit-hover;
|
||||
box-shadow: none;
|
||||
|
@ -17,8 +16,8 @@
|
|||
}
|
||||
}
|
||||
&.ui-sortable-helper {
|
||||
box-shadow: 1px 1px 15px rgba($black, .4);
|
||||
@include transition(box-shadow .3s linear);
|
||||
box-shadow: 1px 1px 15px rgba($black, .4);
|
||||
}
|
||||
&.ui-sortable-placeholder {
|
||||
background: $grayer;
|
||||
|
@ -45,7 +44,6 @@
|
|||
}
|
||||
.taskboard-task-inner {
|
||||
@include table-flex();
|
||||
min-height: 7rem;
|
||||
padding: .5rem;
|
||||
}
|
||||
.taskboard-user-avatar {
|
||||
|
@ -100,7 +98,6 @@
|
|||
}
|
||||
.task-name {
|
||||
@extend %bold;
|
||||
color: $grayer;
|
||||
}
|
||||
.taskboard-text {
|
||||
@extend %small;
|
||||
|
|
|
@ -35,8 +35,8 @@
|
|||
}
|
||||
}
|
||||
.watcher-name {
|
||||
@include table-flex-child(8, 0);
|
||||
@extend %small;
|
||||
@include table-flex-child(8, 0);
|
||||
color: $grayer;
|
||||
margin-left: 1rem;
|
||||
position: relative;
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
$black: #000;
|
||||
$blackish: #050505;
|
||||
$gray: #555;
|
||||
$grayer: #444;
|
||||
$gray: #555;
|
||||
$gray-light: #b8b8b8;
|
||||
$whitish: #f5f5f5;
|
||||
$very-light-gray: #fcfcfc;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// Bourbon
|
||||
$prefix-for-webkit: true;
|
||||
$prefix-for-mozilla: true;
|
||||
$prefix-for-microsoft: true;
|
||||
$prefix-for-opera: true;
|
||||
$prefix-for-spec: true;
|
||||
//$prefix-for-microsoft: true;
|
||||
//$prefix-for-opera: true;
|
||||
//$prefix-for-spec: true;
|
||||
@import '../bourbon/bourbon';
|
||||
|
||||
//#################################################
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
// Basic layout styles
|
||||
html {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -9,9 +8,7 @@ body {
|
|||
background: #fff; // fallback
|
||||
color: #444;
|
||||
-webkit-font-smoothing: antialiased; // Fix for webkit renderin
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
overflow-x: hidden; // open-projects-nav
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
width: 100%;
|
||||
|
@ -90,7 +87,7 @@ body {
|
|||
|
||||
.wrapper {
|
||||
@include table-flex();
|
||||
min-height: 100%;
|
||||
min-height: 100vh;
|
||||
padding-left: 90px;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.invitation-main {
|
||||
@include table-flex(center, center, flex, row, wrap, center);
|
||||
@extend %background-taiga;
|
||||
@include table-flex(center, center, flex, row, wrap, center);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
|
@ -62,8 +62,8 @@
|
|||
}
|
||||
}
|
||||
.forgot-pass {
|
||||
@include transition(all .3s linear);
|
||||
@extend %small;
|
||||
@include transition(all .3s linear);
|
||||
color: $gray-light;
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
.login-main {
|
||||
@extend %triangled-bg;
|
||||
//@include table-flex(center, center, flex, row, wrap, center);
|
||||
@include display(flex);
|
||||
@include align-items(center);
|
||||
@include flex-direction(row);
|
||||
@include justify-content(center);
|
||||
@extend %triangled-bg;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
.error-main {
|
||||
@extend %background-taiga;
|
||||
@include display(flex);
|
||||
@include align-items(center);
|
||||
@include flex-direction(row);
|
||||
@include justify-content(center);
|
||||
@extend %background-taiga;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
.team {
|
||||
h2 {
|
||||
margin: 1rem 0;
|
||||
span {
|
||||
&:last-child {
|
||||
color: $green-taiga;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue