Fixing merge

stable
Alejandro Alonso 2014-12-10 09:40:08 +01:00
commit bfcab13dfb
131 changed files with 2229 additions and 792 deletions

View File

@ -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

View File

@ -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",

View File

@ -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])

View File

@ -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")

View File

@ -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

View File

@ -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) ->

View File

@ -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 = {}

View File

@ -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)

View File

@ -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}

View File

@ -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()

View File

@ -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

View File

@ -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"
})

View File

@ -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)

View File

@ -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"

View File

@ -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"

View File

@ -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])

View File

@ -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.

View File

@ -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])
#############################################################################

View File

@ -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)

View File

@ -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])

View File

@ -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])

View File

@ -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()

View File

@ -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])

View File

@ -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])

View File

@ -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)

View File

@ -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])

View File

@ -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])

View File

@ -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])

View File

@ -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()

View File

@ -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(),

View File

@ -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])
#############################################################################

View File

@ -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"

View File

@ -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}

View File

@ -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])

View File

@ -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

View File

@ -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}

View File

@ -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
])

View File

@ -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])

View File

@ -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])

View File

@ -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", [])

View File

@ -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])

View File

@ -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])

View File

@ -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()

View File

@ -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])

View File

@ -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])

View File

@ -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.

View File

@ -49,4 +49,9 @@
<glyph unicode="&#78;" 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="&#79;" 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="&#80;" 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="&#81;" 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="&#82;" 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="&#83;" 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="&#84;" 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="&#85;" 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.

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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!

View File

@ -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!

View File

@ -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!

View File

@ -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",

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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.

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -1,7 +1,7 @@
.single-filter {
@extend %large;
@include clearfix;
@extend %title;
@include clearfix;
cursor: pointer;
display: block;
height: 32px;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -2,8 +2,8 @@
$black: #000;
$blackish: #050505;
$gray: #555;
$grayer: #444;
$gray: #555;
$gray-light: #b8b8b8;
$whitish: #f5f5f5;
$very-light-gray: #fcfcfc;

View File

@ -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';
//#################################################

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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