diff --git a/CHANGELOG.md b/CHANGELOG.md index ed667b3a..63e2121a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,28 @@ # Changelog # +## 1.4.0 Unknown (Unreleased) + +### Features +- Gitlab integration: + + Create Admin Panel with the Gitlab webhooks settings. +- Bitbucket integration: + + Create Admin Panel with the Bitbucket webhooks settings. +- Added team members section. +- Taskboard enhancements: Collapse of columns (task statuses) and rows (user stories). +- Use enter to submit lightboxes forms. + +### Misc +- Upgrade to AngularJS 1.3. +- Lots of small and not so small bugfixes. + + ## 1.3.0 Dryas hookeriana (2014-11-18) ### Features - GitHub integration (Phase I): + Add button to login/singin with a GitHub account. + Create Admin Panel with the GitHub webhooks settings. +- Show/Hide columns in the Kanban view. - Differentiate blocked user stories on a milestone. ### Misc diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 0b014ad3..223d11d9 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -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", diff --git a/app/coffee/modules/admin/lightboxes.coffee b/app/coffee/modules/admin/lightboxes.coffee index bf30904a..6ad67d99 100644 --- a/app/coffee/modules/admin/lightboxes.coffee +++ b/app/coffee/modules/admin/lightboxes.coffee @@ -30,7 +30,7 @@ MAX_MEMBERSHIP_FIELDSETS = 4 ## Create Members Lightbox Directive ############################################################################# -CreateMembersDirective = ($rs, $rootScope, $confirm, lightboxService) -> +CreateMembersDirective = ($rs, $rootScope, $confirm, $loading ,lightboxService) -> extraTextTemplate = """
@@ -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]) diff --git a/app/coffee/modules/admin/memberships.coffee b/app/coffee/modules/admin/memberships.coffee index c337fbbe..531d12cf 100644 --- a/app/coffee/modules/admin/memberships.coffee +++ b/app/coffee/modules/admin/memberships.coffee @@ -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") diff --git a/app/coffee/modules/admin/project-profile.coffee b/app/coffee/modules/admin/project-profile.coffee index 499452cb..9b00f527 100644 --- a/app/coffee/modules/admin/project-profile.coffee +++ b/app/coffee/modules/admin/project-profile.coffee @@ -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 diff --git a/app/coffee/modules/admin/project-values.coffee b/app/coffee/modules/admin/project-values.coffee index f721a992..8f70c0ce 100644 --- a/app/coffee/modules/admin/project-values.coffee +++ b/app/coffee/modules/admin/project-values.coffee @@ -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) -> diff --git a/app/coffee/modules/admin/roles.coffee b/app/coffee/modules/admin/roles.coffee index 99bae6e1..6174e69e 100644 --- a/app/coffee/modules/admin/roles.coffee +++ b/app/coffee/modules/admin/roles.coffee @@ -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 = {} diff --git a/app/coffee/modules/admin/third-parties.coffee b/app/coffee/modules/admin/third-parties.coffee index 44c4782c..fc7abc4c 100644 --- a/app/coffee/modules/admin/third-parties.coffee +++ b/app/coffee/modules/admin/third-parties.coffee @@ -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) diff --git a/app/coffee/modules/auth.coffee b/app/coffee/modules/auth.coffee index 42a8ce12..173e38fd 100644 --- a/app/coffee/modules/auth.coffee +++ b/app/coffee/modules/auth.coffee @@ -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} diff --git a/app/coffee/modules/backlog/lightboxes.coffee b/app/coffee/modules/backlog/lightboxes.coffee index 52e1d416..d9a6a5b4 100644 --- a/app/coffee/modules/backlog/lightboxes.coffee +++ b/app/coffee/modules/backlog/lightboxes.coffee @@ -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() diff --git a/app/coffee/modules/backlog/main.coffee b/app/coffee/modules/backlog/main.coffee index 87ae636a..59e059ac 100644 --- a/app/coffee/modules/backlog/main.coffee +++ b/app/coffee/modules/backlog/main.coffee @@ -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 diff --git a/app/coffee/modules/backlog/sortable.coffee b/app/coffee/modules/backlog/sortable.coffee index cbb485c6..b027f501 100644 --- a/app/coffee/modules/backlog/sortable.coffee +++ b/app/coffee/modules/backlog/sortable.coffee @@ -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" }) diff --git a/app/coffee/modules/backlog/sprints.coffee b/app/coffee/modules/backlog/sprints.coffee index fcdcd8b6..cba3840f 100644 --- a/app/coffee/modules/backlog/sprints.coffee +++ b/app/coffee/modules/backlog/sprints.coffee @@ -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) diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index 3aeb6e99..a3d8c812 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -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" diff --git a/app/coffee/modules/base/repository.coffee b/app/coffee/modules/base/repository.coffee index 9241975a..866435d7 100644 --- a/app/coffee/modules/base/repository.coffee +++ b/app/coffee/modules/base/repository.coffee @@ -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" diff --git a/app/coffee/modules/common.coffee b/app/coffee/modules/common.coffee index 594469a8..06e01a56 100644 --- a/app/coffee/modules/common.coffee +++ b/app/coffee/modules/common.coffee @@ -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]) diff --git a/app/coffee/modules/common/attachments.coffee b/app/coffee/modules/common/attachments.coffee index 05951831..9fbe6504 100644 --- a/app/coffee/modules/common/attachments.coffee +++ b/app/coffee/modules/common/attachments.coffee @@ -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. diff --git a/app/coffee/modules/common/components.coffee b/app/coffee/modules/common/components.coffee index f6a57961..a0591462 100644 --- a/app/coffee/modules/common/components.coffee +++ b/app/coffee/modules/common/components.coffee @@ -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 = """
{{ 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 = """
+ + + Markdown syntax help + @@ -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]) ############################################################################# diff --git a/app/coffee/modules/common/confirm.coffee b/app/coffee/modules/common/confirm.coffee index 81679f42..48d484a4 100644 --- a/app/coffee/modules/common/confirm.coffee +++ b/app/coffee/modules/common/confirm.coffee @@ -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) diff --git a/app/coffee/modules/common/estimation.coffee b/app/coffee/modules/common/estimation.coffee index 6449fe5e..ac81e19a 100644 --- a/app/coffee/modules/common/estimation.coffee +++ b/app/coffee/modules/common/estimation.coffee @@ -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]) diff --git a/app/coffee/modules/common/history.coffee b/app/coffee/modules/common/history.coffee index b0a52545..679b9d90 100644 --- a/app/coffee/modules/common/history.coffee +++ b/app/coffee/modules/common/history.coffee @@ -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("""
@@ -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]) diff --git a/app/coffee/modules/common/lightboxes.coffee b/app/coffee/modules/common/lightboxes.coffee index 59b06a7d..685a0c91 100644 --- a/app/coffee/modules/common/lightboxes.coffee +++ b/app/coffee/modules/common/lightboxes.coffee @@ -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() diff --git a/app/coffee/modules/common/tags.coffee b/app/coffee/modules/common/tags.coffee index 3e910882..3a8e7fbf 100644 --- a/app/coffee/modules/common/tags.coffee +++ b/app/coffee/modules/common/tags.coffee @@ -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]) diff --git a/app/coffee/modules/common/wisiwyg.coffee b/app/coffee/modules/common/wisiwyg.coffee index cb0f1443..e1ceeae2 100644 --- a/app/coffee/modules/common/wisiwyg.coffee +++ b/app/coffee/modules/common/wisiwyg.coffee @@ -28,7 +28,7 @@ module = angular.module("taigaCommon") ############################################################################# ## WYSIWYG markitup editor directive ############################################################################# -tgMarkitupDirective = ($rootscope, $rs, $tr) -> +tgMarkitupDirective = ($rootscope, $rs, $tr, $selectedText) -> previewTemplate = _.template("""
@@ -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]) diff --git a/app/coffee/modules/events.coffee b/app/coffee/modules/events.coffee index 08445607..bd8923c1 100644 --- a/app/coffee/modules/events.coffee +++ b/app/coffee/modules/events.coffee @@ -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) diff --git a/app/coffee/modules/feedback.coffee b/app/coffee/modules/feedback.coffee index 17add8e7..781f6ddc 100644 --- a/app/coffee/modules/feedback.coffee +++ b/app/coffee/modules/feedback.coffee @@ -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]) diff --git a/app/coffee/modules/issues/detail.coffee b/app/coffee/modules/issues/detail.coffee index cb5f7aa4..ff9f2456 100644 --- a/app/coffee/modules/issues/detail.coffee +++ b/app/coffee/modules/issues/detail.coffee @@ -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(""" 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]) diff --git a/app/coffee/modules/issues/lightboxes.coffee b/app/coffee/modules/issues/lightboxes.coffee index 4daab182..70cd6f90 100644 --- a/app/coffee/modules/issues/lightboxes.coffee +++ b/app/coffee/modules/issues/lightboxes.coffee @@ -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]) diff --git a/app/coffee/modules/kanban/main.coffee b/app/coffee/modules/kanban/main.coffee index d8ba9db5..7f64a924 100644 --- a/app/coffee/modules/kanban/main.coffee +++ b/app/coffee/modules/kanban/main.coffee @@ -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() diff --git a/app/coffee/modules/nav.coffee b/app/coffee/modules/nav.coffee index 3350caa7..8283c76b 100644 --- a/app/coffee/modules/nav.coffee +++ b/app/coffee/modules/nav.coffee @@ -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, $ <% } %> + <% if (project.videoconferences) { %>