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 # # 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) ## 1.3.0 Dryas hookeriana (2014-11-18)
### Features ### Features
- GitHub integration (Phase I): - GitHub integration (Phase I):
+ Add button to login/singin with a GitHub account. + Add button to login/singin with a GitHub account.
+ Create Admin Panel with the GitHub webhooks settings. + Create Admin Panel with the GitHub webhooks settings.
- Show/Hide columns in the Kanban view.
- Differentiate blocked user stories on a milestone. - Differentiate blocked user stories on a milestone.
### Misc ### Misc

View File

@ -63,6 +63,10 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
$routeProvider.when("/project/:pslug/wiki/:slug", $routeProvider.when("/project/:pslug/wiki/:slug",
{templateUrl: "/partials/wiki.html", resolve: {loader: tgLoaderProvider.add()}}) {templateUrl: "/partials/wiki.html", resolve: {loader: tgLoaderProvider.add()}})
# Team
$routeProvider.when("/project/:pslug/team",
{templateUrl: "/partials/views/team/team.html", resolve: {loader: tgLoaderProvider.add()}})
# Issues # Issues
$routeProvider.when("/project/:pslug/issues", $routeProvider.when("/project/:pslug/issues",
{templateUrl: "/partials/issues.html", resolve: {loader: tgLoaderProvider.add()}}) {templateUrl: "/partials/issues.html", resolve: {loader: tgLoaderProvider.add()}})
@ -96,6 +100,10 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
{templateUrl: "/partials/admin-roles.html"}) {templateUrl: "/partials/admin-roles.html"})
$routeProvider.when("/project/:pslug/admin/third-parties/github", $routeProvider.when("/project/:pslug/admin/third-parties/github",
{templateUrl: "/partials/admin-third-parties-github.html"}) {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 # User settings
$routeProvider.when("/project/:pslug/user-settings/user-profile", $routeProvider.when("/project/:pslug/user-settings/user-profile",
@ -134,7 +142,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
{templateUrl: "/partials/permission-denied.html"}) {templateUrl: "/partials/permission-denied.html"})
$routeProvider.otherwise({redirectTo: '/not-found'}) $routeProvider.otherwise({redirectTo: '/not-found'})
$locationProvider.html5Mode(true) $locationProvider.html5Mode({enabled: true, requireBase: false})
defaultHeaders = { defaultHeaders = {
"Content-Type": "application/json" "Content-Type": "application/json"
@ -153,9 +161,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
$tgEventsProvider.setSessionId(taiga.sessionId) $tgEventsProvider.setSessionId(taiga.sessionId)
# Add next param when user try to access to a secction need auth permissions. # Add next param when user try to access to a secction need auth permissions.
authHttpIntercept = ($q, $location, $confirm, $navUrls, $lightboxService) -> authHttpIntercept = ($q, $location, $navUrls, $lightboxService) ->
return (promise) -> httpResponseError = (response) ->
return promise.then null, (response) ->
if response.status == 0 if response.status == 0
$lightboxService.closeAll() $lightboxService.closeAll()
$location.path($navUrls.resolve("error")) $location.path($navUrls.resolve("error"))
@ -163,11 +170,36 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
else if response.status == 401 else if response.status == 401
nextPath = $location.path() nextPath = $location.path()
$location.url($navUrls.resolve("login")).search("next=#{nextPath}") $location.url($navUrls.resolve("login")).search("next=#{nextPath}")
return $q.reject(response) return $q.reject(response)
$provide.factory("authHttpIntercept", ["$q", "$location", "$tgConfirm", "$tgNavUrls", return {
"lightboxService", authHttpIntercept]) responseError: httpResponseError
$httpProvider.responseInterceptors.push('authHttpIntercept') }
$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)
return $q.reject(response)
return {
responseError: httpResponseError
}
$provide.factory("versionCheckHttpIntercept", ["$q", "$tgConfirm", versionCheckHttpIntercept])
$httpProvider.interceptors.push('versionCheckHttpIntercept');
window.checksley.updateValidators({ window.checksley.updateValidators({
linewidth: (val, width) -> linewidth: (val, width) ->
@ -210,6 +242,7 @@ modules = [
"taigaIssues", "taigaIssues",
"taigaUserStories", "taigaUserStories",
"taigaTasks", "taigaTasks",
"taigaTeam",
"taigaWiki", "taigaWiki",
"taigaSearch", "taigaSearch",
"taigaAdmin", "taigaAdmin",

View File

@ -30,7 +30,7 @@ MAX_MEMBERSHIP_FIELDSETS = 4
## Create Members Lightbox Directive ## Create Members Lightbox Directive
############################################################################# #############################################################################
CreateMembersDirective = ($rs, $rootScope, $confirm, lightboxService) -> CreateMembersDirective = ($rs, $rootScope, $confirm, $loading ,lightboxService) ->
extraTextTemplate = """ extraTextTemplate = """
<fieldset class="extra-text"> <fieldset class="extra-text">
<textarea placeholder="(Optional) Add a personalized text to the invitation. Tell something lovely to your new members ;-)"></textarea> <textarea placeholder="(Optional) Add a personalized text to the invitation. Tell something lovely to your new members ;-)"></textarea>
@ -103,15 +103,19 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, lightboxService) ->
$el.find(".add-member-wrapper fieldset:last > a").removeClass("icon-plus add-fieldset") $el.find(".add-member-wrapper fieldset:last > a").removeClass("icon-plus add-fieldset")
.addClass("icon-delete delete-fieldset") .addClass("icon-delete delete-fieldset")
$el.on "click", ".button-green", debounce 2000, (event) -> submit = debounce 2000, (event) =>
event.preventDefault() event.preventDefault()
$loading.start(submitButton)
onSuccess = (data) -> onSuccess = (data) ->
$loading.finish(submitButton)
lightboxService.close($el) lightboxService.close($el)
$confirm.notify("success") $confirm.notify("success")
$rootScope.$broadcast("membersform:new:success") $rootScope.$broadcast("membersform:new:success")
onError = (data) -> onError = (data) ->
$loading.finish(submitButton)
lightboxService.close($el) lightboxService.close($el)
$confirm.notify("error") $confirm.notify("error")
$rootScope.$broadcast("membersform:new: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) $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} return {link: link}
module.directive("tgLbCreateMembers", ["$tgResources", "$rootScope", "$tgConfirm", "lightboxService", module.directive("tgLbCreateMembers", ["$tgResources", "$rootScope", "$tgConfirm", "$tgLoading", "lightboxService",
CreateMembersDirective]) CreateMembersDirective])

View File

@ -22,6 +22,7 @@
taiga = @.taiga taiga = @.taiga
mixOf = @.taiga.mixOf mixOf = @.taiga.mixOf
bindMethods = @.taiga.bindMethods
module = angular.module("taigaAdmin") 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, constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q,
@location, @navUrls, @analytics, @appTitle) -> @location, @navUrls, @analytics, @appTitle) ->
_.bindAll(@) bindMethods(@)
@scope.sectionName = "Manage Members" #i18n @scope.sectionName = "Manage Members" #i18n
@scope.project = {} @scope.project = {}
@ -301,8 +302,10 @@ MembershipsRowAdminCheckboxDirective = ($log, $repo, $confirm) ->
onSuccess = -> onSuccess = ->
$confirm.notify("success") $confirm.notify("success")
onError = -> onError = (data) ->
$confirm.notify("error") member.revert()
$el.find(":checkbox").prop("checked", member.is_owner)
$confirm.notify("error", data.is_owner[0])
target = angular.element(event.currentTarget) target = angular.element(event.currentTarget)
member.is_owner = target.prop("checked") member.is_owner = target.prop("checked")

View File

@ -27,6 +27,7 @@ toString = @.taiga.toString
joinStr = @.taiga.joinStr joinStr = @.taiga.joinStr
groupBy = @.taiga.groupBy groupBy = @.taiga.groupBy
bindOnce = @.taiga.bindOnce bindOnce = @.taiga.bindOnce
debounce = @.taiga.debounce
module = angular.module("taigaAdmin") module = angular.module("taigaAdmin")
@ -95,14 +96,16 @@ module.controller("ProjectProfileController", ProjectProfileController)
ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location) -> ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location) ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
form = $el.find("form").checksley({"onlyOneErrorElement": true}) form = $el.find("form").checksley({"onlyOneErrorElement": true})
submit = (target) => submit = debounce 2000, (event) =>
event.preventDefault()
return if not form.validate() return if not form.validate()
$loading.start(target) $loading.start(submitButton)
promise = $repo.save($scope.project) promise = $repo.save($scope.project)
promise.then -> promise.then ->
$loading.finish(target) $loading.finish(submitButton)
$confirm.notify("success") $confirm.notify("success")
newUrl = $navurls.resolve("project-admin-project-profile-details", {project: $scope.project.slug}) newUrl = $navurls.resolve("project-admin-project-profile-details", {project: $scope.project.slug})
$location.path(newUrl) $location.path(newUrl)
@ -114,24 +117,51 @@ ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location) ->
if data._error_message if data._error_message
$confirm.notify("error", data._error_message) $confirm.notify("error", data._error_message)
$el.on "submit", "form", (event) -> submitButton = $el.find(".submit-button");
event.preventDefault()
submit()
$el.on "click", ".default-values a.button-green", (event) -> $el.on "submit", "form", submit
event.preventDefault() $el.on "click", ".submit-button", submit
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)
return {link:link} return {link:link}
module.directive("tgProjectProfile", ["$tgRepo", "$tgConfirm", "$tgLoading", "$tgNavUrls", "$tgLocation", ProjectProfileDirective]) 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 ## Project Modules Directive

View File

@ -208,7 +208,7 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame) ->
animationFrame.add () -> animationFrame.add () ->
goToBottomList() goToBottomList()
$el.find(".new-value").hide() $el.find(".new-value").addClass("hidden")
initializeNewValue() initializeNewValue()
promise.then null, (data) -> promise.then null, (data) ->

View File

@ -24,6 +24,7 @@ taiga = @.taiga
mixOf = @.taiga.mixOf mixOf = @.taiga.mixOf
bindOnce = @.taiga.bindOnce bindOnce = @.taiga.bindOnce
debounce = @.taiga.debounce debounce = @.taiga.debounce
bindMethods = @.taiga.bindMethods
module = angular.module("taigaAdmin") 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) -> constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @appTitle) ->
_.bindAll(@) bindMethods(@)
@scope.sectionName = "Permissions" #i18n @scope.sectionName = "Permissions" #i18n
@scope.project = {} @scope.project = {}

View File

@ -22,6 +22,8 @@
taiga = @.taiga taiga = @.taiga
mixOf = @.taiga.mixOf mixOf = @.taiga.mixOf
bindMethods = @.taiga.bindMethods
debounce = @.taiga.debounce
module = angular.module("taigaAdmin") module = angular.module("taigaAdmin")
@ -40,11 +42,10 @@ class GithubController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
] ]
constructor: (@scope, @repo, @rs, @params, @appTitle) -> constructor: (@scope, @repo, @rs, @params, @appTitle) ->
_.bindAll(@) bindMethods(@)
@scope.sectionName = "Github" #i18n @scope.sectionName = "Github" #i18n
@scope.project = {} @scope.project = {}
@scope.anyComputableRole = true
promise = @.loadInitialData() promise = @.loadInitialData()
@ -61,8 +62,6 @@ class GithubController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
return @rs.projects.get(@scope.projectId).then (project) => return @rs.projects.get(@scope.projectId).then (project) =>
@scope.project = project @scope.project = project
@scope.$emit('project:loaded', project) @scope.$emit('project:loaded', project)
@scope.anyComputableRole = _.some(_.map(project.roles, (point) -> point.computable))
return project return project
loadInitialData: -> loadInitialData: ->
@ -76,6 +75,106 @@ class GithubController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
module.controller("GithubController", GithubController) 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 = -> SelectInputText = ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
$el.on "click", ".select-input-content", () -> $el.on "click", ".select-input-content", () ->
@ -86,6 +185,7 @@ SelectInputText = ->
module.directive("tgSelectInputText", SelectInputText) module.directive("tgSelectInputText", SelectInputText)
############################################################################# #############################################################################
## GithubWebhooks Directive ## GithubWebhooks Directive
############################################################################# #############################################################################
@ -93,31 +193,122 @@ module.directive("tgSelectInputText", SelectInputText)
GithubWebhooksDirective = ($repo, $confirm, $loading) -> GithubWebhooksDirective = ($repo, $confirm, $loading) ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
form = $el.find("form").checksley({"onlyOneErrorElement": true}) form = $el.find("form").checksley({"onlyOneErrorElement": true})
submit = (target) => submit = debounce 2000, (event) =>
event.preventDefault()
return if not form.validate() return if not form.validate()
$loading.start(target) $loading.start(submitButton)
promise = $repo.saveAttribute($scope.github, "github") promise = $repo.saveAttribute($scope.github, "github")
promise.then -> promise.then ->
$loading.finish(target) $loading.finish(submitButton)
$confirm.notify("success") $confirm.notify("success")
promise.then null, (data) -> promise.then null, (data) ->
$loading.finish(target) $loading.finish(submitButton)
form.setErrors(data) form.setErrors(data)
if data._error_message if data._error_message
$confirm.notify("error", data._error_message) $confirm.notify("error", data._error_message)
$el.on "click", "a.button-green", (event) -> submitButton = $el.find(".submit-button")
event.preventDefault()
target = angular.element(event.currentTarget)
submit(target)
$el.on "submit", "form", (event) -> $el.on "submit", "form", submit
event.preventDefault() $el.on "click", ".submit-button", submit
submit()
return {link:link} return {link:link}
module.directive("tgGithubWebhooks", ["$tgRepo", "$tgConfirm", "$tgLoading", GithubWebhooksDirective]) 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) -> onError = (response) ->
$confirm.notify("light-error", "According to our Oompa Loompas, your username/email $confirm.notify("light-error", "According to our Oompa Loompas, your username/email
or password are incorrect.") #TODO: i18n or password are incorrect.") #TODO: i18n
submit = -> submit = debounce 2000, (event) =>
event.preventDefault()
form = new checksley.Form($el.find("form.login-form")) form = new checksley.Form($el.find("form.login-form"))
if not form.validate() if not form.validate()
return return
@ -202,13 +204,8 @@ LoginDirective = ($auth, $confirm, $location, $config, $routeParams, $navUrls, $
promise = $auth.login(data) promise = $auth.login(data)
return promise.then(onSuccess, onError) return promise.then(onSuccess, onError)
$el.on "click", "a.button-login", (event) -> $el.on "submit", "form", submit
event.preventDefault() $el.on "click", ".submit-button", submit
submit()
$el.on "submit", "form", (event) ->
event.preventDefault()
submit()
return {link:link} return {link:link}
@ -239,20 +236,17 @@ RegisterDirective = ($auth, $confirm, $location, $navUrls, $config, $analytics)
form.setErrors(response.data) form.setErrors(response.data)
submit = debounce 2000, => submit = debounce 2000, (event) =>
event.preventDefault()
if not form.validate() if not form.validate()
return return
promise = $auth.register($scope.data) promise = $auth.register($scope.data)
promise.then(onSuccessSubmit, onErrorSubmit) promise.then(onSuccessSubmit, onErrorSubmit)
$el.on "submit", (event) -> $el.on "submit", "form", submit
event.preventDefault() $el.on "click", ".submit-button", submit
submit()
$el.on "click", "a.button-register", (event) ->
event.preventDefault()
submit()
return {link:link} return {link:link}
@ -279,20 +273,17 @@ ForgotPasswordDirective = ($auth, $confirm, $location, $navUrls) ->
$confirm.notify("light-error", "According to our Oompa Loompas, $confirm.notify("light-error", "According to our Oompa Loompas,
your are not registered yet.") #TODO: i18n your are not registered yet.") #TODO: i18n
submit = debounce 2000, => submit = debounce 2000, (event) =>
event.preventDefault()
if not form.validate() if not form.validate()
return return
promise = $auth.forgotPassword($scope.data) promise = $auth.forgotPassword($scope.data)
promise.then(onSuccessSubmit, onErrorSubmit) promise.then(onSuccessSubmit, onErrorSubmit)
$el.on "submit", (event) -> $el.on "submit", "form", submit
event.preventDefault() $el.on "click", ".submit-button", submit
submit()
$el.on "click", "a.button-forgot", (event) ->
event.preventDefault()
submit()
return {link:link} return {link:link}
@ -324,20 +315,17 @@ ChangePasswordFromRecoveryDirective = ($auth, $confirm, $location, $params, $nav
$confirm.notify("light-error", "One of our Oompa Loompas say $confirm.notify("light-error", "One of our Oompa Loompas say
'#{response.data._error_message}'.") #TODO: i18n '#{response.data._error_message}'.") #TODO: i18n
submit = debounce 2000, => submit = debounce 2000, (event) =>
event.preventDefault()
if not form.validate() if not form.validate()
return return
promise = $auth.changePasswordFromRecovery($scope.data) promise = $auth.changePasswordFromRecovery($scope.data)
promise.then(onSuccessSubmit, onErrorSubmit) promise.then(onSuccessSubmit, onErrorSubmit)
$el.on "submit", (event) -> $el.on "submit", "form", submit
event.preventDefault() $el.on "click", ".submit-button", submit
submit()
$el.on "click", "a.button-change-password", (event) ->
event.preventDefault()
submit()
return {link:link} 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 $confirm.notify("light-error", "According to our Oompa Loompas, your are not registered yet or
typed an invalid password.") #TODO: i18n typed an invalid password.") #TODO: i18n
submitLogin = debounce 2000, => submitLogin = debounce 2000, (event) =>
event.preventDefault()
if not loginForm.validate() if not loginForm.validate()
return return
promise = $auth.acceptInvitiationWithExistingUser($scope.dataLogin) promise = $auth.acceptInvitiationWithExistingUser($scope.dataLogin)
promise.then(onSuccessSubmitLogin, onErrorSubmitLogin) promise.then(onSuccessSubmitLogin, onErrorSubmitLogin)
$el.on "submit", "form.login-form", (event) -> $el.on "submit", "form.login-form", submitLogin
event.preventDefault() $el.on "click", ".button-login", submitLogin
submitLogin()
$el.on "click", "a.button-login", (event) ->
event.preventDefault()
submitLogin()
# Register form # Register form
$scope.dataRegister = {token: token} $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 $confirm.notify("light-error", "According to our Oompa Loompas, that
username or email is already in use.") #TODO: i18n username or email is already in use.") #TODO: i18n
submitRegister = debounce 2000, => submitRegister = debounce 2000, (event) =>
event.preventDefault()
if not registerForm.validate() if not registerForm.validate()
return return
promise = $auth.acceptInvitiationWithNewUser($scope.dataRegister) promise = $auth.acceptInvitiationWithNewUser($scope.dataRegister)
promise.then(onSuccessSubmitRegister, onErrorSubmitRegister) promise.then(onSuccessSubmitRegister, onErrorSubmitRegister)
$el.on "submit", "form.register-form", (event) -> $el.on "submit", "form.register-form", submitRegister
event.preventDefault() $el.on "click", ".button-register", submitRegister
submitRegister
$el.on "click", "a.button-register", (event) ->
event.preventDefault()
submitRegister()
return {link:link} return {link:link}
@ -483,20 +465,17 @@ CancelAccountDirective = ($repo, $model, $auth, $confirm, $location, $params, $n
$confirm.notify("error", "One of our Oompa Loompas says $confirm.notify("error", "One of our Oompa Loompas says
'#{response.data._error_message}'.") #TODO: i18n '#{response.data._error_message}'.") #TODO: i18n
submit = -> submit = debounce 2000, (event) =>
event.preventDefault()
if not form.validate() if not form.validate()
return return
promise = $auth.cancelAccount($scope.data) promise = $auth.cancelAccount($scope.data)
promise.then(onSuccessSubmit, onErrorSubmit) promise.then(onSuccessSubmit, onErrorSubmit)
$el.on "submit", (event) -> $el.on "submit", "form", submit
event.preventDefault() $el.on "click", ".submit-button", submit
submit()
$el.on "click", "a.button-cancel-account", (event) ->
event.preventDefault()
submit()
return {link:link} return {link:link}

View File

@ -41,7 +41,9 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading)
estimated_finish: null estimated_finish: null
} }
submit = (event) -> submit = debounce 2000, (event) =>
event.preventDefault()
target = angular.element(event.currentTarget) target = angular.element(event.currentTarget)
form = $el.find("form").checksley() form = $el.find("form").checksley()
@ -65,17 +67,17 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading)
promise = $repo.save(newSprint) promise = $repo.save(newSprint)
broadcastEvent = "sprintform:edit:success" broadcastEvent = "sprintform:edit:success"
$loading.start(target) $loading.start(submitButton)
promise.then (data) -> promise.then (data) ->
$loading.finish(target) $loading.finish(submitButton)
$scope.sprintsCounter += 1 if createSprint $scope.sprintsCounter += 1 if createSprint
$rootscope.$broadcast(broadcastEvent, data) $rootscope.$broadcast(broadcastEvent, data)
lightboxService.close($el) lightboxService.close($el)
promise.then null, (data) -> promise.then null, (data) ->
$loading.finish(target) $loading.finish(submitButton)
form.setErrors(data) form.setErrors(data)
if data._error_message if data._error_message
@ -152,9 +154,10 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading)
else else
$el.find(".last-sprint-name").removeClass("disappear") $el.find(".last-sprint-name").removeClass("disappear")
$el.on "click", ".button-green", debounce 2000, (event) -> submitButton = $el.find(".submit-button")
event.preventDefault()
submit(event) $el.on "submit", "form", submit
$el.on "click", ".submit-button", submit
$el.on "click", ".delete-sprint .icon-delete", (event) -> $el.on "click", ".delete-sprint .icon-delete", (event) ->
event.preventDefault() event.preventDefault()

View File

@ -27,6 +27,7 @@ scopeDefer = @.taiga.scopeDefer
bindOnce = @.taiga.bindOnce bindOnce = @.taiga.bindOnce
groupBy = @.taiga.groupBy groupBy = @.taiga.groupBy
timeout = @.taiga.timeout timeout = @.taiga.timeout
bindMethods = @.taiga.bindMethods
module = angular.module("taigaBacklog") 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, constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q,
@location, @appTitle, @navUrls, @events, @analytics, tgLoader) -> @location, @appTitle, @navUrls, @events, @analytics, tgLoader) ->
_.bindAll(@) bindMethods(@)
@scope.sectionName = "Backlog" @scope.sectionName = "Backlog"
@showTags = false @showTags = false
@ -389,14 +390,14 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
# Rehash userstories order field # Rehash userstories order field
# and persist in bulk all changes. # 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") items = @.resortUserStories(newSprint.user_stories, "sprint_order")
data = @.prepareBulkUpdateData(items, "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) @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 for us in usList
@rootscope.$broadcast("sprint:us:moved", us, oldSprintId, newSprintId) @rootscope.$broadcast("sprint:us:moved", us, oldSprintId, newSprintId)
@ -444,6 +445,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
return obj return obj
plainStatuses = _.map(@scope.userstories, "status") plainStatuses = _.map(@scope.userstories, "status")
plainStatuses = _.filter plainStatuses, (status) => plainStatuses = _.filter plainStatuses, (status) =>
if status if status
return status return status

View File

@ -58,9 +58,7 @@ BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm) ->
containment: ".wrapper" containment: ".wrapper"
dropOnEmpty: true dropOnEmpty: true
placeholder: "row us-item-row us-item-drag sortable-placeholder" placeholder: "row us-item-row us-item-drag sortable-placeholder"
# With scroll activated, it has strange behavior scroll: true
# with not full screen browser window.
scroll: false
# A consequence of length of backlog user story item # A consequence of length of backlog user story item
# the default tolerance ("intersection") not works properly. # the default tolerance ("intersection") not works properly.
tolerance: "pointer" tolerance: "pointer"
@ -153,6 +151,7 @@ SprintSortableDirective = ($repo, $rs, $rootscope) ->
# If the user has not enough permissions we don't enable the sortable # If the user has not enough permissions we don't enable the sortable
if project.my_permissions.indexOf("modify_us") > -1 if project.my_permissions.indexOf("modify_us") > -1
$el.sortable({ $el.sortable({
scroll: true
dropOnEmpty: true dropOnEmpty: true
connectWith: ".sprint-table,.backlog-table-body,.empty-backlog" connectWith: ".sprint-table,.backlog-table-body,.empty-backlog"
}) })

View File

@ -29,23 +29,44 @@ module = angular.module("taigaBacklog")
############################################################################# #############################################################################
BacklogSprintDirective = ($repo, $rootscope) -> 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) -> link = ($scope, $el, $attrs) ->
$scope.$watch $attrs.tgBacklogSprint, (sprint) -> $scope.$watch $attrs.tgBacklogSprint, (sprint) ->
sprint = $scope.$eval($attrs.tgBacklogSprint) sprint = $scope.$eval($attrs.tgBacklogSprint)
if $scope.$first if $scope.$first
$el.addClass("sprint-current") toggleSprint($el)
$el.find(".sprint-table").addClass('open')
else if sprint.closed else if sprint.closed
$el.addClass("sprint-closed") $el.addClass("sprint-closed")
else if not $scope.$first and not sprint.closed else if not $scope.$first and not sprint.closed
toggleSprint($el)
$el.addClass("sprint-old-open") $el.addClass("sprint-old-open")
# Event Handlers # Event Handlers
$el.on "click", ".sprint-name > .icon-arrow-up", (event) -> $el.on "click", ".sprint-name > .icon-arrow-up", (event) ->
target = $(event.currentTarget) toggleSprint($el)
target.toggleClass('active')
$el.find(".sprint-table").toggleClass('open') $el.find(".sprint-table").slideToggle(slideOptions)
$el.on "click", ".sprint-name > .icon-edit", (event) -> $el.on "click", ".sprint-name > .icon-edit", (event) ->
sprint = $scope.$eval($attrs.tgBacklogSprint) sprint = $scope.$eval($attrs.tgBacklogSprint)

View File

@ -72,8 +72,11 @@ urls = {
"project-issues-detail": "/project/:project/issue/:ref" "project-issues-detail": "/project/:project/issue/:ref"
"project-wiki": "/project/:project/wiki", "project-wiki": "/project/:project/wiki"
"project-wiki-page": "/project/:project/wiki/:slug", "project-wiki-page": "/project/:project/wiki/:slug"
# Team
"project-team": "/project/:project/team"
# Admin # Admin
"project-admin-home": "/project/:project/admin/project-profile/details" "project-admin-home": "/project/:project/admin/project-profile/details"
@ -90,6 +93,8 @@ urls = {
"project-admin-memberships": "/project/:project/admin/memberships" "project-admin-memberships": "/project/:project/admin/memberships"
"project-admin-roles": "/project/:project/admin/roles" "project-admin-roles": "/project/:project/admin/roles"
"project-admin-third-parties-github": "/project/:project/admin/third-parties/github" "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-settings-user-profile": "/project/:project/user-settings/user-profile" "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) -> saveAll: (models, patch=true) ->
promises = _.map(models, (x) => @.save(x, true)) promises = _.map(models, (x) => @.save(x, true))
return @q.all.apply(@q, promises) return @q.all(promises)
save: (model, patch=true) -> save: (model, patch=true) ->
defered = @q.defer() defered = @q.defer()
@ -143,6 +143,7 @@ class RepositoryService extends taiga.Service
queryMany: (name, params, options={}) -> queryMany: (name, params, options={}) ->
url = @urls.resolve(name) url = @urls.resolve(name)
httpOptions = {headers: {}} httpOptions = {headers: {}}
if not options.enablePagination if not options.enablePagination
httpOptions.headers["x-disable-pagination"] = "1" httpOptions.headers["x-disable-pagination"] = "1"

View File

@ -23,6 +23,21 @@ taiga = @.taiga
module = angular.module("taigaCommon", []) 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 ## Permission directive, hide elements when necessary
############################################################################# #############################################################################
@ -143,3 +158,32 @@ LimitLineLengthDirective = () ->
return {link:link} return {link:link}
module.directive("tgLimitLineLength", LimitLineLengthDirective) 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 taiga = @.taiga
sizeFormat = @.taiga.sizeFormat sizeFormat = @.taiga.sizeFormat
bindOnce = @.taiga.bindOnce bindOnce = @.taiga.bindOnce
bindMethods = @.taiga.bindMethods
module = angular.module("taigaCommon") module = angular.module("taigaCommon")
@ -30,7 +31,7 @@ class AttachmentsController extends taiga.Controller
@.$inject = ["$scope", "$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", "$q"] @.$inject = ["$scope", "$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", "$q"]
constructor: (@scope, @rootscope, @repo, @rs, @confirm, @q) -> constructor: (@scope, @rootscope, @repo, @rs, @confirm, @q) ->
_.bindAll(@) bindMethods(@)
@.type = null @.type = null
@.objectId = null @.objectId = null
@.projectId = null @.projectId = null
@ -85,7 +86,7 @@ class AttachmentsController extends taiga.Controller
# Create attachments in bulk # Create attachments in bulk
createAttachments: (attachments) -> createAttachments: (attachments) ->
promises = _.map(attachments, (x) => @._createAttachment(x)) promises = _.map(attachments, (x) => @._createAttachment(x))
return @q.all.apply(null, promises).then => return @q.all(promises).then =>
@.updateCounters() @.updateCounters()
# Add uploading attachment tracking. # Add uploading attachment tracking.

View File

@ -161,7 +161,7 @@ module.directive("tgCreatedByDisplay", CreatedByDisplayDirective)
## Watchers directive ## 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 # You have to include a div with the tg-lb-watchers directive in the page
# where use this directive # where use this directive
# #
@ -204,17 +204,37 @@ WatchersDirective = ($rootscope, $confirm, $repo) ->
isEditable = -> isEditable = ->
return $scope.project?.my_permissions?.indexOf($attrs.requiredPerm) != -1 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 = $repo.save($model.$modelValue)
promise.then -> promise.then ->
$confirm.notify("success") $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) renderWatchers(watchers)
$rootscope.$broadcast("history:reload") $rootscope.$broadcast("history:reload")
promise.then null, -> promise.then null, ->
model.revert() item.revert()
$confirm.notify("error") $confirm.notify("error")
renderWatchers = (watchers) -> renderWatchers = (watchers) ->
ctx = { ctx = {
watchers: watchers watchers: watchers
@ -239,13 +259,11 @@ WatchersDirective = ($rootscope, $confirm, $repo) ->
$confirm.askOnDelete(title, message).then (finish) => $confirm.askOnDelete(title, message).then (finish) =>
finish() finish()
watcherIds = _.clone($model.$modelValue.watchers, false) watcherIds = _.clone($model.$modelValue.watchers, false)
watcherIds = _.pull(watcherIds, watcherId) watcherIds = _.pull(watcherIds, watcherId)
item = $model.$modelValue.clone() deleteWatcher(watcherIds)
item.watchers = watcherIds
$model.$setViewValue(item)
save(item)
$el.on "click", ".add-watcher", (event) -> $el.on "click", ".add-watcher", (event) ->
event.preventDefault() event.preventDefault()
@ -258,10 +276,7 @@ WatchersDirective = ($rootscope, $confirm, $repo) ->
watchers.push(watcherId) watchers.push(watcherId)
watchers = _.uniq(watchers) watchers = _.uniq(watchers)
item = $model.$modelValue.clone() save(watchers)
item.watchers = watchers
$model.$setViewValue(item)
save(item)
$scope.$watch $attrs.ngModel, (item) -> $scope.$watch $attrs.ngModel, (item) ->
return if not item? return if not item?
@ -273,14 +288,14 @@ WatchersDirective = ($rootscope, $confirm, $repo) ->
return {link:link, require:"ngModel"} return {link:link, require:"ngModel"}
module.directive("tgWatchers", ["$rootScope", "$tgConfirm", "$tgRepo", WatchersDirective]) module.directive("tgWatchers", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgQqueue", WatchersDirective])
############################################################################# #############################################################################
## Assigned to directive ## 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 # You have to include a div with the tg-lb-assignedto directive in the page
# where use this directive # where use this directive
# #
@ -315,20 +330,24 @@ AssignedToDirective = ($rootscope, $confirm, $repo, $loading) ->
isEditable = -> isEditable = ->
return $scope.project?.my_permissions?.indexOf($attrs.requiredPerm) != -1 return $scope.project?.my_permissions?.indexOf($attrs.requiredPerm) != -1
save = (model) -> save = $qqueue.bindAdd (userId) =>
$model.$modelValue.assigned_to = userId
$loading.start($el) $loading.start($el)
promise = $repo.save($model.$modelValue) promise = $repo.save($model.$modelValue)
promise.then -> promise.then ->
$loading.finish($el) $loading.finish($el)
$confirm.notify("success") $confirm.notify("success")
renderAssignedTo(model) renderAssignedTo($model.$modelValue)
$rootscope.$broadcast("history:reload") $rootscope.$broadcast("history:reload")
promise.then null, -> promise.then null, ->
model.revert() $model.$modelValue.revert()
$confirm.notify("error") $confirm.notify("error")
$loading.finish($el) $loading.finish($el)
return promise
renderAssignedTo = (issue) -> renderAssignedTo = (issue) ->
assignedToId = issue?.assigned_to assignedToId = issue?.assigned_to
assignedTo = if assignedToId? then $scope.usersById[assignedToId] else null assignedTo = if assignedToId? then $scope.usersById[assignedToId] else null
@ -354,12 +373,12 @@ AssignedToDirective = ($rootscope, $confirm, $repo, $loading) ->
$confirm.ask(title).then (finish) => $confirm.ask(title).then (finish) =>
finish() finish()
$model.$modelValue.assigned_to = null $model.$modelValue.assigned_to = null
save($model.$modelValue) save(null)
$scope.$on "assigned-to:added", (ctx, userId, item) -> $scope.$on "assigned-to:added", (ctx, userId, item) ->
return if item.id != $model.$modelValue.id return if item.id != $model.$modelValue.id
$model.$modelValue.assigned_to = userId
save($model.$modelValue) save(userId)
$scope.$watch $attrs.ngModel, (instance) -> $scope.$watch $attrs.ngModel, (instance) ->
renderAssignedTo(instance) renderAssignedTo(instance)
@ -372,7 +391,7 @@ AssignedToDirective = ($rootscope, $confirm, $repo, $loading) ->
require:"ngModel" 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 ## Editable subject directive
############################################################################# #############################################################################
EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading) -> EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $qqueue) ->
template = """ template = """
<div class="view-subject"> <div class="view-subject">
{{ item.subject }} {{ item.subject }}
@ -492,9 +511,11 @@ EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading) ->
isEditable = -> isEditable = ->
return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1 return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1
save = -> save = $qqueue.bindAdd (subject) =>
$model.$modelValue.subject = $scope.item.subject $model.$modelValue.subject = subject
$loading.start($el.find('.save-container')) $loading.start($el.find('.save-container'))
promise = $repo.save($model.$modelValue) promise = $repo.save($model.$modelValue)
promise.then -> promise.then ->
$confirm.notify("success") $confirm.notify("success")
@ -506,6 +527,8 @@ EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading) ->
promise.finally -> promise.finally ->
$loading.finish($el.find('.save-container')) $loading.finish($el.find('.save-container'))
return promise
$el.click -> $el.click ->
return if not isEditable() return if not isEditable()
$el.find('.edit-subject').show() $el.find('.edit-subject').show()
@ -513,11 +536,13 @@ EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading) ->
$el.find('input').focus() $el.find('input').focus()
$el.on "click", ".save", -> $el.on "click", ".save", ->
save() subject = $scope.item.subject
save(subject)
$el.on "keyup", "input", (event) -> $el.on "keyup", "input", (event) ->
if event.keyCode == 13 if event.keyCode == 13
save() subject = $scope.item.subject
save(subject)
else if event.keyCode == 27 else if event.keyCode == 27
$model.$modelValue.revert() $model.$modelValue.revert()
$el.find('div.edit-subject').hide() $el.find('div.edit-subject').hide()
@ -545,7 +570,7 @@ EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading) ->
template: template template: template
} }
module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue",
EditableSubjectDirective]) EditableSubjectDirective])
@ -553,7 +578,7 @@ module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$
## Editable subject directive ## Editable subject directive
############################################################################# #############################################################################
EditableDescriptionDirective = ($window, $document, $rootscope, $repo, $confirm, $compile, $loading) -> EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading, $selectedText, $qqueue) ->
template = """ template = """
<div class="view-description"> <div class="view-description">
<section class="us-content wysiwyg" <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..." <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" ng-model="item.description"
tg-markitup="tg-markitup"></textarea> 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"> <span class="save-container">
<a class="save icon icon-floppy" href="" title="Save" /> <a class="save icon icon-floppy" href="" title="Save" />
</span> </span>
@ -589,27 +618,8 @@ EditableDescriptionDirective = ($window, $document, $rootscope, $repo, $confirm,
isEditable = -> isEditable = ->
return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1 return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1
getSelectedText = -> save = $qqueue.bindAdd (description) =>
if $window.getSelection $model.$modelValue.description = description
return $window.getSelection().toString()
else if $document.selection
return $document.selection.createRange().text
return null
$el.on "mouseup", ".view-description", (event) ->
# We want to dettect the a inside the div so we use the target and
# not the currentTarget
target = angular.element(event.target)
return if not isEditable()
return if target.is('a')
return if getSelectedText()
$el.find('.edit-description').show()
$el.find('.view-description').hide()
$el.find('textarea').focus()
$el.on "click", ".save", ->
$model.$modelValue.description = $scope.item.description
$loading.start($el.find('.save-container')) $loading.start($el.find('.save-container'))
promise = $repo.save($model.$modelValue) promise = $repo.save($model.$modelValue)
@ -623,6 +633,22 @@ EditableDescriptionDirective = ($window, $document, $rootscope, $repo, $confirm,
promise.finally -> promise.finally ->
$loading.finish($el.find('.save-container')) $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) -> $el.on "keyup", "textarea", (event) ->
if event.keyCode == 27 if event.keyCode == 27
$scope.item.revert() $scope.item.revert()
@ -650,8 +676,8 @@ EditableDescriptionDirective = ($window, $document, $rootscope, $repo, $confirm,
template: template template: template
} }
module.directive("tgEditableDescription", ["$window", "$document", "$rootScope", "$tgRepo", "$tgConfirm", module.directive("tgEditableDescription", ["$rootScope", "$tgRepo", "$tgConfirm",
"$compile", "$tgLoading", EditableDescriptionDirective]) "$compile", "$tgLoading", "$selectedText", "$tgQqueue", EditableDescriptionDirective])
############################################################################# #############################################################################

View File

@ -23,7 +23,7 @@ taiga = @.taiga
timeout = @.taiga.timeout timeout = @.taiga.timeout
cancelTimeout = @.taiga.cancelTimeout cancelTimeout = @.taiga.cancelTimeout
debounce = @.taiga.debounce debounce = @.taiga.debounce
bindMethods = @.taiga.bindMethods
NOTIFICATION_MSG = { NOTIFICATION_MSG = {
"success": "success":
@ -42,7 +42,7 @@ class ConfirmService extends taiga.Service
@.$inject = ["$q", "lightboxService", "$tgLoading"] @.$inject = ["$q", "lightboxService", "$tgLoading"]
constructor: (@q, @lightboxService, @loading) -> constructor: (@q, @lightboxService, @loading) ->
_.bindAll(@) bindMethods(@)
hide: (el)-> hide: (el)->
if el if el
@ -170,7 +170,7 @@ class ConfirmService extends taiga.Service
return defered.promise return defered.promise
notify: (type, message, title) -> notify: (type, message, title, time) ->
# NOTE: Typesi are: error, success, light-error # NOTE: Typesi are: error, success, light-error
# See partials/components/notification-message.jade) # See partials/components/notification-message.jade)
# Add default texts to NOTIFICATION_MSG for new notification types # Add default texts to NOTIFICATION_MSG for new notification types
@ -178,6 +178,8 @@ class ConfirmService extends taiga.Service
selector = ".notification-message-#{type}" selector = ".notification-message-#{type}"
el = angular.element(selector) el = angular.element(selector)
return if el.hasClass("active")
if title if title
el.find("h4").html(title) el.find("h4").html(title)
else else
@ -200,6 +202,7 @@ class ConfirmService extends taiga.Service
if @.tsem if @.tsem
cancelTimeout(@.tsem) cancelTimeout(@.tsem)
if !time
time = if type == 'error' or type == 'light-error' then 3500 else 1500 time = if type == 'error' or type == 'light-error' then 3500 else 1500
@.tsem = timeout time, => @.tsem = timeout time, =>

View File

@ -165,7 +165,7 @@ module.directive("tgLbUsEstimation", ["$rootScope", "$tgRepo", "$tgConfirm", LbU
## User story estimation directive ## User story estimation directive
############################################################################# #############################################################################
UsEstimationDirective = ($rootScope, $repo, $confirm) -> UsEstimationDirective = ($rootScope, $repo, $confirm, $qqueue) ->
# Display the points of a US and you can edit it. # Display the points of a US and you can edit it.
# #
# Example: # Example:
@ -264,6 +264,29 @@ UsEstimationDirective = ($rootScope, $repo, $confirm) ->
return _.reduce(notNullValues, (acc, num) -> acc + num) 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) -> $el.on "click", ".total.clickable", (event) ->
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
@ -287,26 +310,7 @@ UsEstimationDirective = ($rootScope, $repo, $confirm) ->
roleId = target.data("role-id") roleId = target.data("role-id")
pointId = target.data("point-id") pointId = target.data("point-id")
$el.find(".popover").popover().close() save(roleId, pointId)
# 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)
$scope.$watch $attrs.ngModel, (us) -> $scope.$watch $attrs.ngModel, (us) ->
render(us) if us render(us) if us
@ -320,4 +324,4 @@ UsEstimationDirective = ($rootScope, $repo, $confirm) ->
require: "ngModel" 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) return @rs.history.undeleteComment(type, objectId, activityId).then => @.loadHistory(type, objectId)
HistoryDirective = ($log, $loading) -> HistoryDirective = ($log, $loading, $qqueue) ->
templateChangeDiff = _.template(""" templateChangeDiff = _.template("""
<div class="change-entry"> <div class="change-entry">
<div class="activity-changed"> <div class="activity-changed">
@ -233,6 +233,10 @@ HistoryDirective = ($log, $loading) ->
ng-model="<%- ngmodel %>.comment" tg-markitup="tg-markitup"> ng-model="<%- ngmodel %>.comment" tg-markitup="tg-markitup">
</textarea> </textarea>
<% if (mode !== "edit") { %> <% 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> <a href="" title="Comment" class="button button-green save-comment">Comment</a>
<% } %> <% } %>
</div> </div>
@ -432,6 +436,24 @@ HistoryDirective = ($log, $loading) ->
html = renderHistory(changes, totalChanges) html = renderHistory(changes, totalChanges)
$el.find(".changes-list").html(html) $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 # Watchers
$scope.$watch("comments", renderComments) $scope.$watch("comments", renderComments)
@ -443,22 +465,10 @@ HistoryDirective = ($log, $loading) ->
$el.on "click", ".add-comment a.button-green", debounce 2000, (event) -> $el.on "click", ".add-comment a.button-green", debounce 2000, (event) ->
event.preventDefault() event.preventDefault()
$scope.$broadcast("markdown-editor:submit")
target = angular.element(event.currentTarget) target = angular.element(event.currentTarget)
$el.find(".comment-list").addClass("activeanimation") save(target)
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)
$el.on "click", ".show-more", (event) -> $el.on "click", ".show-more", (event) ->
event.preventDefault() 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. # Issue/Userstory blocking message lightbox directive.
BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loading) -> BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loading, $qqueue) ->
link = ($scope, $el, $attrs, $model) -> link = ($scope, $el, $attrs, $model) ->
$el.find("h2.title").text($attrs.title) $el.find("h2.title").text($attrs.title)
$scope.$on "block", -> unblock = $qqueue.bindAdd (item, finishCallback) =>
$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 = ""
promise = $tgrepo.save(item) promise = $tgrepo.save(item)
promise.then -> promise.then ->
$confirm.notify("success") $confirm.notify("success")
@ -154,15 +146,9 @@ BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loadi
promise.finally -> promise.finally ->
finishCallback() finishCallback()
$scope.$on "$destroy", -> return promise
$el.off()
$el.on "click", ".button-green", (event) -> block = $qqueue.bindAdd (item) =>
event.preventDefault()
item = $model.$modelValue.clone()
item.is_blocked = true
item.blocked_note = $el.find(".reason").val()
$model.$setViewValue(item) $model.$setViewValue(item)
$loading.start($el.find(".button-green")) $loading.start($el.find(".button-green"))
@ -181,13 +167,36 @@ BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loadi
$loading.finish($el.find(".button-green")) $loading.finish($el.find(".button-green"))
lightboxService.close($el) 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 { return {
templateUrl: "/partials/views/modules/lightbox-block.html" templateUrl: "/partials/views/modules/lightbox-block.html"
link: link link: link
require: "ngModel" require: "ngModel"
} }
module.directive("tgLbBlock", ["$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) lightboxService.open($el)
$el.on "click", ".button-green", debounce 2000, (event) -> submit = debounce 2000, (event) =>
event.preventDefault() event.preventDefault()
form = $el.find("form").checksley()
target = angular.element(event.currentTarget)
form = $el.find("form").checksley()
if not form.validate() if not form.validate()
return return
$loading.start(target) $loading.start(submitButton)
if $scope.isNew if $scope.isNew
promise = $repo.create("userstories", $scope.us) promise = $repo.create("userstories", $scope.us)
@ -303,16 +311,21 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService,
broadcastEvent = "usform:edit:success" broadcastEvent = "usform:edit:success"
promise.then (data) -> promise.then (data) ->
$loading.finish(target) $loading.finish(submitButton)
lightboxService.close($el) lightboxService.close($el)
$rootScope.$broadcast(broadcastEvent, data) $rootScope.$broadcast(broadcastEvent, data)
promise.then null, (data) -> promise.then null, (data) ->
$loading.finish(target) $loading.finish(submitButton)
form.setErrors(data) form.setErrors(data)
if data._error_message if data._error_message
$confirm.notify("error", 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) -> $el.on "click", ".close", (event) ->
event.preventDefault() event.preventDefault()
$scope.$apply -> $scope.$apply ->
@ -356,28 +369,32 @@ CreateBulkUserstoriesDirective = ($repo, $rs, $rootscope, lightboxService, $load
} }
lightboxService.open($el) lightboxService.open($el)
$el.on "click", ".button-green", debounce 2000, (event) -> submit = debounce 2000, (event) =>
event.preventDefault() event.preventDefault()
target = angular.element(event.currentTarget)
form = $el.find("form").checksley({onlyOneErrorElement: true}) form = $el.find("form").checksley({onlyOneErrorElement: true})
if not form.validate() if not form.validate()
return return
$loading.start(target) $loading.start(submitButton)
promise = $rs.userstories.bulkCreate($scope.new.projectId, $scope.new.statusId, $scope.new.bulk) promise = $rs.userstories.bulkCreate($scope.new.projectId, $scope.new.statusId, $scope.new.bulk)
promise.then (result) -> promise.then (result) ->
$loading.finish(target) $loading.finish(submitButton)
$rootscope.$broadcast("usform:bulk:success", result) $rootscope.$broadcast("usform:bulk:success", result)
lightboxService.close($el) lightboxService.close($el)
promise.then null, (data) -> promise.then null, (data) ->
$loading.finish(target) $loading.finish(submitButton)
form.setErrors(data) form.setErrors(data)
if data._error_message if data._error_message
$confirm.notify("error", 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", -> $scope.$on "$destroy", ->
$el.off() $el.off()

View File

@ -228,7 +228,7 @@ module.directive("tgLbTagLine", ["$tgResources", LbTagLineDirective])
## TagLine Directive (for detail pages) ## TagLine Directive (for detail pages)
############################################################################# #############################################################################
TagLineDirective = ($rootScope, $repo, $rs, $confirm) -> TagLineDirective = ($rootScope, $repo, $rs, $confirm, $qqueue) ->
ENTER_KEY = 13 ENTER_KEY = 13
ESC_KEY = 27 ESC_KEY = 27
@ -288,7 +288,7 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm) ->
$el.find("input").autocomplete("close") $el.find("input").autocomplete("close")
## Aux methods ## Aux methods
addValue = (value) -> addValue = $qqueue.bindAdd (value) ->
value = trim(value.toLowerCase()) value = trim(value.toLowerCase())
return if value.length == 0 return if value.length == 0
@ -308,7 +308,7 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm) ->
$model.$setViewValue(model) $model.$setViewValue(model)
$repo.save(model).then(onSuccess, onError) $repo.save(model).then(onSuccess, onError)
deleteValue = (value) -> deleteValue = $qqueue.bindAdd (value) ->
value = trim(value.toLowerCase()) value = trim(value.toLowerCase())
return if value.length == 0 return if value.length == 0
@ -325,7 +325,8 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm) ->
$confirm.notify("error") $confirm.notify("error")
model.revert() model.revert()
$model.$setViewValue(model) $model.$setViewValue(model)
$repo.save(model).then(onSuccess, onError)
return $repo.save(model).then(onSuccess, onError)
saveInputTag = () -> saveInputTag = () ->
value = $el.find("input").val() value = $el.find("input").val()
@ -369,6 +370,7 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm) ->
target = angular.element(event.currentTarget) target = angular.element(event.currentTarget)
value = target.siblings(".tag-name").text() value = target.siblings(".tag-name").text()
deleteValue(value) deleteValue(value)
bindOnce $scope, "project", (project) -> bindOnce $scope, "project", (project) ->
@ -415,4 +417,4 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm) ->
template: template 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 ## WYSIWYG markitup editor directive
############################################################################# #############################################################################
tgMarkitupDirective = ($rootscope, $rs, $tr) -> tgMarkitupDirective = ($rootscope, $rs, $tr, $selectedText) ->
previewTemplate = _.template(""" previewTemplate = _.template("""
<div class="preview"> <div class="preview">
<div class="actions"> <div class="actions">
@ -61,10 +61,16 @@ tgMarkitupDirective = ($rootscope, $rs, $tr) ->
markdownDomNode.append(previewTemplate({data: data.data})) markdownDomNode.append(previewTemplate({data: data.data}))
markItUpDomNode.hide() markItUpDomNode.hide()
# FIXME: Really `.parents()` is need? seems `.closest` markdown = element.closest(".markdown")
# function is better aproach for it
element.parents(".markdown").one "click", ".preview", (event) -> markdown.on "mouseup.preview", ".preview", (event) ->
event.preventDefault() event.preventDefault()
target = angular.element(event.target)
if !target.is('a') and $selectedText.get().length
return
markdown.off(".preview")
closePreviewMode() closePreviewMode()
markdownCaretPositon = false markdownCaretPositon = false
@ -277,4 +283,4 @@ tgMarkitupDirective = ($rootscope, $rs, $tr) ->
return {link:link, require:"ngModel"} 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 taiga = @.taiga
startswith = @.taiga.startswith
bindMethods = @.taiga.bindMethods
module = angular.module("taigaEvents", []) module = angular.module("taigaEvents", [])
class EventsService class EventsService
constructor: (@win, @log, @config, @auth) -> constructor: (@win, @log, @config, @auth) ->
_.bindAll(@) bindMethods(@)
initialize: (sessionId) -> initialize: (sessionId) ->
@.sessionId = sessionId @.sessionId = sessionId
@ -41,7 +43,18 @@ class EventsService
setupConnection: -> setupConnection: ->
@.stopExistingConnection() @.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 = new @win.WebSocket(url)
@.ws.addEventListener("open", @.onOpen) @.ws.addEventListener("open", @.onOpen)

View File

@ -29,29 +29,33 @@ trim = @.taiga.trim
module = angular.module("taigaFeedback", []) module = angular.module("taigaFeedback", [])
FeedbackDirective = ($lightboxService, $repo, $confirm)-> FeedbackDirective = ($lightboxService, $repo, $confirm, $loading)->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
form = $el.find("form").checksley() form = $el.find("form").checksley()
submit = debounce 2000, -> submit = debounce 2000, (event) =>
event.preventDefault()
if not form.validate() if not form.validate()
return return
$loading.start(submitButton)
promise = $repo.create("feedback", $scope.feedback) promise = $repo.create("feedback", $scope.feedback)
promise.then (data) -> promise.then (data) ->
$loading.finish(submitButton)
$lightboxService.close($el) $lightboxService.close($el)
$confirm.notify("success", "\\o/ we'll be happy to read your") $confirm.notify("success", "\\o/ we'll be happy to read your")
promise.then null, -> promise.then null, ->
$loading.finish(submitButton)
$confirm.notify("error") $confirm.notify("error")
$el.on "submit", (event) -> submitButton = $el.find(".submit-button")
submit()
$el.on "click", ".button-green", (event) -> $el.on "submit", "form", submit
event.preventDefault() $el.on "click", ".submit-button", submit
submit()
$scope.$on "feedback:show", -> $scope.$on "feedback:show", ->
$scope.$apply -> $scope.$apply ->
@ -65,4 +69,4 @@ FeedbackDirective = ($lightboxService, $repo, $confirm)->
return {link:link} 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 ## 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. # Display the status of Issue and you can edit it.
# #
# Example: # Example:
@ -236,6 +236,21 @@ IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
}) })
$el.html(html) $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) -> $el.on "click", ".status-data", (event) ->
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
@ -256,20 +271,7 @@ IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
issue.status = target.data("status-id") issue.status = target.data("status-id")
$model.$setViewValue(issue) $model.$setViewValue(issue)
$scope.$apply() save($model.$modelValue, 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)
$scope.$watch $attrs.ngModel, (issue) -> $scope.$watch $attrs.ngModel, (issue) ->
render(issue) if issue render(issue) if issue
@ -283,13 +285,13 @@ IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
require: "ngModel" require: "ngModel"
} }
module.directive("tgIssueStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", IssueStatusButtonDirective]) module.directive("tgIssueStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", IssueStatusButtonDirective])
############################################################################# #############################################################################
## Issue type button directive ## 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. # Display the type of Issue and you can edit it.
# #
# Example: # Example:
@ -330,6 +332,26 @@ IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
}) })
$el.html(html) $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) -> $el.on "click", ".type-data", (event) ->
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
@ -343,26 +365,8 @@ IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
return if not isEditable() return if not isEditable()
target = angular.element(event.currentTarget) target = angular.element(event.currentTarget)
type = target.data("type-id")
$.fn.popover().closeAll() save(type)
issue = $model.$modelValue.clone()
issue.type = target.data("type-id")
$model.$setViewValue(issue)
$scope.$apply()
onSuccess = ->
$confirm.notify("success")
$rootScope.$broadcast("history:reload")
$loading.finish($el.find(".level-name"))
onError = ->
$confirm.notify("error")
issue.revert()
$model.$setViewValue(issue)
$loading.finish($el.find(".level-name"))
$loading.start($el.find(".level-name"))
$repo.save($model.$modelValue).then(onSuccess, onError)
$scope.$watch $attrs.ngModel, (issue) -> $scope.$watch $attrs.ngModel, (issue) ->
render(issue) if issue render(issue) if issue
@ -376,14 +380,14 @@ IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
require: "ngModel" require: "ngModel"
} }
module.directive("tgIssueTypeButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", IssueTypeButtonDirective]) module.directive("tgIssueTypeButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", IssueTypeButtonDirective])
############################################################################# #############################################################################
## Issue severity button directive ## 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. # Display the severity of Issue and you can edit it.
# #
# Example: # Example:
@ -424,6 +428,26 @@ IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
}) })
$el.html(html) $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) -> $el.on "click", ".severity-data", (event) ->
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
@ -437,26 +461,9 @@ IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
return if not isEditable() return if not isEditable()
target = angular.element(event.currentTarget) target = angular.element(event.currentTarget)
severity = target.data("severity-id")
$.fn.popover().closeAll() save(severity)
issue = $model.$modelValue.clone()
issue.severity = target.data("severity-id")
$model.$setViewValue(issue)
$scope.$apply()
onSuccess = ->
$confirm.notify("success")
$rootScope.$broadcast("history:reload")
$loading.finish($el.find(".level-name"))
onError = ->
$confirm.notify("error")
issue.revert()
$model.$setViewValue(issue)
$loading.finish($el.find(".level-name"))
$loading.start($el.find(".level-name"))
$repo.save($model.$modelValue).then(onSuccess, onError)
$scope.$watch $attrs.ngModel, (issue) -> $scope.$watch $attrs.ngModel, (issue) ->
render(issue) if issue render(issue) if issue
@ -470,14 +477,14 @@ IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
require: "ngModel" require: "ngModel"
} }
module.directive("tgIssueSeverityButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", IssueSeverityButtonDirective]) module.directive("tgIssueSeverityButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", IssueSeverityButtonDirective])
############################################################################# #############################################################################
## Issue priority button directive ## 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. # Display the priority of Issue and you can edit it.
# #
# Example: # Example:
@ -518,6 +525,26 @@ IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
}) })
$el.html(html) $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) -> $el.on "click", ".priority-data", (event) ->
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
@ -531,26 +558,9 @@ IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
return if not isEditable() return if not isEditable()
target = angular.element(event.currentTarget) target = angular.element(event.currentTarget)
priority = target.data("priority-id")
$.fn.popover().closeAll() save(priority)
issue = $model.$modelValue.clone()
issue.priority = target.data("priority-id")
$model.$setViewValue(issue)
$scope.$apply()
onSuccess = ->
$confirm.notify("success")
$rootScope.$broadcast("history:reload")
$loading.finish($el.find(".level-name"))
onError = ->
$confirm.notify("error")
issue.revert()
$model.$setViewValue(issue)
$loading.finish($el.find(".level-name"))
$loading.start($el.find(".level-name"))
$repo.save($model.$modelValue).then(onSuccess, onError)
$scope.$watch $attrs.ngModel, (issue) -> $scope.$watch $attrs.ngModel, (issue) ->
render(issue) if issue render(issue) if issue
@ -564,14 +574,14 @@ IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
require: "ngModel" 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 ## Promote Issue to US button directive
############################################################################# #############################################################################
PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm) -> PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm, $qqueue) ->
template = _.template(""" template = _.template("""
<a class="button button-gray editable" tg-check-permission="add_us"> <a class="button button-gray editable" tg-check-permission="add_us">
Promote to User Story Promote to User Story
@ -579,15 +589,8 @@ PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm) ->
""") # TODO: i18n """) # TODO: i18n
link = ($scope, $el, $attrs, $model) -> link = ($scope, $el, $attrs, $model) ->
$el.on "click", "a", (event) ->
event.preventDefault()
issue = $model.$modelValue
title = "Promote this issue to a new user story" # TODO: i18n save = $qqueue.bindAdd (issue, finish) =>
message = "Are you sure you want to create a new US from this Issue?" # TODO: i18n
subtitle = issue.subject
$confirm.ask(title, subtitle, message).then (finish) =>
data = { data = {
generated_from_issue: issue.id generated_from_issue: issue.id
project: issue.project, project: issue.project,
@ -609,6 +612,19 @@ PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm) ->
$repo.create("userstories", data).then(onSuccess, onError) $repo.create("userstories", data).then(onSuccess, onError)
$el.on "click", "a", (event) ->
event.preventDefault()
issue = $model.$modelValue
title = "Promote this issue to a new user story" # TODO: i18n
message = "Are you sure you want to create a new US from this Issue?" # TODO: i18n
subtitle = issue.subject
$confirm.ask(title, subtitle, message).then (finish) =>
save(issue, finish)
$scope.$on "$destroy", -> $scope.$on "$destroy", ->
$el.off() $el.off()
@ -619,5 +635,5 @@ PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm) ->
link: link link: link
} }
module.directive("tgPromoteIssueToUsButton", ["$rootScope", "$tgRepo", "$tgConfirm", module.directive("tgPromoteIssueToUsButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgQqueue",
PromoteIssueToUsButtonDirective]) PromoteIssueToUsButtonDirective])

View File

@ -29,7 +29,7 @@ module = angular.module("taigaIssues")
## Issue Create Lightbox Directive ## Issue Create Lightbox Directive
############################################################################# #############################################################################
CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService) -> CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService, $loading) ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
form = $el.find("form").checksley() form = $el.find("form").checksley()
$scope.issue = {} $scope.issue = {}
@ -50,31 +50,35 @@ CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService) ->
$scope.$on "$destroy", -> $scope.$on "$destroy", ->
$el.off() $el.off()
submit = debounce 2000, -> submit = debounce 2000, (event) =>
event.preventDefault()
if not form.validate() if not form.validate()
return return
$loading.start(submitButton)
promise = $repo.create("issues", $scope.issue) promise = $repo.create("issues", $scope.issue)
promise.then (data) -> promise.then (data) ->
$loading.finish(submitButton)
$rootscope.$broadcast("issueform:new:success", data) $rootscope.$broadcast("issueform:new:success", data)
lightboxService.close($el) lightboxService.close($el)
$confirm.notify("success") $confirm.notify("success")
promise.then null, -> promise.then null, ->
$loading.finish(submitButton)
$confirm.notify("error") $confirm.notify("error")
$el.on "click", ".button-green", (event) ->
event.preventDefault()
submit()
$el.on "submit", "form", (event) -> submitButton = $el.find(".submit-button")
event.preventDefault()
submit() $el.on "submit", "form", submit
$el.on "click", ".submit-button", submit
return {link:link} return {link:link}
module.directive("tgLbCreateIssue", ["$tgRepo", "$tgConfirm", "$rootScope", "lightboxService", module.directive("tgLbCreateIssue", ["$tgRepo", "$tgConfirm", "$rootScope", "lightboxService", "$tgLoading",
CreateIssueDirective]) CreateIssueDirective])
@ -82,7 +86,7 @@ module.directive("tgLbCreateIssue", ["$tgRepo", "$tgConfirm", "$rootScope", "lig
## Issue Bulk Create Lightbox Directive ## Issue Bulk Create Lightbox Directive
############################################################################# #############################################################################
CreateBulkIssuesDirective = ($repo, $rs, $confirm, $rootscope, lightboxService) -> CreateBulkIssuesDirective = ($repo, $rs, $confirm, $rootscope, $loading, lightboxService) ->
link = ($scope, $el, attrs) -> link = ($scope, $el, attrs) ->
$scope.$on "issueform:bulk", (ctx, projectId, status)-> $scope.$on "issueform:bulk", (ctx, projectId, status)->
lightboxService.open($el) lightboxService.open($el)
@ -91,29 +95,38 @@ CreateBulkIssuesDirective = ($repo, $rs, $confirm, $rootscope, lightboxService)
bulk: "" bulk: ""
} }
$el.on "click", ".button-green", debounce 2000, (event) -> submit = debounce 2000, (event) =>
event.preventDefault() event.preventDefault()
form = $el.find("form").checksley() form = $el.find("form").checksley()
if not form.validate() if not form.validate()
return return
$loading.start(submitButton)
data = $scope.new.bulk data = $scope.new.bulk
projectId = $scope.new.projectId projectId = $scope.new.projectId
promise = $rs.issues.bulkCreate(projectId, data) promise = $rs.issues.bulkCreate(projectId, data)
promise.then (result) -> promise.then (result) ->
$loading.finish(submitButton)
$rootscope.$broadcast("issueform:new:success", result) $rootscope.$broadcast("issueform:new:success", result)
lightboxService.close($el) lightboxService.close($el)
$confirm.notify("success") $confirm.notify("success")
promise.then null, -> promise.then null, ->
$loading.finish(submitButton)
$confirm.notify("error") $confirm.notify("error")
submitButton = $el.find(".submit-button")
$el.on "submit", "form", submit
$el.on "click", ".submit-button", submit
$scope.$on "$destroy", -> $scope.$on "$destroy", ->
$el.off() $el.off()
return {link: link} return {link: link}
module.directive("tgLbCreateBulkIssues", ["$tgRepo", "$tgResources", "$tgConfirm", "$rootScope", module.directive("tgLbCreateBulkIssues", ["$tgRepo", "$tgResources", "$tgConfirm", "$rootScope", "$tgLoading",
"lightboxService", CreateBulkIssuesDirective]) "lightboxService", CreateBulkIssuesDirective])

View File

@ -27,6 +27,7 @@ scopeDefer = @.taiga.scopeDefer
bindOnce = @.taiga.bindOnce bindOnce = @.taiga.bindOnce
groupBy = @.taiga.groupBy groupBy = @.taiga.groupBy
timeout = @.taiga.timeout timeout = @.taiga.timeout
bindMethods = @.taiga.bindMethods
module = angular.module("taigaKanban") 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, constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
@appTitle, @navUrls, @events, @analytics, tgLoader) -> @appTitle, @navUrls, @events, @analytics, tgLoader) ->
_.bindAll(@)
bindMethods(@)
@scope.sectionName = "Kanban" @scope.sectionName = "Kanban"
@scope.statusViewModes = {} @scope.statusViewModes = {}
@.initializeEventHandlers() @.initializeEventHandlers()

View File

@ -121,7 +121,10 @@ ProjectsNavigationDirective = ($rootscope, animationFrame, $timeout, tgLoader, $
timeout timeoutValue, -> timeout timeoutValue, ->
overlay.one 'transitionend', () -> 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() overlay.hide()
$(document.body).addClass("closed-projects-nav") $(document.body).addClass("closed-projects-nav")
@ -153,11 +156,12 @@ ProjectsNavigationDirective = ($rootscope, animationFrame, $timeout, tgLoader, $
$scope.$on "nav:projects-list:open", -> $scope.$on "nav:projects-list:open", ->
if !$(document.body).hasClass("open-projects-nav") if !$(document.body).hasClass("open-projects-nav")
animationFrame.add () -> animationFrame.add () => overlay.show()
overlay.show()
animationFrame.add () -> animationFrame.add(
$(document.body).toggleClass("open-projects-nav") () => $(document.body).css("overflow-x", "hidden")
() => $(document.body).toggleClass("open-projects-nav")
)
$el.on "click", ".projects-list > li > a", (event) -> $el.on "click", ".projects-list > li > a", (event) ->
# HACK: to solve a problem with the loader when the next url # HACK: to solve a problem with the loader when the next url
@ -243,6 +247,12 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $
</a> </a>
</li> </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) { %> <% if (project.videoconferences) { %>
<li id="nav-video"> <li id="nav-video">
<a href="<%- project.videoconferenceUrl %>" target="_blank" title="Meet Up"> <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> <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) -> renderMainMenu = ($el) ->
html = mainTemplate({}) html = mainTemplate({})
$el.html(html) $el.html(html)
@ -318,7 +344,7 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $
# content loaded signal is raised using inner scope. # content loaded signal is raised using inner scope.
renderMenuEntries = ($el, targetScope, project={}) -> renderMenuEntries = ($el, targetScope, project={}) ->
container = $el.find(".menu-container") container = $el.find(".menu-container")
sectionName = targetScope.section sectionName = getSectionName($el, targetScope.section, project)
ctx = { ctx = {
user: $auth.getUser(), user: $auth.getUser(),

View File

@ -26,7 +26,7 @@ debounce = @.taiga.debounce
module = angular.module("taigaProject") 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) -> link = ($scope, $el, attrs) ->
$scope.data = {} $scope.data = {}
$scope.templates = [] $scope.templates = []
@ -39,12 +39,14 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
# than another deleted in the same session # than another deleted in the same session
$cacheFactory.get('$http').removeAll() $cacheFactory.get('$http').removeAll()
$loading.finish(submitButton)
$rootscope.$broadcast("projects:reload") $rootscope.$broadcast("projects:reload")
$confirm.notify("success", "Success") #TODO: i18n $confirm.notify("success", "Success") #TODO: i18n
$location.url($projectUrl.get(response)) $location.url($projectUrl.get(response))
lightboxService.close($el) lightboxService.close($el)
onErrorSubmit = (response) -> onErrorSubmit = (response) ->
$loading.finish(submitButton)
form.setErrors(response) form.setErrors(response)
selectors = [] selectors = []
for error_field in _.keys(response) for error_field in _.keys(response)
@ -54,10 +56,14 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
error_step.addClass("active") error_step.addClass("active")
$el.find('.progress-bar').removeClass().addClass('progress-bar').addClass(error_step.data("step")) $el.find('.progress-bar').removeClass().addClass('progress-bar').addClass(error_step.data("step"))
submit = -> submit = (event) =>
event.preventDefault()
if not form.validate() if not form.validate()
return return
$loading.start(submitButton)
promise = $repo.create("projects", $scope.data) promise = $repo.create("projects", $scope.data)
promise.then(onSuccessSubmit, onErrorSubmit) promise.then(onSuccessSubmit, onErrorSubmit)
@ -109,10 +115,10 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
step = prev.data('step') step = prev.data('step')
$el.find('.progress-bar').removeClass().addClass('progress-bar').addClass(step) $el.find('.progress-bar').removeClass().addClass('progress-bar').addClass(step)
submitButton = $el.find(".submit-button")
$el.on "click", ".button-submit", debounce 2000, (event) -> $el.on "submit", "form", submit
event.preventDefault() $el.on "click", ".submit-button", submit
submit()
$el.on "click", ".close", (event) -> $el.on "click", ".close", (event) ->
event.preventDefault() event.preventDefault()
@ -121,7 +127,7 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
return {link:link} return {link:link}
module.directive("tgLbCreateProject", ["$rootScope", "$tgRepo", "$tgConfirm", "$location", "$tgNavUrls", module.directive("tgLbCreateProject", ["$rootScope", "$tgRepo", "$tgConfirm", "$location", "$tgNavUrls",
"$tgResources", "$projectUrl", "lightboxService", "$cacheFactory", CreateProject]) "$tgResources", "$projectUrl", "$tgLoading", "lightboxService", "$cacheFactory", CreateProject])
############################################################################# #############################################################################

View File

@ -42,6 +42,7 @@ urls = {
"userstories-restore": "/userstories/%s/restore" "userstories-restore": "/userstories/%s/restore"
"tasks": "/tasks" "tasks": "/tasks"
"bulk-create-tasks": "/tasks/bulk_create" "bulk-create-tasks": "/tasks/bulk_create"
"bulk-update-task-taskboard-order": "/tasks/bulk_update_taskboard_order"
"tasks-restore": "/tasks/%s/restore" "tasks-restore": "/tasks/%s/restore"
"issues": "/issues" "issues": "/issues"
"bulk-create-issues": "/issues/bulk_create" "bulk-create-issues": "/issues/bulk_create"

View File

@ -28,11 +28,14 @@ resourceProvider = ($repo, $http, $urls) ->
service.get = (id) -> service.get = (id) ->
return $repo.queryOne("memberships", id) return $repo.queryOne("memberships", id)
service.list = (projectId, filters) -> service.list = (projectId, filters, enablePagination=true) ->
params = {project: projectId} params = {project: projectId}
params = _.extend({}, params, filters or {}) params = _.extend({}, params, filters or {})
if enablePagination
return $repo.queryPaginated("memberships", params) return $repo.queryPaginated("memberships", params)
return $repo.queryMany("memberships", params, options={enablePagination:enablePagination})
service.listByUser = (userId, filters) -> service.listByUser = (userId, filters) ->
params = {user: userId} params = {user: userId}
params = _.extend({}, params, filters or {}) params = _.extend({}, params, filters or {})

View File

@ -22,7 +22,7 @@
taiga = @.taiga taiga = @.taiga
resourceProvider = ($repo) -> resourceProvider = ($repo, $http, $urls) ->
service = {} service = {}
service.get = (id) -> service.get = (id) ->
@ -45,6 +45,13 @@ resourceProvider = ($repo) ->
service.stats = (projectId) -> service.stats = (projectId) ->
return $repo.queryOneRaw("projects", "#{projectId}/stats") 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) -> service.tagsColors = (id) ->
return $repo.queryOne("projects", "#{id}/tags_colors") return $repo.queryOne("projects", "#{id}/tags_colors")
@ -53,4 +60,4 @@ resourceProvider = ($repo) ->
module = angular.module("taigaResources") 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) -> resourceProvider = ($repo, $http, $urls, $storage) ->
service = {} service = {}
hashSuffix = "tasks-queryparams" hashSuffix = "tasks-queryparams"
hashSuffixStatusColumnModes = "tasks-statuscolumnmodels"
hashSuffixUsRowModes = "tasks-usrowmodels"
service.get = (projectId, taskId) -> service.get = (projectId, taskId) ->
params = service.getQueryParams(projectId) params = service.getQueryParams(projectId)
@ -46,6 +48,11 @@ resourceProvider = ($repo, $http, $urls, $storage) ->
return $http.post(url, params).then (result) -> return $http.post(url, params).then (result) ->
return result.data 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) -> service.listValues = (projectId, type) ->
params = {"project": projectId} params = {"project": projectId}
return $repo.queryMany(type, params) return $repo.queryMany(type, params)
@ -60,6 +67,28 @@ resourceProvider = ($repo, $http, $urls, $storage) ->
hash = generateHash([projectId, ns]) hash = generateHash([projectId, ns])
return $storage.get(hash) or {} 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) -> return (instance) ->
instance.tasks = service instance.tasks = service

View File

@ -26,6 +26,7 @@ bindOnce = @.taiga.bindOnce
mixOf = @.taiga.mixOf mixOf = @.taiga.mixOf
debounceLeading = @.taiga.debounceLeading debounceLeading = @.taiga.debounceLeading
trim = @.taiga.trim trim = @.taiga.trim
debounce = @.taiga.debounce
module = angular.module("taigaSearch", []) module = angular.module("taigaSearch", [])
@ -111,7 +112,9 @@ SearchBoxDirective = ($lightboxService, $navurls, $location, $route)->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
project = null project = null
submit = -> submit = debounce 2000, (event) =>
event.preventDefault()
form = $el.find("form").checksley() form = $el.find("form").checksley()
if not form.validate() if not form.validate()
return return
@ -131,12 +134,8 @@ SearchBoxDirective = ($lightboxService, $navurls, $location, $route)->
$lightboxService.open($el) $lightboxService.open($el)
$el.find("#search-text").val("") $el.find("#search-text").val("")
$el.on "submit", (event) -> $el.on "submit", "form", submit
submit() $el.on "click", ".submit-button", submit
$el.on "click", ".button-green", (event) ->
event.preventDefault()
submit()
return {link:link} return {link:link}

View File

@ -23,7 +23,7 @@ taiga = @.taiga
bindOnce = @.taiga.bindOnce bindOnce = @.taiga.bindOnce
debounce = @.taiga.debounce debounce = @.taiga.debounce
CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, lightboxService) -> CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxService) ->
link = ($scope, $el, attrs) -> link = ($scope, $el, attrs) ->
$scope.isNew = true $scope.isNew = true
@ -53,7 +53,10 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, lightboxService) ->
$el.find(".title").html("Edit task ") #TODO: i18n $el.find(".title").html("Edit task ") #TODO: i18n
lightboxService.open($el) lightboxService.open($el)
$el.on "click", ".button-green", debounce 2000, (event) ->
submitButton = $el.find(".submit-button")
submit = debounce 2000, (event) =>
event.preventDefault() event.preventDefault()
form = $el.find("form").checksley() form = $el.find("form").checksley()
@ -67,32 +70,36 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, lightboxService) ->
promise = $repo.save($scope.task) promise = $repo.save($scope.task)
broadcastEvent = "taskform:edit:success" broadcastEvent = "taskform:edit:success"
$loading.start(submitButton)
# FIXME: error handling? # FIXME: error handling?
promise.then (data) -> promise.then (data) ->
$loading.finish(submitButton)
lightboxService.close($el) lightboxService.close($el)
$rootscope.$broadcast(broadcastEvent, data) $rootscope.$broadcast(broadcastEvent, data)
$el.on "submit", "form", submit
$el.on "click", ".submit-button", submit
$scope.$on "$destroy", -> $scope.$on "$destroy", ->
$el.off() $el.off()
return {link: link} return {link: link}
CreateBulkTasksDirective = ($repo, $rs, $rootscope, lightboxService) -> CreateBulkTasksDirective = ($repo, $rs, $rootscope, $loading, lightboxService) ->
link = ($scope, $el, attrs) -> link = ($scope, $el, attrs) ->
$scope.form = {data: "", usId: null} $scope.form = {data: "", usId: null}
$scope.$on "taskform:bulk", (ctx, sprintId, usId)-> submit = debounce 2000, (event) =>
lightboxService.open($el)
$scope.form = {data: "", sprintId: sprintId, usId: usId}
$el.on "click", ".button-green", debounce 2000, (event) ->
event.preventDefault() event.preventDefault()
form = $el.find("form").checksley() form = $el.find("form").checksley()
if not form.validate() if not form.validate()
return return
$loading.start(submitButton)
data = $scope.form.data data = $scope.form.data
projectId = $scope.projectId projectId = $scope.projectId
sprintId = $scope.form.sprintId sprintId = $scope.form.sprintId
@ -100,13 +107,24 @@ CreateBulkTasksDirective = ($repo, $rs, $rootscope, lightboxService) ->
promise = $rs.tasks.bulkCreate(projectId, sprintId, usId, data) promise = $rs.tasks.bulkCreate(projectId, sprintId, usId, data)
promise.then (result) -> promise.then (result) ->
$loading.finish(submitButton)
$rootscope.$broadcast("taskform:bulk:success", result) $rootscope.$broadcast("taskform:bulk:success", result)
lightboxService.close($el) lightboxService.close($el)
# TODO: error handling # TODO: error handling
promise.then null, -> promise.then null, ->
$loading.finish(submitButton)
console.log "FAIL" 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", -> $scope.$on "$destroy", ->
$el.off() $el.off()
@ -120,6 +138,7 @@ module.directive("tgLbCreateEditTask", [
"$tgModel", "$tgModel",
"$tgResources", "$tgResources",
"$rootScope", "$rootScope",
"$tgLoading",
"lightboxService", "lightboxService",
CreateEditTaskDirective CreateEditTaskDirective
]) ])
@ -128,6 +147,7 @@ module.directive("tgLbCreateBulkTasks", [
"$tgRepo", "$tgRepo",
"$tgResources", "$tgResources",
"$rootScope", "$rootScope",
"$tgLoading",
"lightboxService", "lightboxService",
CreateBulkTasksDirective CreateBulkTasksDirective
]) ])

View File

@ -26,6 +26,7 @@ groupBy = @.taiga.groupBy
bindOnce = @.taiga.bindOnce bindOnce = @.taiga.bindOnce
scopeDefer = @.taiga.scopeDefer scopeDefer = @.taiga.scopeDefer
timeout = @.taiga.timeout timeout = @.taiga.timeout
bindMethods = @.taiga.bindMethods
module = angular.module("taigaTaskboard") 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, constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @appTitle, @location, @navUrls,
@events, @analytics, tgLoader) -> @events, @analytics, tgLoader) ->
_.bindAll(@) bindMethods(@)
@scope.sectionName = "Taskboard" @scope.sectionName = "Taskboard"
@.initializeEventHandlers() @.initializeEventHandlers()
@ -102,7 +103,6 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
loadProject: -> loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) => return @rs.projects.get(@scope.projectId).then (project) =>
@scope.project = project @scope.project = project
@scope.$emit('project:loaded', project)
# Not used at this momment # Not used at this momment
@scope.pointsList = _.sortBy(project.points, "order") @scope.pointsList = _.sortBy(project.points, "order")
# @scope.roleList = _.sortBy(project.roles, "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.taskStatusList = _.sortBy(project.task_statuses, "order")
@scope.usStatusList = _.sortBy(project.us_statuses, "order") @scope.usStatusList = _.sortBy(project.us_statuses, "order")
@scope.usStatusById = groupBy(project.us_statuses, (e) -> e.id) @scope.usStatusById = groupBy(project.us_statuses, (e) -> e.id)
@scope.$emit('project:loaded', project)
return project return project
loadSprintStats: -> loadSprintStats: ->
@ -144,7 +147,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
loadTasks: -> loadTasks: ->
return @rs.tasks.list(@scope.projectId, @scope.sprintId).then (tasks) => return @rs.tasks.list(@scope.projectId, @scope.sprintId).then (tasks) =>
@scope.tasks = tasks @scope.tasks = _.sortBy(tasks, 'taskboard_order')
@scope.usTasks = {} @scope.usTasks = {}
# Iterate over all userstories and # Iterate over all userstories and
@ -183,21 +186,46 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
.then(=> @.loadUsersAndRoles()) .then(=> @.loadUsersAndRoles())
.then(=> @.loadTaskboard()) .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) -> taskMove: (ctx, task, usId, statusId, order) ->
# Remove task from old position # Remove task from old position
r = @scope.usTasks[task.user_story][task.status].indexOf(task) r = @scope.usTasks[task.user_story][task.status].indexOf(task)
@scope.usTasks[task.user_story][task.status].splice(r, 1) @scope.usTasks[task.user_story][task.status].splice(r, 1)
# Add task to new position # 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.user_story = usId
task.status = statusId task.status = statusId
task.order = order task.taskboard_order = order
promise = @repo.save(task) promise = @repo.save(task)
@rootscope.$broadcast("sprint:task:moved", task)
promise.then => promise.then =>
@.refreshTasksOrder(tasks)
@.loadSprintStats() @.loadSprintStats()
promise.then null, => promise.then null, =>
console.log "FAIL TASK SAVE" console.log "FAIL TASK SAVE"
@ -267,22 +295,6 @@ TaskboardTaskDirective = ($rootscope) ->
module.directive("tgTaskboardTask", ["$rootScope", TaskboardTaskDirective]) 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 ## Taskboard Table Height Fixer Directive
############################################################################# #############################################################################
@ -307,64 +319,156 @@ TaskboardTableHeightFixerDirective = ->
module.directive("tgTaskboardTableHeightFixer", 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 ## Taskboard User Directive
############################################################################# #############################################################################
TaskboardUserDirective = ($log) -> TaskboardUserDirective = ($log) ->
template = _.template(""" template = """
<figure class="avatar"> <figure class="avatar avatar-assigned-to">
<a href="#" title="Assign task" <% if (!clickable) {%>class="not-clickable"<% } %>> <a href="#" title="Assign task" ng-class="{'not-clickable': !clickable}">
<img src="<%- imgurl %>" alt="<%- name %>"> <img ng-src="{{imgurl}}">
</a> </a>
</figure> </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 clickable = false
link = ($scope, $el, $attrs, $model) -> link = ($scope, $el, $attrs) ->
if not $attrs.tgTaskboardUserAvatar?
return $log.error "TaskboardUserDirective: no attr is defined"
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)
render = (user) ->
if user is undefined
ctx = {name: "Unassigned", imgurl: "/images/unnamed.png", clickable: clickable}
else
ctx = {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 = $el.parent().find("a.task-assigned")
username_label.html(ctx.name)
username_label.on "click", (event) -> username_label.on "click", (event) ->
if $el.find('a').hasClass('noclick') if $el.find('a').hasClass('noclick')
return return
us = $model.$modelValue
$ctrl = $el.controller() $ctrl = $el.controller()
$ctrl.editTaskAssignedTo(us) $ctrl.editTaskAssignedTo($scope.task)
$scope.$watch 'task.assigned_to', (assigned_to) ->
user = $scope.usersById[assigned_to]
if user is undefined
_.assign($scope, {name: "Unassigned", imgurl: "/images/unnamed.png", clickable: clickable})
else
_.assign($scope, {name: user.full_name_display, imgurl: user.photo, clickable: clickable})
username_label.text($scope.name)
bindOnce $scope, "project", (project) -> bindOnce $scope, "project", (project) ->
if project.my_permissions.indexOf("modify_task") > -1 if project.my_permissions.indexOf("modify_task") > -1
clickable = true clickable = true
$el.on "click", (event) => $el.find(".avatar-assigned-to").on "click", (event) =>
if $el.find('a').hasClass('noclick') if $el.find('a').hasClass('noclick')
return return
us = $model.$modelValue
$ctrl = $el.controller() $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]) module.directive("tgTaskboardUserAvatar", ["$log", TaskboardUserDirective])

View File

@ -199,7 +199,7 @@ module.directive("tgTaskStatusDisplay", TaskStatusDisplayDirective)
## Task status button directive ## 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. # Display the status of Task and you can edit it.
# #
# Example: # Example:
@ -240,6 +240,26 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
}) })
$el.html(html) $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) -> $el.on "click", ".status-data", (event) ->
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
@ -256,25 +276,7 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
$.fn.popover().closeAll() $.fn.popover().closeAll()
task = $model.$modelValue.clone() save(target.data("status-id"))
task.status = target.data("status-id")
$model.$setViewValue(task)
$scope.$apply()
onSuccess = ->
$confirm.notify("success")
$rootScope.$broadcast("history:reload")
$loading.finish($el.find(".level-name"))
onError = ->
$confirm.notify("error")
task.revert()
$model.$setViewValue(task)
$loading.finish($el.find(".level-name"))
$loading.start($el.find(".level-name"))
$repo.save($model.$modelValue).then(onSuccess, onError)
$scope.$watch $attrs.ngModel, (task) -> $scope.$watch $attrs.ngModel, (task) ->
render(task) if task render(task) if task
@ -288,11 +290,11 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
require: "ngModel" require: "ngModel"
} }
module.directive("tgTaskStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", module.directive("tgTaskStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue",
TaskStatusButtonDirective]) TaskStatusButtonDirective])
TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) -> TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue) ->
template = _.template(""" 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!"> <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" <label for="is-iocaine"
@ -319,15 +321,15 @@ TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
html = template(ctx) html = template(ctx)
$el.html(html) $el.html(html)
$el.on "click", ".is-iocaine", (event) -> save = $qqueue.bindAdd (is_iocaine) =>
return if not isEditable()
task = $model.$modelValue.clone() task = $model.$modelValue.clone()
task.is_iocaine = not task.is_iocaine task.is_iocaine = is_iocaine
$model.$setViewValue(task) $model.$setViewValue(task)
$loading.start($el.find('label')) $loading.start($el.find('label'))
promise = $tgrepo.save($model.$modelValue) promise = $tgrepo.save(task)
promise.then -> promise.then ->
$confirm.notify("success") $confirm.notify("success")
$rootscope.$broadcast("history:reload") $rootscope.$broadcast("history:reload")
@ -340,6 +342,12 @@ TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
promise.finally -> promise.finally ->
$loading.finish($el.find('label')) $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) -> $scope.$watch $attrs.ngModel, (task) ->
render(task) if task render(task) if task
@ -352,4 +360,4 @@ TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
require: "ngModel" 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 taiga = @.taiga
mixOf = @.taiga.mixOf mixOf = @.taiga.mixOf
debounce = @.taiga.debounce
module = angular.module("taigaUserSettings") module = angular.module("taigaUserSettings")
@ -66,18 +67,6 @@ class UserChangePasswordController extends mixOf(taiga.Controller, taiga.PageMix
return promise.then(=> @.loadProject()) 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) module.controller("UserChangePasswordController", UserChangePasswordController)
@ -85,11 +74,36 @@ module.controller("UserChangePasswordController", UserChangePasswordController)
## User ChangePassword Directive ## User ChangePassword Directive
############################################################################# #############################################################################
UserChangePasswordDirective = () -> UserChangePasswordDirective = ($rs, $confirm, $loading) ->
link = ($scope, $el, $attrs) -> 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", -> $scope.$on "$destroy", ->
$el.off() $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 mixOf = @.taiga.mixOf
sizeFormat = @.taiga.sizeFormat sizeFormat = @.taiga.sizeFormat
module = angular.module("taigaUserSettings") module = angular.module("taigaUserSettings")
debounce = @.taiga.debounce
############################################################################# #############################################################################
## User settings Controller ## User settings Controller
@ -82,7 +82,9 @@ module.controller("UserSettingsController", UserSettingsController)
UserProfileDirective = ($confirm, $auth, $repo) -> UserProfileDirective = ($confirm, $auth, $repo) ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
$el.on "click", ".user-profile form .save-profile", (event) -> submit = debounce 2000, (event) =>
event.preventDefault()
form = $el.find("form").checksley() form = $el.find("form").checksley()
return if not form.validate() return if not form.validate()
@ -103,6 +105,10 @@ UserProfileDirective = ($confirm, $auth, $repo) ->
$repo.save($scope.user).then(onSuccess, onError) $repo.save($scope.user).then(onSuccess, onError)
$el.on "submit", "form", submit
$el.on "click", ".submit-button", submit
$scope.$on "$destroy", -> $scope.$on "$destroy", ->
$el.off() $el.off()

View File

@ -259,7 +259,7 @@ module.directive("tgUsTasksProgressDisplay", UsTasksProgressDisplayDirective)
## User story status button directive ## 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. # Display the status of a US and you can edit it.
# #
# Example: # Example:
@ -300,28 +300,15 @@ UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
}) })
$el.html(html) $el.html(html)
$el.on "click", ".status-data", (event) ->
event.preventDefault()
event.stopPropagation()
return if not isEditable()
$el.find(".pop-status").popover().open() save = $qqueue.bindAdd (status) =>
us = $model.$modelValue.clone()
$el.on "click", ".status", (event) -> us.status = status
event.preventDefault()
event.stopPropagation()
return if not isEditable()
target = angular.element(event.currentTarget)
$.fn.popover().closeAll() $.fn.popover().closeAll()
us = $model.$modelValue.clone()
us.status = target.data("status-id")
$model.$setViewValue(us) $model.$setViewValue(us)
$scope.$apply()
onSuccess = -> onSuccess = ->
$confirm.notify("success") $confirm.notify("success")
$rootScope.$broadcast("history:reload") $rootScope.$broadcast("history:reload")
@ -334,8 +321,26 @@ UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
$loading.finish($el.find(".level-name")) $loading.finish($el.find(".level-name"))
$loading.start($el.find(".level-name")) $loading.start($el.find(".level-name"))
$repo.save($model.$modelValue).then(onSuccess, onError) $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) -> $scope.$watch $attrs.ngModel, (us) ->
render(us) if us render(us) if us
@ -348,7 +353,7 @@ UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading) ->
require: "ngModel" require: "ngModel"
} }
module.directive("tgUsStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", module.directive("tgUsStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading","$tgQqueue",
UsStatusButtonDirective]) UsStatusButtonDirective])
@ -356,7 +361,7 @@ module.directive("tgUsStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$t
## User story team requirements button directive ## User story team requirements button directive
############################################################################# #############################################################################
UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) -> UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue) ->
template = _.template(""" template = _.template("""
<label for="team-requirement" <label for="team-requirement"
class="button button-gray team-requirement <% if(canEdit){ %>editable<% }; %> <% if(isRequired){ %>active<% }; %>"> class="button button-gray team-requirement <% if(canEdit){ %>editable<% }; %> <% if(isRequired){ %>active<% }; %>">
@ -381,24 +386,32 @@ UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
html = template(ctx) html = template(ctx)
$el.html(html) $el.html(html)
$el.on "click", ".team-requirement", (event) -> save = $qqueue.bindAdd (team_requirement) =>
return if not canEdit()
us = $model.$modelValue.clone() us = $model.$modelValue.clone()
us.team_requirement = not us.team_requirement us.team_requirement = team_requirement
$model.$setViewValue(us) $model.$setViewValue(us)
$loading.start($el.find("label")) $loading.start($el.find("label"))
promise = $tgrepo.save($model.$modelValue) promise = $tgrepo.save($model.$modelValue)
promise.then => promise.then =>
$loading.finish($el.find("label")) $loading.finish($el.find("label"))
$rootscope.$broadcast("history:reload") $rootscope.$broadcast("history:reload")
promise.then null, -> promise.then null, ->
$loading.finish($el.find("label")) $loading.finish($el.find("label"))
$confirm.notify("error") $confirm.notify("error")
us.revert() us.revert()
$model.$setViewValue(us) $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) -> $scope.$watch $attrs.ngModel, (us) ->
render(us) if us render(us) if us
@ -411,13 +424,13 @@ UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) ->
require: "ngModel" 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 ## User story client requirements button directive
############################################################################# #############################################################################
UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) -> UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue) ->
template = _.template(""" template = _.template("""
<label for="client-requirement" <label for="client-requirement"
class="button button-gray client-requirement <% if(canEdit){ %>editable<% }; %> <% if(isRequired){ %>active<% }; %>"> class="button button-gray client-requirement <% if(canEdit){ %>editable<% }; %> <% if(isRequired){ %>active<% }; %>">
@ -442,11 +455,10 @@ UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) -
html = template(ctx) html = template(ctx)
$el.html(html) $el.html(html)
$el.on "click", ".client-requirement", (event) -> save = $qqueue.bindAdd (client_requirement) =>
return if not canEdit()
us = $model.$modelValue.clone() us = $model.$modelValue.clone()
us.client_requirement = not us.client_requirement us.client_requirement = client_requirement
$model.$setViewValue(us) $model.$setViewValue(us)
$loading.start($el.find("label")) $loading.start($el.find("label"))
@ -460,6 +472,12 @@ UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) -
us.revert() us.revert()
$model.$setViewValue(us) $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) -> $scope.$watch $attrs.ngModel, (us) ->
render(us) if us render(us) if us
@ -472,5 +490,5 @@ UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading) -
require: "ngModel" require: "ngModel"
} }
module.directive("tgUsClientRequirementButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", module.directive("tgUsClientRequirementButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue",
UsClientRequirementButtonDirective]) UsClientRequirementButtonDirective])

View File

@ -212,17 +212,20 @@ module.directive("tgWikiSummary", ["$log", WikiSummaryDirective])
############################################################################# #############################################################################
EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $location, $navUrls, EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $location, $navUrls,
$analytics) -> $analytics, $qqueue) ->
template = """ template = """
<div class="view-wiki-content"> <div class="view-wiki-content">
<section class="wysiwyg" <section class="wysiwyg" tg-bind-html="wiki.html"></section>
tg-bind-html="wiki.html"></section>
<span class="edit icon icon-edit" title="Edit"></span> <span class="edit icon icon-edit" title="Edit"></span>
</div> </div>
<div class="edit-wiki-content" style="display: none;"> <div class="edit-wiki-content" style="display: none;">
<textarea placeholder="Write your wiki page here" <textarea placeholder="Write your wiki page here"
ng-model="wiki.content" ng-model="wiki.content"
tg-markitup="tg-markitup"></textarea> 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"> <span class="action-container">
<a class="save icon icon-floppy" href="" title="Save" /> <a class="save icon icon-floppy" href="" title="Save" />
<a class="cancel icon icon-delete" href="" title="Cancel" /> <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 $document.selection.createRange().text
return null 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) -> $el.on "mouseup", ".view-wiki-content", (event) ->
# We want to dettect the a inside the div so we use the target and # We want to dettect the a inside the div so we use the target and
# not the currentTarget # not the currentTarget
@ -272,25 +298,7 @@ EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $
switchToEditMode() switchToEditMode()
$el.on "click", ".save", debounce 2000, -> $el.on "click", ".save", debounce 2000, ->
onSuccess = (wikiPage) -> save($scope.wiki)
if not $scope.wiki.id?
$analytics.trackEvent("wikipage", "create", "create wiki page", 1)
$scope.wiki = wikiPage
$model.setModelValue = $scope.wiki
$confirm.notify("success")
switchToReadMode()
onError = ->
$confirm.notify("error")
$loading.start($el.find('.save-container'))
if $scope.wiki.id?
promise = $repo.save($scope.wiki).then(onSuccess, onError)
else
promise = $repo.create("wiki", $scope.wiki).then(onSuccess, onError)
promise.finally ->
$loading.finish($el.find('.save-container'))
$el.on "click", ".cancel", -> $el.on "click", ".cancel", ->
cancelEdition() cancelEdition()
@ -321,5 +329,5 @@ EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $
} }
module.directive("tgEditableWikiContent", ["$window", "$document", "$tgRepo", "$tgConfirm", "$tgLoading", module.directive("tgEditableWikiContent", ["$window", "$document", "$tgRepo", "$tgConfirm", "$tgLoading",
"$tgLocation", "$tgNavUrls", "$tgAnalytics", "$tgLocation", "$tgNavUrls", "$tgAnalytics", "$tgQqueue",
EditableWikiContentDirective]) EditableWikiContentDirective])

View File

@ -23,6 +23,17 @@ nl2br = (str) =>
breakTag = '<br />' breakTag = '<br />'
return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + breakTag + '$2') 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) => bindOnce = (scope, attr, continuation) =>
val = scope.$eval(attr) val = scope.$eval(attr)
if val != undefined if val != undefined
@ -106,9 +117,11 @@ joinStr = (str, coll) ->
debounce = (wait, func) -> debounce = (wait, func) ->
return _.debounce(func, wait, {leading: true, trailing: false}) return _.debounce(func, wait, {leading: true, trailing: false})
debounceLeading = (wait, func) -> debounceLeading = (wait, func) ->
return _.debounce(func, wait, {leading: false, trailing: true}) return _.debounce(func, wait, {leading: false, trailing: true})
startswith = (str1, str2) -> startswith = (str1, str2) ->
return _.str.startsWith(str1, str2) return _.str.startsWith(str1, str2)
@ -130,6 +143,7 @@ sizeFormat = (input, precision=1) ->
taiga = @.taiga taiga = @.taiga
taiga.nl2br = nl2br taiga.nl2br = nl2br
taiga.bindMethods = bindMethods
taiga.bindOnce = bindOnce taiga.bindOnce = bindOnce
taiga.mixOf = mixOf taiga.mixOf = mixOf
taiga.trim = trim 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="&#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="&#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="&#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> </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 title Taiga Your agile, free, and open source project management tool
block content 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'") ng-init="section='admin'; sectionName='Default values'")
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile") sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile")
include views/modules/admin-menu include views/modules/admin-menu

View File

@ -93,5 +93,5 @@ block content
option(value="") Select a videoconference system option(value="") Select a videoconference system
input(type="text", ng-model="project.videoconferences_salt", input(type="text", ng-model="project.videoconferences_salt",
placeholder="If you want you can append a salt code to the name of the chat room") placeholder="If you want you can append a salt code to the name of the chat room")
input(type="submit", class="hidden") button(type="submit", class="hidden")
a.button.button-green(href="") Save 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. p All projects are private during Taiga's beta period.
input(type="submit", class="hidden") button(type="submit", class="hidden")
a.button.button-green(href="") Save a.button.button-green.submit-button(href="", title="Save") Save
a.delete-project(href="", title="Delete this project", ng-click="ctrl.openDeleteLightbox()") Delete this project a.delete-project(href="", title="Delete this project", ng-click="ctrl.openDeleteLightbox()") Delete this project
div.lightbox.lightbox-delete-project(tg-lb-delete-project) div.lightbox.lightbox-delete-project(tg-lb-delete-project)

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 .icon.icon-copy
.help-copy Copy to clipboard: Ctrl+C .help-copy Copy to clipboard: Ctrl+C
input(type="submit", class="hidden") button(type="submit", class="hidden")
a.button.button-green(href="") Save a.button.button-green.submit-button(href="", title="Save") Save
.help a.help-button(href="https://taiga.io/support/github-integration/", target="_blank")
h2 How to use it span.icon.icon-help
span Do you need help? Check out our support page!
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.

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-number(tg-bo-ref="issue.ref")
span.us-name(tg-editable-subject, ng-model="issue", required-perm="modify_issue") 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", a(ng-repeat="us in issue.generated_user_stories",
tg-check-permission="view_us", href="", tg-check-permission="view_us", href="",
tg-bo-title="'#' + us.ref + ' ' + us.subject", tg-bo-title="'#' + us.ref + ' ' + us.subject",

View File

@ -5,7 +5,7 @@ block head
block content block content
div.wrapper(ng-controller="TaskDetailController as ctrl", div.wrapper(ng-controller="TaskDetailController as ctrl",
ng-init="section='backlog'") ng-init="section='backlog-kanban'")
div.main.us-detail div.main.us-detail
div.us-detail-header.header-with-actions div.us-detail-header.header-with-actions
include views/components/mainTitle include views/components/mainTitle

View File

@ -11,7 +11,7 @@ block content
span(tg-bo-bind="project.name", class="project-name-short") span(tg-bo-bind="project.name", class="project-name-short")
span.green(tg-bo-bind="sprint.name") span.green(tg-bo-bind="sprint.name")
span.date(tg-date-range="sprint.estimated_start,sprint.estimated_finish") 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.graphics-container
div.burndown(tg-sprint-graph) div.burndown(tg-sprint-graph)

View File

@ -5,7 +5,7 @@ block head
block content block content
div.wrapper(ng-controller="UserStoryDetailController as ctrl", div.wrapper(ng-controller="UserStoryDetailController as ctrl",
ng-init="section='backlog'") ng-init="section='backlog-kanban'")
div.main.us-detail div.main.us-detail
div.us-detail-header.header-with-actions div.us-detail-header.header-with-actions
include views/components/mainTitle include views/components/mainTitle

View File

@ -25,5 +25,5 @@ block content
label(for="retype-password") Retype Password label(for="retype-password") Retype Password
input(type="password", placeholder="Retype Password", id="retype-password", ng-model="newPassword2") input(type="password", placeholder="Retype Password", id="retype-password", ng-model="newPassword2")
fieldset fieldset
input(type="submit", class="hidden") button(type="submit", class="hidden")
a.button.button-green(href="", ng-click="ctrl.save()") Save a.button.button-green.submit-button(href="", title="Save") Save

View File

@ -55,8 +55,8 @@ block content
ng-model="user.bio") ng-model="user.bio")
fieldset.submit fieldset.submit
input(type="submit", class="hidden") button(type="submit", title="Save", class="hidden")
a.button.button-green.save-profile(href="") Save a.button.button-green.save-profile.submit-button(href="") Save
a.delete-account(href="", title="Delete Taiga account", a.delete-account(href="", title="Delete Taiga account",
ng-click="ctrl.openDeleteLightbox()") Delete Taiga account ng-click="ctrl.openDeleteLightbox()") Delete Taiga account

View File

@ -1,7 +1,6 @@
div.taskboard-tagline(tg-colorize-tags="task.tags", tg-colorize-tags-type="taskboard") div.taskboard-tagline(tg-colorize-tags="task.tags", tg-colorize-tags-type="taskboard")
div.taskboard-task-inner div.taskboard-task-inner
div.taskboard-user-avatar(tg-taskboard-user-avatar="task.assigned_to", ng-model="task", div.taskboard-user-avatar(tg-taskboard-user-avatar, users="usersById", task="task", project="project", ng-class="{iocaine: task.is_iocaine}")
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!") 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 p.taskboard-text
a.task-assigned(href="", title="Assign task") 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") a(href="", tg-nav="project-admin-third-parties-github:project=project.slug")
span.title Github span.title Github
span.icon.icon-arrow-right 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") ng-options="s.id as s.name for s in issueStatusList")
fieldset fieldset
input(type="submit", class="hidden") button(type="submit", class="hidden")
a.button.button-green(href="", title="Save") Save 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 /> strong Cancel your account <br />
span We're sorry you are leaving the taiga, we hope you enjoyed your stay :) span We're sorry you are leaving the taiga, we hope you enjoyed your stay :)
form(ng-submit="ctrl.submit()") form
fieldset fieldset
input(type="hidden", name="cancel_token", ng-model="data.cancel_token", data-required="true", input(type="hidden", name="cancel_token", ng-model="data.cancel_token", data-required="true",
placeholder="cancel account token") placeholder="cancel account token")
a.button.button-cancel-account.button-gray(href="", title="Yes, I'm leaving") Yes, I'm leaving! 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 /> strong Change your email <br />
span One click more and your email will be updated! span One click more and your email will be updated!
form(ng-submit="ctrl.submit()") form
fieldset fieldset
input(type="hidden", name="email_token", ng-model="data.email_token", data-required="true", input(type="hidden", name="email_token", ng-model="data.email_token", data-required="true",
placeholder="change email token") placeholder="change email token")
a.button.button-change-email.button-gray(href="", title="Change email") Change email 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 /> 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 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") fieldset.token-change-password(ng-hide="tokenInParams")
input(type="text", name="token", ng-model="data.token", data-required="true", input(type="text", name="token", ng-model="data.token", data-required="true",
placeholder="Recover password token") 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", input(type="password", name="password2", id="password2", ng-model="data.password2",
data-required="true", data-equalto="#password", placeholder="Re-type new password") data-required="true", data-equalto="#password", placeholder="Re-type new password")
fieldset fieldset
a.button.button-change-password.button-gray(href="", title="Reset Password") Reset Password a.button.button-change-password.button-gray.submit-button(href="", title="Reset Password") Reset Password
input(type="submit", style="display:none") 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", input(type="text", name="username", ng-model="data.username", data-required="true",
placeholder="Username or email") placeholder="Username or email")
fieldset fieldset
a.button.button-forgot.button-gray(href="", title="Reset Password") Reset Password button(type="submit", class="hidden")
input(type="submit", style="display:none") 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. 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") placeholder="Password")
a.forgot-pass(href="", tg-nav="forgot-password", title="Did you forgot your password?") Forgot it? a.forgot-pass(href="", tg-nav="forgot-password", title="Did you forgot your password?") Forgot it?
fieldset fieldset
a.button.button-login.button-gray(href="", title="Log in") Enter a.button.button-login.button-gray.submit-button(href="", title="Log in") Enter
input(type="submit", style="display:none") button(type="submit", class="hidden")
fieldset(tg-github-login-button) fieldset(tg-github-login-button)

View File

@ -20,7 +20,7 @@ form.register-form
placeholder="Set a password") placeholder="Set a password")
fieldset fieldset
a.button.button-register.button-gray(href="", title="Sign up") Sign up button(type="submit", class="hidden")
input(type="submit", style="display:none") a.button.button-register.button-gray.submit-button(href="", title="Sign up") Sign up
tg-terms-notice tg-terms-notice

View File

@ -6,7 +6,9 @@ form
//- Form is set in a directive //- Form is set in a directive
.add-member-forms .add-member-forms
a.button.button-green(href="", title="Save")
button(type="submit", class="hidden")
a.button.button-green.submit-button(href="", title="Create")
span Create span Create
p.help-text If users are already registered on Taiga, they will be added automatically. Otherwise they will receive an invitation. 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") textarea.description(placeholder="Description", ng-model="issue.description")
// include lightbox-attachments // include lightbox-attachments
input(type="submit", style="display:none") button(type="submit", class="hidden")
a.button.button-green(href="", title="Save") a.button.button-green.submit-button(href="", title="Save")
span Create span Create

View File

@ -6,6 +6,6 @@ form
textarea(ng-model="feedback.comment", data-required="true", textarea(ng-model="feedback.comment", data-required="true",
placeholder="...a bug, some suggestions, something cool... or even your worst nightmare with Taiga") placeholder="...a bug, some suggestions, something cool... or even your worst nightmare with Taiga")
fieldset fieldset
input.hidden(type="submit") button(type="submit", class="hidden")
a.button.button-green(href="", title="Send feedback") a.button.button-green.submit-button(href="", title="Send feedback")
span Send feedback span Send feedback

View File

@ -4,5 +4,6 @@ form
h2.title(tr="common.new-bulk") h2.title(tr="common.new-bulk")
fieldset 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") 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 fieldset
input(type="text", name="text", id="search-text", placeholder="What are you looking for?", data-required="true") input(type="text", name="text", id="search-text", placeholder="What are you looking for?", data-required="true")
fieldset fieldset
input.hidden(type="submit") button(type="submit", class="hidden")
a.button.button-green(href="", title="Accept") a.button.button-green.submit-button(href="", title="Search") Search
span Search

View File

@ -15,7 +15,8 @@ form
input.date-end(type="text", name="estimated_finish", placeholder="Estimated End", input.date-end(type="text", name="estimated_finish", placeholder="Estimated End",
ng-model="sprint.estimated_finish", data-required="true", tg-date-selector) 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 span Create
div(tg-check-permission="delete_milestone") div(tg-check-permission="delete_milestone")

View File

@ -4,5 +4,6 @@ form
h2.title(tr="common.new-bulk") h2.title(tr="common.new-bulk")
fieldset fieldset
textarea(cols="200", wrap="off", tg-limit-line-length, tr="placeholder:common.one-item-line", ng-model="form.data", data-required="true") 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") 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 span Create

View File

@ -4,5 +4,6 @@ form
h2.title(tr="common.new-bulk") h2.title(tr="common.new-bulk")
fieldset 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") 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") tg-blocking-message-input(watch="us.is_blocked", ng-model="us.blocked_note")
a.button.button-green(href="", title="Save") button(type="submit", class="hidden")
span Create 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? a.forgot-pass(href="", tg-nav="forgot-password", title="Did you forgot your password?") Forgot it?
fieldset fieldset
a.button.button-login.button-gray(href="", title="Sign in") Sign in button(type="submit", class="hidden")
input(type="submit", style="display:none") a.button.button-login.button-gray.submit-button(href="", title="Sign in") Sign in
fieldset(tg-github-login-button) fieldset(tg-github-login-button)

View File

@ -21,8 +21,8 @@ div.register-form-container(tg-register)
placeholder="Set a password (case sensitive)") placeholder="Set a password (case sensitive)")
fieldset fieldset
a.button.button-register.button-gray(href="", title="Sign up") Sign up button(type="submit", class="hidden")
input(type="submit", class="hidden") a.button.button-register.button-gray.submit-button(href="", title="Sign up") Sign up
fieldset(tg-github-login-button) 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-header
div.taskboard-table-inner(tg-taskboard-row-width-fixer) div.taskboard-table-inner
h2.task-colum-name "User story" h2.task-colum-name "User story"
h2.task-colum-name(ng-repeat="s in taskStatusList track by s.id", 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")
ng-style="{'border-top-color':s.color}")
span(tg-bo-bind="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-body(tg-taskboard-table-height-fixer)
div.taskboard-table-inner(tg-taskboard-row-width-fixer) div.taskboard-table-inner
div.task-row(ng-repeat="us in userstories track by us.id", ng-class="{blocked: us.is_blocked}") 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") 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 h3.us-title
a(href="", tg-nav="project-userstories-detail:project=project.slug,ref=us.ref", a(href="", tg-nav="project-userstories-detail:project=project.slug,ref=us.ref",
tg-bo-title="'#' + us.ref + ' ' + us.subject") tg-bo-title="'#' + us.ref + ' ' + us.subject")
@ -18,22 +21,20 @@ div.taskboard-table
p.points-value p.points-value
span(ng-bind="us.total_points") span(ng-bind="us.total_points")
span points span points
include ../components/addnewtask.jade 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-tasks-box.task-column(ng-repeat="st in taskStatusList track by st.id",
tg-taskboard-sortable)
div.taskboard-task(ng-repeat="task in usTasks[us.id][st.id] track by task.id", div.taskboard-task(ng-repeat="task in usTasks[us.id][st.id] track by task.id",
tg-taskboard-task) tg-taskboard-task)
include ../components/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 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 h3.us-title
span Unassigned tasks span Unassigned tasks
include ../components/addnewtask.jade include ../components/addnewtask.jade
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-tasks-box.task-column(ng-repeat="st in taskStatusList track by st.id",
tg-taskboard-sortable)
div.taskboard-task(ng-repeat="task in usTasks[null][st.id] track by task.id", div.taskboard-task(ng-repeat="task in usTasks[null][st.id] track by task.id",
tg-taskboard-task) tg-taskboard-task)
include ../components/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 fieldset.wizard-action
div div
a.button-prev.button.button-gray(href="", title="Prev") Prev 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-bar
div.progress-state 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 { .single-filter {
@extend %large; @extend %large;
@include clearfix;
@extend %title; @extend %title;
@include clearfix;
cursor: pointer; cursor: pointer;
display: block; display: block;
height: 32px; height: 32px;

View File

@ -14,8 +14,8 @@
} }
} }
&.ui-sortable-helper { &.ui-sortable-helper {
box-shadow: 1px 1px 15px rgba($black, .4);
@include transition(box-shadow .3s linear); @include transition(box-shadow .3s linear);
box-shadow: 1px 1px 15px rgba($black, .4);
} }
&.blocked { &.blocked {
background: $red; background: $red;
@ -68,8 +68,8 @@
display: block; display: block;
} }
.task-text { .task-text {
@include table-flex-child($flex-grow: 10, $flex-basis: 50px);
@extend %small; @extend %small;
@include table-flex-child($flex-grow: 10, $flex-basis: 50px);
padding: 0 .5rem 0 .8rem; padding: 0 .5rem 0 .8rem;
word-wrap: break-word; word-wrap: break-word;
} }
@ -79,12 +79,11 @@
} }
.task-name { .task-name {
@extend %bold; @extend %bold;
color: $grayer;
} }
.icon-edit, .icon-edit,
.icon-drag-h { .icon-drag-h {
@include transition(opacity .2s linear);
@extend %large; @extend %large;
@include transition(opacity .2s linear);
color: $postit-hover; color: $postit-hover;
opacity: 0; opacity: 0;
position: absolute; 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 { .taskboard-task {
@include transition (all .4s linear);
background: $postit; background: $postit;
border: 1px solid $postit-hover; border: 1px solid $postit-hover;
box-shadow: none; box-shadow: none;
@ -17,8 +16,8 @@
} }
} }
&.ui-sortable-helper { &.ui-sortable-helper {
box-shadow: 1px 1px 15px rgba($black, .4);
@include transition(box-shadow .3s linear); @include transition(box-shadow .3s linear);
box-shadow: 1px 1px 15px rgba($black, .4);
} }
&.ui-sortable-placeholder { &.ui-sortable-placeholder {
background: $grayer; background: $grayer;
@ -45,7 +44,6 @@
} }
.taskboard-task-inner { .taskboard-task-inner {
@include table-flex(); @include table-flex();
min-height: 7rem;
padding: .5rem; padding: .5rem;
} }
.taskboard-user-avatar { .taskboard-user-avatar {
@ -100,7 +98,6 @@
} }
.task-name { .task-name {
@extend %bold; @extend %bold;
color: $grayer;
} }
.taskboard-text { .taskboard-text {
@extend %small; @extend %small;

View File

@ -35,8 +35,8 @@
} }
} }
.watcher-name { .watcher-name {
@include table-flex-child(8, 0);
@extend %small; @extend %small;
@include table-flex-child(8, 0);
color: $grayer; color: $grayer;
margin-left: 1rem; margin-left: 1rem;
position: relative; position: relative;

View File

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

View File

@ -1,9 +1,9 @@
// Bourbon // Bourbon
$prefix-for-webkit: true; $prefix-for-webkit: true;
$prefix-for-mozilla: true; $prefix-for-mozilla: true;
$prefix-for-microsoft: true; //$prefix-for-microsoft: true;
$prefix-for-opera: true; //$prefix-for-opera: true;
$prefix-for-spec: true; //$prefix-for-spec: true;
@import '../bourbon/bourbon'; @import '../bourbon/bourbon';
//################################################# //#################################################

View File

@ -1,6 +1,5 @@
// Basic layout styles // Basic layout styles
html { html {
height: 100%;
min-height: 100%; min-height: 100%;
width: 100%; width: 100%;
} }
@ -9,9 +8,7 @@ body {
background: #fff; // fallback background: #fff; // fallback
color: #444; color: #444;
-webkit-font-smoothing: antialiased; // Fix for webkit renderin -webkit-font-smoothing: antialiased; // Fix for webkit renderin
height: 100%;
min-height: 100%; min-height: 100%;
overflow-x: hidden; // open-projects-nav
-ms-text-size-adjust: 100%; -ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
width: 100%; width: 100%;
@ -90,7 +87,7 @@ body {
.wrapper { .wrapper {
@include table-flex(); @include table-flex();
min-height: 100%; min-height: 100vh;
padding-left: 90px; padding-left: 90px;
} }

View File

@ -1,6 +1,6 @@
.invitation-main { .invitation-main {
@include table-flex(center, center, flex, row, wrap, center);
@extend %background-taiga; @extend %background-taiga;
@include table-flex(center, center, flex, row, wrap, center);
bottom: 0; bottom: 0;
left: 0; left: 0;
position: fixed; position: fixed;
@ -62,8 +62,8 @@
} }
} }
.forgot-pass { .forgot-pass {
@include transition(all .3s linear);
@extend %small; @extend %small;
@include transition(all .3s linear);
color: $gray-light; color: $gray-light;
opacity: 1; opacity: 1;
position: absolute; position: absolute;

View File

@ -1,10 +1,10 @@
.login-main { .login-main {
@extend %triangled-bg;
//@include table-flex(center, center, flex, row, wrap, center); //@include table-flex(center, center, flex, row, wrap, center);
@include display(flex); @include display(flex);
@include align-items(center); @include align-items(center);
@include flex-direction(row); @include flex-direction(row);
@include justify-content(center); @include justify-content(center);
@extend %triangled-bg;
bottom: 0; bottom: 0;
left: 0; left: 0;
position: fixed; position: fixed;

View File

@ -1,9 +1,9 @@
.error-main { .error-main {
@extend %background-taiga;
@include display(flex); @include display(flex);
@include align-items(center); @include align-items(center);
@include flex-direction(row); @include flex-direction(row);
@include justify-content(center); @include justify-content(center);
@extend %background-taiga;
bottom: 0; bottom: 0;
left: 0; left: 0;
position: fixed; 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