Merge branch 'master' into stable
commit
9a53551b23
|
@ -0,0 +1,10 @@
|
||||||
|
<a name="1.0.0"></a>
|
||||||
|
# 1.0.0 taiga-front (2014-10-07)
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
- Lots of small and not so small bugfixes
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- Redesign for taskboard and backlog summaries
|
||||||
|
- Allow feedback for users from the platform
|
||||||
|
- Real time changes for backlog, taskboard, kanban and issues
|
34
README.md
34
README.md
|
@ -1,10 +1,8 @@
|
||||||
Taiga Front
|
# Taiga Front #
|
||||||
===============
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Setup initial environment
|
## Setup initial environment ##
|
||||||
-------------------------
|
|
||||||
|
|
||||||
Install requirements:
|
Install requirements:
|
||||||
|
|
||||||
|
@ -13,21 +11,29 @@ Install requirements:
|
||||||
You can install Ruby through the apt package manager, rbenv, or rvm.
|
You can install Ruby through the apt package manager, rbenv, or rvm.
|
||||||
Install Sass through your **Terminal or Command Prompt**.
|
Install Sass through your **Terminal or Command Prompt**.
|
||||||
|
|
||||||
```bash
|
```
|
||||||
gem install sass
|
$ gem install sass scss-lint
|
||||||
sass -v // should return Sass 3.3.8 (Maptastic Maple)
|
$ export PATH="~/.gem/ruby/2.1.0/bin:$PATH"
|
||||||
|
$ sass -v // should return Sass 3.3.8 (Maptastic Maple)
|
||||||
```
|
```
|
||||||
|
|
||||||
> Complete process for all OS at: http://sass-lang.com/install
|
Complete process for all OS at: http://sass-lang.com/install
|
||||||
|
|
||||||
**Node + Bower + Gulp**
|
**Node + Bower + Gulp**
|
||||||
|
|
||||||
```bash
|
```
|
||||||
sudo npm install -g gulp
|
$ sudo npm install -g gulp
|
||||||
npm install
|
$ sudo npm install -g bower
|
||||||
sudo npm install -g bower
|
$ npm install
|
||||||
bower install
|
$ bower install
|
||||||
gulp
|
$ gulp
|
||||||
```
|
```
|
||||||
|
|
||||||
And go in your browser to: http://localhost:9001/
|
And go in your browser to: http://localhost:9001/
|
||||||
|
|
||||||
|
|
||||||
|
## Community ##
|
||||||
|
|
||||||
|
[Taiga has a mailing list](http://groups.google.com/d/forum/taigaio). Feel free to join it and ask any questions you may have.
|
||||||
|
|
||||||
|
To subscribe for announcements of releases, important changes and so on, please follow [@taigaio](https://twitter.com/taigaio) on Twitter.
|
||||||
|
|
|
@ -21,7 +21,21 @@
|
||||||
|
|
||||||
@taiga = taiga = {}
|
@taiga = taiga = {}
|
||||||
|
|
||||||
configure = ($routeProvider, $locationProvider, $httpProvider, $provide, tgLoaderProvider) ->
|
# Generic function for generate hash from a arbitrary length
|
||||||
|
# collection of parameters.
|
||||||
|
taiga.generateHash = (components=[]) ->
|
||||||
|
components = _.map(components, (x) -> JSON.stringify(x))
|
||||||
|
return hex_sha1(components.join(":"))
|
||||||
|
|
||||||
|
taiga.generateUniqueSessionIdentifier = ->
|
||||||
|
date = (new Date()).getTime()
|
||||||
|
randomNumber = Math.floor(Math.random() * 0x9000000)
|
||||||
|
return taiga.generateHash([date, randomNumber])
|
||||||
|
|
||||||
|
taiga.sessionId = taiga.generateUniqueSessionIdentifier()
|
||||||
|
|
||||||
|
|
||||||
|
configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEventsProvider, tgLoaderProvider) ->
|
||||||
$routeProvider.when("/",
|
$routeProvider.when("/",
|
||||||
{templateUrl: "/partials/projects.html", resolve: {loader: tgLoaderProvider.add()}})
|
{templateUrl: "/partials/projects.html", resolve: {loader: tgLoaderProvider.add()}})
|
||||||
$routeProvider.when("/project/:pslug/",
|
$routeProvider.when("/project/:pslug/",
|
||||||
|
@ -127,13 +141,18 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, tgLoade
|
||||||
defaultHeaders = {
|
defaultHeaders = {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
"Accept-Language": "en"
|
"Accept-Language": "en"
|
||||||
|
"X-Session-Id": taiga.sessionId
|
||||||
}
|
}
|
||||||
|
|
||||||
$httpProvider.defaults.headers.delete = defaultHeaders
|
$httpProvider.defaults.headers.delete = defaultHeaders
|
||||||
$httpProvider.defaults.headers.patch = defaultHeaders
|
$httpProvider.defaults.headers.patch = defaultHeaders
|
||||||
$httpProvider.defaults.headers.post = defaultHeaders
|
$httpProvider.defaults.headers.post = defaultHeaders
|
||||||
$httpProvider.defaults.headers.put = defaultHeaders
|
$httpProvider.defaults.headers.put = defaultHeaders
|
||||||
$httpProvider.defaults.headers.get = {}
|
$httpProvider.defaults.headers.get = {
|
||||||
|
"X-Session-Id": 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, $confirm, $navUrls, $lightboxService) ->
|
||||||
|
@ -148,7 +167,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, tgLoade
|
||||||
$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", "lightboxService", authHttpIntercept])
|
$provide.factory("authHttpIntercept", ["$q", "$location", "$tgConfirm", "$tgNavUrls",
|
||||||
|
"lightboxService", authHttpIntercept])
|
||||||
$httpProvider.responseInterceptors.push('authHttpIntercept')
|
$httpProvider.responseInterceptors.push('authHttpIntercept')
|
||||||
$httpProvider.interceptors.push('loaderInterceptor')
|
$httpProvider.interceptors.push('loaderInterceptor')
|
||||||
|
|
||||||
|
@ -166,10 +186,13 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, tgLoade
|
||||||
linewidth: "The subject must have a maximum size of %s"
|
linewidth: "The subject must have a maximum size of %s"
|
||||||
})
|
})
|
||||||
|
|
||||||
init = ($log, $i18n, $config, $rootscope) ->
|
init = ($log, $i18n, $config, $rootscope, $auth, $events) ->
|
||||||
$i18n.initialize($config.get("defaultLanguage"))
|
$i18n.initialize($config.get("defaultLanguage"))
|
||||||
$log.debug("Initialize application")
|
$log.debug("Initialize application")
|
||||||
|
|
||||||
|
if $auth.isAuthenticated()
|
||||||
|
$events.setupConnection()
|
||||||
|
|
||||||
# Default Value for taiga local config module.
|
# Default Value for taiga local config module.
|
||||||
angular.module("taigaLocalConfig", []).value("localconfig", {})
|
angular.module("taigaLocalConfig", []).value("localconfig", {})
|
||||||
|
|
||||||
|
@ -181,6 +204,7 @@ modules = [
|
||||||
"taigaResources",
|
"taigaResources",
|
||||||
"taigaLocales",
|
"taigaLocales",
|
||||||
"taigaAuth",
|
"taigaAuth",
|
||||||
|
"taigaEvents",
|
||||||
|
|
||||||
# Specific Modules
|
# Specific Modules
|
||||||
"taigaRelatedTasks",
|
"taigaRelatedTasks",
|
||||||
|
@ -196,6 +220,7 @@ modules = [
|
||||||
"taigaNavMenu",
|
"taigaNavMenu",
|
||||||
"taigaProject",
|
"taigaProject",
|
||||||
"taigaUserSettings",
|
"taigaUserSettings",
|
||||||
|
"taigaFeedback",
|
||||||
"taigaPlugins",
|
"taigaPlugins",
|
||||||
|
|
||||||
# Vendor modules
|
# Vendor modules
|
||||||
|
@ -211,6 +236,7 @@ module.config([
|
||||||
"$locationProvider",
|
"$locationProvider",
|
||||||
"$httpProvider",
|
"$httpProvider",
|
||||||
"$provide",
|
"$provide",
|
||||||
|
"$tgEventsProvider",
|
||||||
"tgLoaderProvider",
|
"tgLoaderProvider",
|
||||||
configure
|
configure
|
||||||
])
|
])
|
||||||
|
@ -220,5 +246,7 @@ module.run([
|
||||||
"$tgI18n",
|
"$tgI18n",
|
||||||
"$tgConfig",
|
"$tgConfig",
|
||||||
"$rootScope",
|
"$rootScope",
|
||||||
|
"$tgAuth",
|
||||||
|
"$tgEvents",
|
||||||
init
|
init
|
||||||
])
|
])
|
||||||
|
|
|
@ -38,6 +38,8 @@ class ConfigService extends taiga.Service
|
||||||
|
|
||||||
termsOfServiceUrl: null
|
termsOfServiceUrl: null
|
||||||
privacyPolicyUrl: null
|
privacyPolicyUrl: null
|
||||||
|
|
||||||
|
feedbackEnabled: true
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize: (localconfig) ->
|
initialize: (localconfig) ->
|
||||||
|
|
|
@ -34,10 +34,10 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, lightboxService) ->
|
||||||
template = _.template("""
|
template = _.template("""
|
||||||
<div class="add-member-wrapper">
|
<div class="add-member-wrapper">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<input type="email" placeholder="Type an Email" data-required="true" data-type="email"/>
|
<input type="email" placeholder="Type an Email" <% if(required) { %> data-required="true" <% } %> data-type="email" />
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<select data-required="true">
|
<select <% if(required) { %> data-required="true" <% } %> data-required="true">
|
||||||
<% _.each(roleList, function(role) { %>
|
<% _.each(roleList, function(role) { %>
|
||||||
<option value="<%- role.id %>"><%- role.name %></option>
|
<option value="<%- role.id %>"><%- role.name %></option>
|
||||||
<% }); %>
|
<% }); %>
|
||||||
|
@ -48,8 +48,8 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, lightboxService) ->
|
||||||
""") # i18n
|
""") # i18n
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
createFieldSet = ->
|
createFieldSet = (required = true)->
|
||||||
ctx = {roleList: $scope.roles}
|
ctx = {roleList: $scope.roles, required: required}
|
||||||
return template(ctx)
|
return template(ctx)
|
||||||
|
|
||||||
resetForm = ->
|
resetForm = ->
|
||||||
|
@ -86,10 +86,11 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, lightboxService) ->
|
||||||
target.removeClass("icon-plus add-fieldset")
|
target.removeClass("icon-plus add-fieldset")
|
||||||
.addClass("icon-delete delete-fieldset")
|
.addClass("icon-delete delete-fieldset")
|
||||||
|
|
||||||
newFieldSet = createFieldSet()
|
newFieldSet = createFieldSet(false)
|
||||||
|
|
||||||
fieldSet.after(newFieldSet)
|
fieldSet.after(newFieldSet)
|
||||||
|
|
||||||
if $el.find("fieldset").length == MAX_MEMBERSHIP_FIELDSETS
|
if $el.find(".add-member-wrapper").length == MAX_MEMBERSHIP_FIELDSETS
|
||||||
$el.find("fieldset:last > a").removeClass("icon-plus add-fieldset")
|
$el.find("fieldset:last > a").removeClass("icon-plus add-fieldset")
|
||||||
.addClass("icon-delete delete-fieldset")
|
.addClass("icon-delete delete-fieldset")
|
||||||
|
|
||||||
|
@ -107,18 +108,30 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, lightboxService) ->
|
||||||
$rootScope.$broadcast("membersform:new:error")
|
$rootScope.$broadcast("membersform:new:error")
|
||||||
|
|
||||||
form = $el.find("form").checksley()
|
form = $el.find("form").checksley()
|
||||||
|
|
||||||
|
#checksley find new fields
|
||||||
|
form.destroy()
|
||||||
|
form.initialize()
|
||||||
|
|
||||||
if not form.validate()
|
if not form.validate()
|
||||||
return
|
return
|
||||||
|
|
||||||
memberWrappers = $el.find("form > .add-member-wrapper")
|
memberWrappers = $el.find("form > .add-member-wrapper")
|
||||||
|
|
||||||
|
memberWrappers = _.filter memberWrappers, (mw) ->
|
||||||
|
angular.element(mw).find("input").hasClass('checksley-ok')
|
||||||
|
|
||||||
invitations = _.map memberWrappers, (mw) ->
|
invitations = _.map memberWrappers, (mw) ->
|
||||||
memberWrapper = angular.element(mw)
|
memberWrapper = angular.element(mw)
|
||||||
|
email = memberWrapper.find("input")
|
||||||
|
role = memberWrapper.find("select")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
email: memberWrapper.find("input").val()
|
email: email.val()
|
||||||
role_id: memberWrapper.find("select").val()
|
role_id: role.val()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if invitations.length
|
||||||
$rs.memberships.bulkCreateMemberships($scope.project.id, invitations).then(onSuccess, onError)
|
$rs.memberships.bulkCreateMemberships($scope.project.id, invitations).then(onSuccess, onError)
|
||||||
|
|
||||||
return {link: link}
|
return {link: link}
|
||||||
|
|
|
@ -241,12 +241,12 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm) ->
|
||||||
categories = []
|
categories = []
|
||||||
|
|
||||||
milestonePermissions = [
|
milestonePermissions = [
|
||||||
{ key: "view_milestones", description: "View milestones" }
|
{ key: "view_milestones", description: "View sprints" }
|
||||||
{ key: "add_milestone", description: "Add milestone" }
|
{ key: "add_milestone", description: "Add sprint" }
|
||||||
{ key: "modify_milestone", description: "Modify milestone" }
|
{ key: "modify_milestone", description: "Modify sprint" }
|
||||||
{ key: "delete_milestone", description: "Delete milestone" }
|
{ key: "delete_milestone", description: "Delete sprint" }
|
||||||
]
|
]
|
||||||
categories.push({ name: "Milestones", permissions: setActivePermissions(milestonePermissions) })
|
categories.push({ name: "Sprints", permissions: setActivePermissions(milestonePermissions) })
|
||||||
|
|
||||||
userStoryPermissions = [
|
userStoryPermissions = [
|
||||||
{ key: "view_us", description: "View user story" }
|
{ key: "view_us", description: "View user story" }
|
||||||
|
|
|
@ -170,7 +170,7 @@ PublicRegisterMessageDirective = ($config, $navUrls) ->
|
||||||
module.directive("tgPublicRegisterMessage", ["$tgConfig", "$tgNavUrls", PublicRegisterMessageDirective])
|
module.directive("tgPublicRegisterMessage", ["$tgConfig", "$tgNavUrls", PublicRegisterMessageDirective])
|
||||||
|
|
||||||
|
|
||||||
LoginDirective = ($auth, $confirm, $location, $routeParams, $navUrls) ->
|
LoginDirective = ($auth, $confirm, $location, $config, $routeParams, $navUrls, $events) ->
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
$scope.data = {}
|
$scope.data = {}
|
||||||
|
|
||||||
|
@ -180,6 +180,7 @@ LoginDirective = ($auth, $confirm, $location, $routeParams, $navUrls) ->
|
||||||
else
|
else
|
||||||
nextUrl = $navUrls.resolve("home")
|
nextUrl = $navUrls.resolve("home")
|
||||||
|
|
||||||
|
$events.setupConnection()
|
||||||
$location.path(nextUrl)
|
$location.path(nextUrl)
|
||||||
|
|
||||||
onErrorSubmit = (response) ->
|
onErrorSubmit = (response) ->
|
||||||
|
@ -203,8 +204,8 @@ LoginDirective = ($auth, $confirm, $location, $routeParams, $navUrls) ->
|
||||||
|
|
||||||
return {link:link}
|
return {link:link}
|
||||||
|
|
||||||
module.directive("tgLogin", ["$tgAuth", "$tgConfirm", "$tgLocation", "$routeParams", "$tgNavUrls",
|
module.directive("tgLogin", ["$tgAuth", "$tgConfirm", "$tgLocation", "$tgConfig", "$routeParams",
|
||||||
LoginDirective])
|
"$tgNavUrls", "$tgEvents", LoginDirective])
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Register Directive
|
## Register Directive
|
||||||
|
@ -220,7 +221,7 @@ RegisterDirective = ($auth, $confirm, $location, $navUrls, $config) ->
|
||||||
form = $el.find("form").checksley()
|
form = $el.find("form").checksley()
|
||||||
|
|
||||||
onSuccessSubmit = (response) ->
|
onSuccessSubmit = (response) ->
|
||||||
$confirm.notify("success", "Our Oompa Loompas are happy, wellcome to Taiga.") #TODO: i18n
|
$confirm.notify("success", "Our Oompa Loompas are happy, welcome to Taiga.") #TODO: i18n
|
||||||
$location.path($navUrls.resolve("home"))
|
$location.path($navUrls.resolve("home"))
|
||||||
|
|
||||||
onErrorSubmit = (response) ->
|
onErrorSubmit = (response) ->
|
||||||
|
@ -307,7 +308,7 @@ ChangePasswordFromRecoveryDirective = ($auth, $confirm, $location, $params, $nav
|
||||||
|
|
||||||
onSuccessSubmit = (response) ->
|
onSuccessSubmit = (response) ->
|
||||||
$location.path($navUrls.resolve("login"))
|
$location.path($navUrls.resolve("login"))
|
||||||
$confirm.success("Our Oompa Loompas save your new password.<br />
|
$confirm.success("Our Oompa Loompas saved your new password.<br />
|
||||||
Try to <strong>sign in</strong> with it.") #TODO: i18n
|
Try to <strong>sign in</strong> with it.") #TODO: i18n
|
||||||
|
|
||||||
onErrorSubmit = (response) ->
|
onErrorSubmit = (response) ->
|
||||||
|
@ -348,8 +349,8 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls) ->
|
||||||
|
|
||||||
promise.then null, (response) ->
|
promise.then null, (response) ->
|
||||||
$location.path($navUrls.resolve("login"))
|
$location.path($navUrls.resolve("login"))
|
||||||
$confirm.success("<strong>Ooops, we have a problems</strong><br />
|
$confirm.success("<strong>Ooops, we have a problem</strong><br />
|
||||||
Our Oompa Loompas can't find your invitations.") #TODO: i18n
|
Our Oompa Loompas can't find your invitation.") #TODO: i18n
|
||||||
|
|
||||||
# Login form
|
# Login form
|
||||||
$scope.dataLogin = {token: token}
|
$scope.dataLogin = {token: token}
|
||||||
|
@ -357,12 +358,12 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls) ->
|
||||||
|
|
||||||
onSuccessSubmitLogin = (response) ->
|
onSuccessSubmitLogin = (response) ->
|
||||||
$location.path($navUrls.resolve("project", {project: $scope.invitation.project_slug}))
|
$location.path($navUrls.resolve("project", {project: $scope.invitation.project_slug}))
|
||||||
$confirm.notify("success", "You've successfully joined to this project",
|
$confirm.notify("success", "You've successfully joined this project",
|
||||||
"Wellcome to #{$scope.invitation.project_name}")
|
"Welcome to #{$scope.invitation.project_name}")
|
||||||
|
|
||||||
onErrorSubmitLogin = (response) ->
|
onErrorSubmitLogin = (response) ->
|
||||||
$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
|
||||||
type an invalid password.") #TODO: i18n
|
typed an invalid password.") #TODO: i18n
|
||||||
|
|
||||||
submitLogin = ->
|
submitLogin = ->
|
||||||
if not loginForm.validate()
|
if not loginForm.validate()
|
||||||
|
@ -385,11 +386,11 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls) ->
|
||||||
|
|
||||||
onSuccessSubmitRegister = (response) ->
|
onSuccessSubmitRegister = (response) ->
|
||||||
$location.path($navUrls.resolve("project", {project: $scope.invitation.project_slug}))
|
$location.path($navUrls.resolve("project", {project: $scope.invitation.project_slug}))
|
||||||
$confirm.notify("success", "You've successfully joined to this project",
|
$confirm.notify("success", "You've successfully joined this project",
|
||||||
"Wellcome to #{$scope.invitation.project_name}")
|
"Welcome to #{$scope.invitation.project_name}")
|
||||||
|
|
||||||
onErrorSubmitRegister = (response) ->
|
onErrorSubmitRegister = (response) ->
|
||||||
$confirm.notify("light-error", "According to our Oompa Loompas, the
|
$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 = ->
|
submitRegister = ->
|
||||||
|
|
|
@ -46,11 +46,12 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
"$tgLocation",
|
"$tgLocation",
|
||||||
"$appTitle",
|
"$appTitle",
|
||||||
"$tgNavUrls",
|
"$tgNavUrls",
|
||||||
|
"$tgEvents",
|
||||||
"tgLoader"
|
"tgLoader"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @appTitle, @navUrls,
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q,
|
||||||
tgLoader) ->
|
@location, @appTitle, @navUrls, @events, tgLoader) ->
|
||||||
_.bindAll(@)
|
_.bindAll(@)
|
||||||
|
|
||||||
@scope.sectionName = "Backlog"
|
@scope.sectionName = "Backlog"
|
||||||
|
@ -90,12 +91,24 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
@scope.$on("sprintform:remove:success", @.loadUserstories)
|
@scope.$on("sprintform:remove:success", @.loadUserstories)
|
||||||
@scope.$on("usform:new:success", @.loadUserstories)
|
@scope.$on("usform:new:success", @.loadUserstories)
|
||||||
@scope.$on("usform:edit:success", @.loadUserstories)
|
@scope.$on("usform:edit:success", @.loadUserstories)
|
||||||
|
@scope.$on("usform:new:success", @.loadProjectStats)
|
||||||
|
@scope.$on("usform:bulk:success", @.loadProjectStats)
|
||||||
@scope.$on("sprint:us:move", @.moveUs)
|
@scope.$on("sprint:us:move", @.moveUs)
|
||||||
@scope.$on("sprint:us:moved", @.loadSprints)
|
@scope.$on("sprint:us:moved", @.loadSprints)
|
||||||
@scope.$on("sprint:us:moved", @.loadProjectStats)
|
@scope.$on("sprint:us:moved", @.loadProjectStats)
|
||||||
|
|
||||||
|
initializeSubscription: ->
|
||||||
|
routingKey1 = "changes.project.#{@scope.projectId}.userstories"
|
||||||
|
@events.subscribe @scope, routingKey1, (message) =>
|
||||||
|
@.loadUserstories()
|
||||||
|
@.loadSprints()
|
||||||
|
|
||||||
|
routingKey2 = "changes.project.#{@scope.projectId}.milestones"
|
||||||
|
@events.subscribe @scope, routingKey2, (message) =>
|
||||||
|
@.loadSprints()
|
||||||
|
|
||||||
toggleShowTags: ->
|
toggleShowTags: ->
|
||||||
@scope.$apply () =>
|
@scope.$apply =>
|
||||||
@showTags = !@showTags
|
@showTags = !@showTags
|
||||||
@rs.userstories.storeShowTags(@scope.projectId, @showTags)
|
@rs.userstories.storeShowTags(@scope.projectId, @showTags)
|
||||||
|
|
||||||
|
@ -186,6 +199,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
# Resolve project slug
|
# Resolve project slug
|
||||||
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
|
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
|
||||||
@scope.projectId = data.project
|
@scope.projectId = data.project
|
||||||
|
@.initializeSubscription()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
return promise.then(=> @.loadProject())
|
return promise.then(=> @.loadProject())
|
||||||
|
@ -956,3 +970,52 @@ tgBacklogGraphDirective = ->
|
||||||
|
|
||||||
|
|
||||||
module.directive("tgGmBacklogGraph", tgBacklogGraphDirective)
|
module.directive("tgGmBacklogGraph", tgBacklogGraphDirective)
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## Backlog progress bar directive
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
TgBacklogProgressBarDirective = ->
|
||||||
|
template = _.template("""
|
||||||
|
<div class="defined-points" title="Excess of points"></div>
|
||||||
|
<div class="project-points-progress" title="Pending Points" style="width: <%- projectPointsPercentaje %>%"></div>
|
||||||
|
<div class="closed-points-progress" title="Closed points" style="width: <%- closedPointsPercentaje %>%"></div>
|
||||||
|
""")
|
||||||
|
|
||||||
|
render = (el, projectPointsPercentaje, closedPointsPercentaje) ->
|
||||||
|
el.html(template({
|
||||||
|
projectPointsPercentaje: projectPointsPercentaje,
|
||||||
|
closedPointsPercentaje:closedPointsPercentaje
|
||||||
|
}))
|
||||||
|
|
||||||
|
adjustPercentaje = (percentage) ->
|
||||||
|
adjusted = _.max([0 , percentage])
|
||||||
|
adjusted = _.min([100, adjusted])
|
||||||
|
return Math.round(adjusted)
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs) ->
|
||||||
|
element = angular.element($el)
|
||||||
|
|
||||||
|
$scope.$watch $attrs.tgBacklogProgressBar, (stats) ->
|
||||||
|
if stats?
|
||||||
|
totalPoints = stats.total_points
|
||||||
|
definedPoints = stats.defined_points
|
||||||
|
closedPoints = stats.closed_points
|
||||||
|
if definedPoints > totalPoints
|
||||||
|
projectPointsPercentaje = totalPoints * 100 / definedPoints
|
||||||
|
closedPointsPercentaje = closedPoints * 100 / definedPoints
|
||||||
|
else
|
||||||
|
projectPointsPercentaje = 100
|
||||||
|
closedPointsPercentaje = closedPoints * 100 / totalPoints
|
||||||
|
|
||||||
|
projectPointsPercentaje = adjustPercentaje(projectPointsPercentaje - 3)
|
||||||
|
closedPointsPercentaje = adjustPercentaje(closedPointsPercentaje - 3)
|
||||||
|
render($el, projectPointsPercentaje, closedPointsPercentaje)
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
return {link: link}
|
||||||
|
|
||||||
|
module.directive("tgBacklogProgressBar", TgBacklogProgressBarDirective)
|
||||||
|
|
|
@ -333,15 +333,19 @@ ListItemPriorityDirective = ->
|
||||||
"""
|
"""
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
issue = $scope.$eval($attrs.tgListitemPriority)
|
render = (priorityById, issue) ->
|
||||||
bindOnce $scope, "priorityById", (priorityById) ->
|
|
||||||
priority = priorityById[issue.priority]
|
priority = priorityById[issue.priority]
|
||||||
|
domNode = $el.find(".level")
|
||||||
domNode = $el.find("div.level")
|
|
||||||
domNode.css("background-color", priority.color)
|
domNode.css("background-color", priority.color)
|
||||||
domNode.addClass(priority.name.toLowerCase())
|
|
||||||
domNode.attr("title", priority.name)
|
domNode.attr("title", priority.name)
|
||||||
|
|
||||||
|
bindOnce $scope, "priorityById", (priorityById) ->
|
||||||
|
issue = $scope.$eval($attrs.tgListitemPriority)
|
||||||
|
render(priorityById, issue)
|
||||||
|
|
||||||
|
$scope.$watch $attrs.tgListitemPriority, (issue) ->
|
||||||
|
render($scope.priorityById, issue)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
link: link
|
link: link
|
||||||
template: template
|
template: template
|
||||||
|
@ -354,15 +358,19 @@ ListItemSeverityDirective = ->
|
||||||
"""
|
"""
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
issue = $scope.$eval($attrs.tgListitemSeverity)
|
render = (severityById, issue) ->
|
||||||
bindOnce $scope, "severityById", (severityById) ->
|
|
||||||
severity = severityById[issue.severity]
|
severity = severityById[issue.severity]
|
||||||
|
domNode = $el.find(".level")
|
||||||
domNode = $el.find("div.level")
|
|
||||||
domNode.css("background-color", severity.color)
|
domNode.css("background-color", severity.color)
|
||||||
domNode.addClass(severity.name.toLowerCase())
|
|
||||||
domNode.attr("title", severity.name)
|
domNode.attr("title", severity.name)
|
||||||
|
|
||||||
|
bindOnce $scope, "severityById", (severityById) ->
|
||||||
|
issue = $scope.$eval($attrs.tgListitemSeverity)
|
||||||
|
render(severityById, issue)
|
||||||
|
|
||||||
|
$scope.$watch $attrs.tgListitemSeverity, (issue) ->
|
||||||
|
render($scope.severityById, issue)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
link: link
|
link: link
|
||||||
template: template
|
template: template
|
||||||
|
@ -374,15 +382,18 @@ ListItemTypeDirective = ->
|
||||||
"""
|
"""
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
issue = $scope.$eval($attrs.tgListitemType)
|
render = (issueTypeById, issue) ->
|
||||||
|
type = issueTypeById[issue.type]
|
||||||
|
domNode = $el.find(".level")
|
||||||
|
domNode.css("background-color", type.color)
|
||||||
|
domNode.attr("title", type.name)
|
||||||
|
|
||||||
bindOnce $scope, "issueTypeById", (issueTypeById) ->
|
bindOnce $scope, "issueTypeById", (issueTypeById) ->
|
||||||
type = issueTypeById[issue.type]
|
issue = $scope.$eval($attrs.tgListitemType)
|
||||||
|
render(issueTypeById, issue)
|
||||||
|
|
||||||
domNode = $el.find("div.level")
|
$scope.$watch $attrs.tgListitemType, (issue) ->
|
||||||
domNode.css("background-color", type.color)
|
render($scope.issueTypeById, issue)
|
||||||
domNode.addClass(type.name.toLowerCase())
|
|
||||||
domNode.attr("title", type.name)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
link: link
|
link: link
|
||||||
|
|
|
@ -269,7 +269,7 @@ HistoryDirective = ($log, $loading) ->
|
||||||
team_requirement: "team requirement"
|
team_requirement: "team requirement"
|
||||||
|
|
||||||
# Task
|
# Task
|
||||||
milestone: "spreint"
|
milestone: "sprint"
|
||||||
user_story: "user story"
|
user_story: "user story"
|
||||||
is_iocaine: "is iocaine"
|
is_iocaine: "is iocaine"
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ debounce = @.taiga.debounce
|
||||||
class LightboxService extends taiga.Service
|
class LightboxService extends taiga.Service
|
||||||
open: ($el) ->
|
open: ($el) ->
|
||||||
$el.css('display', 'flex')
|
$el.css('display', 'flex')
|
||||||
|
$el.find('input,textarea').first().focus()
|
||||||
timeout(70, -> $el.addClass("open"))
|
timeout(70, -> $el.addClass("open"))
|
||||||
|
|
||||||
docEl = angular.element(document)
|
docEl = angular.element(document)
|
||||||
|
@ -45,9 +46,10 @@ class LightboxService extends taiga.Service
|
||||||
docEl.off(".keyboard-navigation") # Hack: to fix problems in the WYSIWYG textareas when press ENTER
|
docEl.off(".keyboard-navigation") # Hack: to fix problems in the WYSIWYG textareas when press ENTER
|
||||||
|
|
||||||
$el.one "transitionend", =>
|
$el.one "transitionend", =>
|
||||||
$el.css('display', 'none')
|
$el.removeAttr('style')
|
||||||
|
$el.removeClass("open").removeClass('close')
|
||||||
|
|
||||||
$el.removeClass("open")
|
$el.addClass('close')
|
||||||
|
|
||||||
closeAll: ->
|
closeAll: ->
|
||||||
docEl = angular.element(document)
|
docEl = angular.element(document)
|
||||||
|
|
|
@ -42,9 +42,9 @@ UsStatusDirective = ($repo, popoverService) ->
|
||||||
|
|
||||||
NOTE: This directive need 'usStatusById' and 'project'.
|
NOTE: This directive need 'usStatusById' and 'project'.
|
||||||
###
|
###
|
||||||
selectionTemplate = _.template("""
|
template = _.template("""
|
||||||
<ul class="popover pop-status">
|
<ul class="popover pop-status">
|
||||||
<% _.forEach(statuses, function(status) { %>
|
<% _.each(statuses, function(status) { %>
|
||||||
<li>
|
<li>
|
||||||
<a href="" class="status" title="<%- status.name %>" data-status-id="<%- status.id %>">
|
<a href="" class="status" title="<%- status.name %>" data-status-id="<%- status.id %>">
|
||||||
<%- status.name %>
|
<%- status.name %>
|
||||||
|
@ -53,51 +53,56 @@ UsStatusDirective = ($repo, popoverService) ->
|
||||||
<% }); %>
|
<% }); %>
|
||||||
</ul>""")
|
</ul>""")
|
||||||
|
|
||||||
updateUsStatus = ($el, us, usStatusById) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
|
$ctrl = $el.controller()
|
||||||
|
|
||||||
|
render = (us) ->
|
||||||
usStatusDomParent = $el.find(".us-status")
|
usStatusDomParent = $el.find(".us-status")
|
||||||
usStatusDom = $el.find(".us-status .us-status-bind")
|
usStatusDom = $el.find(".us-status .us-status-bind")
|
||||||
|
usStatusById = $scope.usStatusById
|
||||||
|
|
||||||
if usStatusById[us.status]
|
if usStatusById[us.status]
|
||||||
usStatusDom.text(usStatusById[us.status].name)
|
usStatusDom.text(usStatusById[us.status].name)
|
||||||
usStatusDomParent.prop("title", usStatusById[us.status].name)
|
usStatusDomParent.css("color", usStatusById[us.status].color)
|
||||||
usStatusDomParent.css('color', usStatusById[us.status].color)
|
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
|
||||||
$ctrl = $el.controller()
|
|
||||||
us = $scope.$eval($attrs.tgUsStatus)
|
|
||||||
|
|
||||||
$el.on "click", ".us-status", (event) ->
|
$el.on "click", ".us-status", (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
$el.find(".pop-status").popover().open()
|
$el.find(".pop-status").popover().open()
|
||||||
|
|
||||||
# pop = $el.find(".pop-status")
|
|
||||||
# popoverService.open(pop)
|
|
||||||
|
|
||||||
$el.on "click", ".status", debounce 2000, (event) ->
|
$el.on "click", ".status", debounce 2000, (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
target = angular.element(event.currentTarget)
|
target = angular.element(event.currentTarget)
|
||||||
|
|
||||||
|
us = $scope.$eval($attrs.tgUsStatus)
|
||||||
us.status = target.data("status-id")
|
us.status = target.data("status-id")
|
||||||
|
render(us)
|
||||||
|
|
||||||
$el.find(".pop-status").popover().close()
|
$el.find(".pop-status").popover().close()
|
||||||
updateUsStatus($el, us, $scope.usStatusById)
|
|
||||||
|
|
||||||
$scope.$apply () ->
|
$scope.$apply () ->
|
||||||
$repo.save(us).then ->
|
$repo.save(us).then ->
|
||||||
$scope.$eval($attrs.onUpdate)
|
$scope.$eval($attrs.onUpdate)
|
||||||
|
|
||||||
taiga.bindOnce $scope, "project", (project) ->
|
|
||||||
$el.append(selectionTemplate({ 'statuses': project.us_statuses }))
|
$scope.$on("userstories:loaded", -> render($scope.$eval($attrs.tgUsStatus)))
|
||||||
updateUsStatus($el, us, $scope.usStatusById)
|
$scope.$on("$destroy", -> $el.off())
|
||||||
|
|
||||||
|
# Bootstrap
|
||||||
|
us = $scope.$eval($attrs.tgUsStatus)
|
||||||
|
render(us)
|
||||||
|
|
||||||
|
bindOnce $scope, "project", (project) ->
|
||||||
|
html = template({"statuses": project.us_statuses})
|
||||||
|
$el.append(html)
|
||||||
|
|
||||||
# If the user has not enough permissions the click events are unbinded
|
# If the user has not enough permissions the click events are unbinded
|
||||||
if project.my_permissions.indexOf("modify_us") == -1
|
if $scope.project.my_permissions.indexOf("modify_us") == -1
|
||||||
$el.unbind("click")
|
$el.unbind("click")
|
||||||
$el.find("a").addClass("not-clickable")
|
$el.find("a").addClass("not-clickable")
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
|
||||||
$el.off()
|
|
||||||
|
|
||||||
return {link: link}
|
return {link: link}
|
||||||
|
|
||||||
|
|
|
@ -96,6 +96,7 @@ TagLineDirective = ($log, $rs) ->
|
||||||
template = """
|
template = """
|
||||||
<div class="tags-container"></div>
|
<div class="tags-container"></div>
|
||||||
<input type="text" placeholder="Write tag..." class="tag-input" />
|
<input type="text" placeholder="Write tag..." class="tag-input" />
|
||||||
|
<a href="" title="Save" class="save icon icon-floppy"></a>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Tags template (rendered manually using lodash)
|
# Tags template (rendered manually using lodash)
|
||||||
|
@ -137,6 +138,14 @@ TagLineDirective = ($log, $rs) ->
|
||||||
$scope.$apply ->
|
$scope.$apply ->
|
||||||
$model.$setViewValue(normalizeTags(tags))
|
$model.$setViewValue(normalizeTags(tags))
|
||||||
|
|
||||||
|
saveInputTag = () ->
|
||||||
|
input = $el.find('input')
|
||||||
|
|
||||||
|
addValue(input.val())
|
||||||
|
input.val("")
|
||||||
|
input.autocomplete("close")
|
||||||
|
$el.find('.save').hide()
|
||||||
|
|
||||||
$scope.$watch $attrs.ngModel, (val) ->
|
$scope.$watch $attrs.ngModel, (val) ->
|
||||||
tags_colors = if $scope.project?.tags_colors? then $scope.project.tags_colors else []
|
tags_colors = if $scope.project?.tags_colors? then $scope.project.tags_colors else []
|
||||||
renderTags($el, val, editable, tags_colors)
|
renderTags($el, val, editable, tags_colors)
|
||||||
|
@ -171,13 +180,16 @@ TagLineDirective = ($log, $rs) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
$el.on "keyup", "input", (event) ->
|
$el.on "keyup", "input", (event) ->
|
||||||
return if event.keyCode != 13
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
target = angular.element(event.currentTarget)
|
target = angular.element(event.currentTarget)
|
||||||
addValue(target.val())
|
|
||||||
target.val("")
|
if event.keyCode == 13
|
||||||
$el.find("input").autocomplete("close")
|
saveInputTag()
|
||||||
|
else if target.val().length
|
||||||
|
$el.find('.save').show()
|
||||||
|
else
|
||||||
|
$el.find('.save').hide()
|
||||||
|
|
||||||
|
$el.on "click", ".save", saveInputTag
|
||||||
|
|
||||||
$el.on "click", ".icon-delete", (event) ->
|
$el.on "click", ".icon-delete", (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
###
|
||||||
|
# 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/events.coffee
|
||||||
|
###
|
||||||
|
|
||||||
|
taiga = @.taiga
|
||||||
|
|
||||||
|
module = angular.module("taigaEvents", [])
|
||||||
|
|
||||||
|
|
||||||
|
class EventsService
|
||||||
|
constructor: (@win, @log, @config, @auth) ->
|
||||||
|
_.bindAll(@)
|
||||||
|
|
||||||
|
initialize: (sessionId) ->
|
||||||
|
@.sessionId = sessionId
|
||||||
|
@.subscriptions = {}
|
||||||
|
@.connected = false
|
||||||
|
@.error = false
|
||||||
|
@.pendingMessages = []
|
||||||
|
|
||||||
|
if @win.WebSocket is undefined
|
||||||
|
@log.info "WebSockets not supported on your browser"
|
||||||
|
|
||||||
|
setupConnection: ->
|
||||||
|
@.stopExistingConnection()
|
||||||
|
|
||||||
|
url = @config.get("eventsUrl", "ws://localhost:8888/events")
|
||||||
|
|
||||||
|
@.ws = new @win.WebSocket(url)
|
||||||
|
@.ws.addEventListener("open", @.onOpen)
|
||||||
|
@.ws.addEventListener("message", @.onMessage)
|
||||||
|
@.ws.addEventListener("error", @.onError)
|
||||||
|
@.ws.addEventListener("close", @.onClose)
|
||||||
|
|
||||||
|
stopExistingConnection: ->
|
||||||
|
if @.ws is undefined
|
||||||
|
return
|
||||||
|
|
||||||
|
@.ws.removeEventListener("open", @.onOpen)
|
||||||
|
@.ws.removeEventListener("close", @.onClose)
|
||||||
|
@.ws.removeEventListener("error", @.onError)
|
||||||
|
@.ws.removeEventListener("message", @.onMessage)
|
||||||
|
@.ws.close()
|
||||||
|
|
||||||
|
delete @.ws
|
||||||
|
|
||||||
|
serialize: (message) ->
|
||||||
|
if _.isObject(message)
|
||||||
|
return JSON.stringify(message)
|
||||||
|
return message
|
||||||
|
|
||||||
|
sendMessage: (message) ->
|
||||||
|
@.pendingMessages.push(message)
|
||||||
|
|
||||||
|
if not @.connected
|
||||||
|
return
|
||||||
|
|
||||||
|
messages = _.map(@.pendingMessages, @.serialize)
|
||||||
|
@.pendingMessages = []
|
||||||
|
|
||||||
|
for msg in messages
|
||||||
|
@.ws.send(msg)
|
||||||
|
|
||||||
|
subscribe: (scope, routingKey, callback) ->
|
||||||
|
if @.error
|
||||||
|
return
|
||||||
|
|
||||||
|
@log.debug("Subscribe to: #{routingKey}")
|
||||||
|
subscription = {
|
||||||
|
scope: scope,
|
||||||
|
routingKey: routingKey,
|
||||||
|
callback: _.debounce(callback, 500, {"leading": true, "trailing": false})
|
||||||
|
}
|
||||||
|
|
||||||
|
message = {
|
||||||
|
"cmd": "subscribe",
|
||||||
|
"routing_key": routingKey
|
||||||
|
}
|
||||||
|
|
||||||
|
@.subscriptions[routingKey] = subscription
|
||||||
|
@.sendMessage(message)
|
||||||
|
scope.$on("$destroy", => @.unsubscribe(routingKey))
|
||||||
|
|
||||||
|
unsubscribe: (routingKey) ->
|
||||||
|
if @.error
|
||||||
|
return
|
||||||
|
|
||||||
|
@log.debug("Unsubscribe from: #{routingKey}")
|
||||||
|
|
||||||
|
message = {
|
||||||
|
"cmd": "unsubscribe",
|
||||||
|
"routing_key": routingKey
|
||||||
|
}
|
||||||
|
|
||||||
|
@.sendMessage(message)
|
||||||
|
|
||||||
|
onOpen: ->
|
||||||
|
@.connected = true
|
||||||
|
|
||||||
|
@log.debug("WebSocket connection opened")
|
||||||
|
token = @auth.getToken()
|
||||||
|
|
||||||
|
message = {
|
||||||
|
cmd: "auth"
|
||||||
|
data: {token: token, sessionId: @.sessionId}
|
||||||
|
}
|
||||||
|
|
||||||
|
@.sendMessage(message)
|
||||||
|
|
||||||
|
onMessage: (event) ->
|
||||||
|
@.log.debug "WebSocket message received: #{event.data}"
|
||||||
|
|
||||||
|
data = JSON.parse(event.data)
|
||||||
|
routingKey = data.routing_key
|
||||||
|
|
||||||
|
if not @.subscriptions[routingKey]?
|
||||||
|
return
|
||||||
|
|
||||||
|
subscription = @.subscriptions[routingKey]
|
||||||
|
subscription.scope.$apply ->
|
||||||
|
subscription.callback(data.data)
|
||||||
|
|
||||||
|
onError: (error) ->
|
||||||
|
@log.error("WebSocket error: #{error}")
|
||||||
|
@.error = true
|
||||||
|
|
||||||
|
onClose: ->
|
||||||
|
@log.debug("WebSocket closed.")
|
||||||
|
@.connected = false
|
||||||
|
|
||||||
|
|
||||||
|
class EventsProvider
|
||||||
|
setSessionId: (sessionId) ->
|
||||||
|
@.sessionId = sessionId
|
||||||
|
|
||||||
|
$get: ($win, $log, $conf, $auth) ->
|
||||||
|
service = new EventsService($win, $log, $conf, $auth)
|
||||||
|
service.initialize(@.sessionId)
|
||||||
|
return service
|
||||||
|
|
||||||
|
@.prototype.$get.$inject = ["$window", "$log", "$tgConfig", "$tgAuth"]
|
||||||
|
|
||||||
|
module.provider("$tgEvents", EventsProvider)
|
|
@ -0,0 +1,68 @@
|
||||||
|
###
|
||||||
|
# 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/feedback.coffee
|
||||||
|
###
|
||||||
|
|
||||||
|
taiga = @.taiga
|
||||||
|
|
||||||
|
groupBy = @.taiga.groupBy
|
||||||
|
bindOnce = @.taiga.bindOnce
|
||||||
|
mixOf = @.taiga.mixOf
|
||||||
|
debounce = @.taiga.debounce
|
||||||
|
trim = @.taiga.trim
|
||||||
|
|
||||||
|
module = angular.module("taigaFeedback", [])
|
||||||
|
|
||||||
|
FeedbackDirective = ($lightboxService, $repo, $confirm)->
|
||||||
|
link = ($scope, $el, $attrs) ->
|
||||||
|
form = $el.find("form").checksley()
|
||||||
|
|
||||||
|
submit = debounce 2000, ->
|
||||||
|
if not form.validate()
|
||||||
|
return
|
||||||
|
|
||||||
|
promise = $repo.create("feedback", $scope.feedback)
|
||||||
|
|
||||||
|
promise.then (data) ->
|
||||||
|
$lightboxService.close($el)
|
||||||
|
$confirm.notify("success", "\\o/ we'll be happy to read your")
|
||||||
|
|
||||||
|
promise.then null, ->
|
||||||
|
$confirm.notify("error")
|
||||||
|
|
||||||
|
$el.on "submit", (event) ->
|
||||||
|
submit()
|
||||||
|
|
||||||
|
$el.on "click", ".button-green", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
submit()
|
||||||
|
|
||||||
|
$scope.$on "feedback:show", ->
|
||||||
|
$scope.$apply ->
|
||||||
|
$scope.feedback = {}
|
||||||
|
|
||||||
|
$lightboxService.open($el)
|
||||||
|
$el.find("textarea").focus()
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
|
return {link:link}
|
||||||
|
|
||||||
|
module.directive("tgLbFeedback", ["lightboxService", "$tgRepo", "$tgConfirm", FeedbackDirective])
|
|
@ -49,11 +49,12 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
||||||
"$tgLocation",
|
"$tgLocation",
|
||||||
"$appTitle",
|
"$appTitle",
|
||||||
"$tgNavUrls",
|
"$tgNavUrls",
|
||||||
|
"$tgEvents",
|
||||||
"tgLoader"
|
"tgLoader"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @urls, @params, @q, @location, @appTitle,
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @urls, @params, @q, @location, @appTitle,
|
||||||
@navUrls, tgLoader) ->
|
@navUrls, @events, tgLoader) ->
|
||||||
@scope.sectionName = "Issues"
|
@scope.sectionName = "Issues"
|
||||||
@scope.filters = {}
|
@scope.filters = {}
|
||||||
|
|
||||||
|
@ -82,6 +83,11 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
||||||
@.loadIssues()
|
@.loadIssues()
|
||||||
@.loadFilters()
|
@.loadFilters()
|
||||||
|
|
||||||
|
initializeSubscription: ->
|
||||||
|
routingKey = "changes.project.#{@scope.projectId}.issues"
|
||||||
|
@events.subscribe @scope, routingKey, (message) =>
|
||||||
|
@.loadIssues()
|
||||||
|
|
||||||
storeFilters: ->
|
storeFilters: ->
|
||||||
@rs.issues.storeFilters(@params.pslug, @location.search())
|
@rs.issues.storeFilters(@params.pslug, @location.search())
|
||||||
|
|
||||||
|
@ -256,6 +262,7 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
||||||
loadInitialData: ->
|
loadInitialData: ->
|
||||||
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
|
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
|
||||||
@scope.projectId = data.project
|
@scope.projectId = data.project
|
||||||
|
@.initializeSubscription()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
return promise.then(=> @.loadProject())
|
return promise.then(=> @.loadProject())
|
||||||
|
@ -755,6 +762,9 @@ IssueStatusInlineEditionDirective = ($repo, popoverService) ->
|
||||||
$el.unbind("click")
|
$el.unbind("click")
|
||||||
$el.find("a").addClass("not-clickable")
|
$el.find("a").addClass("not-clickable")
|
||||||
|
|
||||||
|
$scope.$watch $attrs.tgIssueStatusInlineEdition, (val) =>
|
||||||
|
updateIssueStatus($el, val, $scope.issueStatusById)
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
$el.off()
|
$el.off()
|
||||||
|
|
||||||
|
@ -803,6 +813,9 @@ IssueAssignedToInlineEditionDirective = ($repo, $rootscope, popoverService) ->
|
||||||
$repo.save(updatedIssue)
|
$repo.save(updatedIssue)
|
||||||
updateIssue(updatedIssue)
|
updateIssue(updatedIssue)
|
||||||
|
|
||||||
|
$scope.$watch $attrs.tgIssueAssignedToInlineEdition, (val) =>
|
||||||
|
updateIssue(val)
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
$el.off()
|
$el.off()
|
||||||
|
|
||||||
|
|
|
@ -59,11 +59,12 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
||||||
"$tgLocation",
|
"$tgLocation",
|
||||||
"$appTitle",
|
"$appTitle",
|
||||||
"$tgNavUrls",
|
"$tgNavUrls",
|
||||||
|
"$tgEvents",
|
||||||
"tgLoader"
|
"tgLoader"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @appTitle, @navUrls,
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
|
||||||
tgLoader) ->
|
@appTitle, @navUrls, @events, tgLoader) ->
|
||||||
_.bindAll(@)
|
_.bindAll(@)
|
||||||
@scope.sectionName = "Kanban"
|
@scope.sectionName = "Kanban"
|
||||||
@scope.statusViewModes = {}
|
@scope.statusViewModes = {}
|
||||||
|
@ -162,10 +163,16 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
||||||
@scope.$emit("project:loaded", project)
|
@scope.$emit("project:loaded", project)
|
||||||
return project
|
return project
|
||||||
|
|
||||||
|
initializeSubscription: ->
|
||||||
|
routingKey1 = "changes.project.#{@scope.projectId}.userstories"
|
||||||
|
@events.subscribe @scope, routingKey1, (message) =>
|
||||||
|
@.loadUserstories()
|
||||||
|
|
||||||
loadInitialData: ->
|
loadInitialData: ->
|
||||||
# Resolve project slug
|
# Resolve project slug
|
||||||
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
|
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
|
||||||
@scope.projectId = data.project
|
@scope.projectId = data.project
|
||||||
|
@.initializeSubscription()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
return promise.then(=> @.loadProject())
|
return promise.then(=> @.loadProject())
|
||||||
|
@ -250,13 +257,16 @@ module.controller("KanbanController", KanbanController)
|
||||||
|
|
||||||
KanbanDirective = ($repo, $rootscope) ->
|
KanbanDirective = ($repo, $rootscope) ->
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
|
|
||||||
tableBodyDom = $el.find(".kanban-table-body")
|
tableBodyDom = $el.find(".kanban-table-body")
|
||||||
|
|
||||||
tableBodyDom.on "scroll", (event) ->
|
tableBodyDom.on "scroll", (event) ->
|
||||||
target = angular.element(event.currentTarget)
|
target = angular.element(event.currentTarget)
|
||||||
tableHeaderDom = $el.find(".kanban-table-header .kanban-table-inner")
|
tableHeaderDom = $el.find(".kanban-table-header .kanban-table-inner")
|
||||||
tableHeaderDom.css("left", -1 * target.scrollLeft())
|
tableHeaderDom.css("left", -1 * target.scrollLeft())
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
return {link: link}
|
return {link: link}
|
||||||
|
|
||||||
module.directive("tgKanban", ["$tgRepo", "$rootScope", KanbanDirective])
|
module.directive("tgKanban", ["$tgRepo", "$rootScope", KanbanDirective])
|
||||||
|
@ -273,10 +283,14 @@ KanbanRowWidthFixerDirective = ->
|
||||||
size = (statuses.length * itemSize) - 10
|
size = (statuses.length * itemSize) - 10
|
||||||
$el.css("width", "#{size}px")
|
$el.css("width", "#{size}px")
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
return {link: link}
|
return {link: link}
|
||||||
|
|
||||||
module.directive("tgKanbanRowWidthFixer", KanbanRowWidthFixerDirective)
|
module.directive("tgKanbanRowWidthFixer", KanbanRowWidthFixerDirective)
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Kanban Column Height Fixer Directive
|
## Kanban Column Height Fixer Directive
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
@ -294,6 +308,9 @@ KanbanColumnHeightFixerDirective = ->
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
timeout(500, -> renderSize($el))
|
timeout(500, -> renderSize($el))
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
return {link:link}
|
return {link:link}
|
||||||
|
|
||||||
|
|
||||||
|
@ -305,14 +322,23 @@ module.directive("tgKanbanColumnHeightFixer", KanbanColumnHeightFixerDirective)
|
||||||
|
|
||||||
KanbanUserstoryDirective = ($rootscope) ->
|
KanbanUserstoryDirective = ($rootscope) ->
|
||||||
link = ($scope, $el, $attrs, $model) ->
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
$el.disableSelection()
|
||||||
|
|
||||||
|
$scope.$watch "us", (us) ->
|
||||||
|
if us.is_blocked and not $el.hasClass("blocked")
|
||||||
|
$el.addClass("blocked")
|
||||||
|
else if not us.is_blocked and $el.hasClass("blocked")
|
||||||
|
$el.removeClass("blocked")
|
||||||
|
|
||||||
$el.find(".icon-edit").on "click", (event) ->
|
$el.find(".icon-edit").on "click", (event) ->
|
||||||
if $el.find(".icon-edit").hasClass("noclick")
|
if $el.find(".icon-edit").hasClass("noclick")
|
||||||
return
|
return
|
||||||
|
|
||||||
$scope.$apply ->
|
$scope.$apply ->
|
||||||
$rootscope.$broadcast("usform:edit", $model.$modelValue)
|
$rootscope.$broadcast("usform:edit", $model.$modelValue)
|
||||||
if $scope.us.is_blocked
|
|
||||||
$el.addClass("blocked")
|
$scope.$on "$destroy", ->
|
||||||
$el.disableSelection()
|
$el.off()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
templateUrl: "/partials/views/components/kanban-task.html"
|
templateUrl: "/partials/views/components/kanban-task.html"
|
||||||
|
@ -320,7 +346,6 @@ KanbanUserstoryDirective = ($rootscope) ->
|
||||||
require: "ngModel"
|
require: "ngModel"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
module.directive("tgKanbanUserstory", ["$rootScope", KanbanUserstoryDirective])
|
module.directive("tgKanbanUserstory", ["$rootScope", KanbanUserstoryDirective])
|
||||||
|
|
||||||
|
|
||||||
|
@ -344,6 +369,9 @@ KanbanWipLimitDirective = ->
|
||||||
$scope.$on "usform:new:success", redrawWipLimit
|
$scope.$on "usform:new:success", redrawWipLimit
|
||||||
$scope.$on "usform:bulk:success", redrawWipLimit
|
$scope.$on "usform:bulk:success", redrawWipLimit
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
return {link: link}
|
return {link: link}
|
||||||
|
|
||||||
module.directive("tgKanbanWipLimit", KanbanWipLimitDirective)
|
module.directive("tgKanbanWipLimit", KanbanWipLimitDirective)
|
||||||
|
@ -405,7 +433,9 @@ KanbanUserDirective = ($log) ->
|
||||||
$ctrl = $el.controller()
|
$ctrl = $el.controller()
|
||||||
$ctrl.changeUsAssignedTo(us)
|
$ctrl.changeUsAssignedTo(us)
|
||||||
|
|
||||||
|
$scope.$on "$destroy", ->
|
||||||
|
$el.off()
|
||||||
|
|
||||||
return {link: link, require:"ngModel"}
|
return {link: link, require:"ngModel"}
|
||||||
|
|
||||||
|
|
||||||
module.directive("tgKanbanUserAvatar", ["$log", KanbanUserDirective])
|
module.directive("tgKanbanUserAvatar", ["$log", KanbanUserDirective])
|
||||||
|
|
|
@ -37,6 +37,10 @@ module = angular.module("taigaKanban")
|
||||||
|
|
||||||
KanbanSortableDirective = ($repo, $rs, $rootscope) ->
|
KanbanSortableDirective = ($repo, $rs, $rootscope) ->
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
|
bindOnce $scope, "project", (project) ->
|
||||||
|
if not (project.my_permissions.indexOf("modify_us") > -1)
|
||||||
|
return
|
||||||
|
|
||||||
oldParentScope = null
|
oldParentScope = null
|
||||||
newParentScope = null
|
newParentScope = null
|
||||||
itemEl = null
|
itemEl = null
|
||||||
|
|
|
@ -59,6 +59,7 @@ class ProjectsNavigationController extends taiga.Controller
|
||||||
return projects
|
return projects
|
||||||
|
|
||||||
newProject: ->
|
newProject: ->
|
||||||
|
@scope.$apply () =>
|
||||||
@rootscope.$broadcast("projects:create")
|
@rootscope.$broadcast("projects:create")
|
||||||
|
|
||||||
filterProjects: (text) ->
|
filterProjects: (text) ->
|
||||||
|
@ -200,7 +201,7 @@ module.directive("tgProjectsNav", ["$rootScope", "animationFrame", "$timeout", "
|
||||||
## Project
|
## Project
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $navUrls) ->
|
ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $navUrls, $config) ->
|
||||||
menuEntriesTemplate = _.template("""
|
menuEntriesTemplate = _.template("""
|
||||||
<div class="menu-container">
|
<div class="menu-container">
|
||||||
<ul class="main-nav">
|
<ul class="main-nav">
|
||||||
|
@ -262,6 +263,9 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $
|
||||||
<li><a href="" title="User Profile", tg-nav="user-settings-user-profile:project=project.slug">User Profile</a></li>
|
<li><a href="" title="User Profile", tg-nav="user-settings-user-profile:project=project.slug">User Profile</a></li>
|
||||||
<li><a href="" title="Change Password", tg-nav="user-settings-user-change-password:project=project.slug">Change Password</a></li>
|
<li><a href="" title="Change Password", tg-nav="user-settings-user-change-password:project=project.slug">Change Password</a></li>
|
||||||
<li><a href="" title="Notifications", tg-nav="user-settings-mail-notifications:project=project.slug">Notifications</a></li>
|
<li><a href="" title="Notifications", tg-nav="user-settings-mail-notifications:project=project.slug">Notifications</a></li>
|
||||||
|
<% if (feedbackEnabled) { %>
|
||||||
|
<li><a href="" class="feedback" title="Feedback"">Feedback</a></li>
|
||||||
|
<% } %>
|
||||||
<li><a href="" title="Logout" class="logout">Logout</a></li>
|
<li><a href="" title="Logout" class="logout">Logout</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<a href="" title="User preferences" class="avatar" id="nav-user-settings">
|
<a href="" title="User preferences" class="avatar" id="nav-user-settings">
|
||||||
|
@ -275,8 +279,11 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $
|
||||||
mainTemplate = _.template("""
|
mainTemplate = _.template("""
|
||||||
<div class="logo-container logo">
|
<div class="logo-container logo">
|
||||||
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
|
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 134.2 134.3" version="1.1" preserveAspectRatio="xMidYMid meet">
|
viewBox="0 0 134.2 134.3" version="1.1" preserveAspectRatio="xMidYMid meet" shape-rendering="geometricPrecision">
|
||||||
<style>
|
<style>
|
||||||
|
svg {
|
||||||
|
transform: scale(.99);
|
||||||
|
}
|
||||||
path {
|
path {
|
||||||
fill:#f5f5f5;
|
fill:#f5f5f5;
|
||||||
opacity:0.7;
|
opacity:0.7;
|
||||||
|
@ -322,7 +329,14 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $
|
||||||
renderMenuEntries = ($el, targetScope, project={}) ->
|
renderMenuEntries = ($el, targetScope, project={}) ->
|
||||||
container = $el.find(".menu-container")
|
container = $el.find(".menu-container")
|
||||||
sectionName = targetScope.section
|
sectionName = targetScope.section
|
||||||
dom = $compile(menuEntriesTemplate({user: $auth.getUser(), project: project}))(targetScope)
|
|
||||||
|
ctx = {
|
||||||
|
user: $auth.getUser(),
|
||||||
|
project: project,
|
||||||
|
feedbackEnabled: $config.get("feedbackEnabled")
|
||||||
|
}
|
||||||
|
dom = $compile(menuEntriesTemplate(ctx))(targetScope)
|
||||||
|
|
||||||
dom.find("a.active").removeClass("active")
|
dom.find("a.active").removeClass("active")
|
||||||
dom.find("#nav-#{sectionName} > a").addClass("active")
|
dom.find("#nav-#{sectionName} > a").addClass("active")
|
||||||
|
|
||||||
|
@ -368,6 +382,10 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
$rootscope.$broadcast("search-box:show", project)
|
$rootscope.$broadcast("search-box:show", project)
|
||||||
|
|
||||||
|
$el.on "click", ".feedback", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
$rootscope.$broadcast("feedback:show")
|
||||||
|
|
||||||
$scope.$on "projects:loaded", (listener) ->
|
$scope.$on "projects:loaded", (listener) ->
|
||||||
$el.addClass("hidden")
|
$el.addClass("hidden")
|
||||||
listener.stopPropagation()
|
listener.stopPropagation()
|
||||||
|
@ -383,4 +401,4 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $
|
||||||
return {link: link}
|
return {link: link}
|
||||||
|
|
||||||
module.directive("tgProjectMenu", ["$log", "$compile", "$tgAuth", "$rootScope", "$tgAuth", "$tgLocation",
|
module.directive("tgProjectMenu", ["$log", "$compile", "$tgAuth", "$rootScope", "$tgAuth", "$tgLocation",
|
||||||
"$tgNavUrls", ProjectMenuDirective])
|
"$tgNavUrls", "$tgConfig", ProjectMenuDirective])
|
||||||
|
|
|
@ -67,7 +67,6 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
|
||||||
$scope.templates = result
|
$scope.templates = result
|
||||||
$scope.data.creation_template = _.head(_.filter($scope.templates, (x) -> x.slug == "scrum")).id
|
$scope.data.creation_template = _.head(_.filter($scope.templates, (x) -> x.slug == "scrum")).id
|
||||||
else
|
else
|
||||||
$scope.$apply ->
|
|
||||||
$scope.data.creation_template = _.head(_.filter($scope.templates, (x) -> x.slug == "scrum")).id
|
$scope.data.creation_template = _.head(_.filter($scope.templates, (x) -> x.slug == "scrum")).id
|
||||||
|
|
||||||
$el.find(".active").removeClass("active")
|
$el.find(".active").removeClass("active")
|
||||||
|
|
|
@ -241,8 +241,7 @@ RelatedTaskCreateFormDirective = ($repo, $compile, $confirm, $tgmodel, $loading)
|
||||||
createTask(newTask).then ->
|
createTask(newTask).then ->
|
||||||
$el.html("")
|
$el.html("")
|
||||||
|
|
||||||
$scope.$watch "us", (val) ->
|
taiga.bindOnce $scope, "us", (val) ->
|
||||||
return if not val
|
|
||||||
newTask["status"] = $scope.project.default_task_status
|
newTask["status"] = $scope.project.default_task_status
|
||||||
newTask["project"] = $scope.project.id
|
newTask["project"] = $scope.project.id
|
||||||
newTask["user_story"] = $scope.us.id
|
newTask["user_story"] = $scope.us.id
|
||||||
|
@ -299,8 +298,7 @@ RelatedTasksDirective = ($repo, $rs, $rootscope) ->
|
||||||
$scope.$on "related-tasks:add-new-clicked", ->
|
$scope.$on "related-tasks:add-new-clicked", ->
|
||||||
$scope.$broadcast("related-tasks:show-form")
|
$scope.$broadcast("related-tasks:show-form")
|
||||||
|
|
||||||
$scope.$watch "us", (val) ->
|
taiga.bindOnce $scope, "us", (val) ->
|
||||||
return if not val
|
|
||||||
loadTasks()
|
loadTasks()
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
|
|
|
@ -94,6 +94,9 @@ urls = {
|
||||||
"attachments/issue": "/api/v1/issues/attachments"
|
"attachments/issue": "/api/v1/issues/attachments"
|
||||||
"attachments/task": "/api/v1/tasks/attachments"
|
"attachments/task": "/api/v1/tasks/attachments"
|
||||||
"attachments/wiki_page": "/api/v1/wiki/attachments"
|
"attachments/wiki_page": "/api/v1/wiki/attachments"
|
||||||
|
|
||||||
|
# Feedback
|
||||||
|
"feedback": "/api/v1/feedback"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Initialize api urls service
|
# Initialize api urls service
|
||||||
|
|
|
@ -46,11 +46,12 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
"$appTitle",
|
"$appTitle",
|
||||||
"$tgLocation",
|
"$tgLocation",
|
||||||
"$tgNavUrls"
|
"$tgNavUrls"
|
||||||
|
"$tgEvents"
|
||||||
"tgLoader"
|
"tgLoader"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @appTitle, @location, @navUrls,
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @appTitle, @location, @navUrls,
|
||||||
tgLoader) ->
|
@events, tgLoader) ->
|
||||||
_.bindAll(@)
|
_.bindAll(@)
|
||||||
|
|
||||||
@scope.sectionName = "Taskboard"
|
@scope.sectionName = "Taskboard"
|
||||||
|
@ -82,6 +83,17 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
promise.then null, ->
|
promise.then null, ->
|
||||||
console.log "FAIL" # TODO
|
console.log "FAIL" # TODO
|
||||||
|
|
||||||
|
initializeSubscription: ->
|
||||||
|
routingKey = "changes.project.#{@scope.projectId}.tasks"
|
||||||
|
@events.subscribe @scope, routingKey, (message) =>
|
||||||
|
@.loadTaskboard()
|
||||||
|
|
||||||
|
routingKey1 = "changes.project.#{@scope.projectId}.userstories"
|
||||||
|
@events.subscribe @scope, routingKey1, (message) =>
|
||||||
|
@.refreshTagsColors()
|
||||||
|
@.loadSprintStats()
|
||||||
|
@.loadSprint()
|
||||||
|
|
||||||
loadProject: ->
|
loadProject: ->
|
||||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||||
@scope.project = project
|
@scope.project = project
|
||||||
|
@ -111,6 +123,8 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
@scope.stats.completedPercentage = Math.round(100 * stats.completedPointsSum / stats.totalPointsSum)
|
@scope.stats.completedPercentage = Math.round(100 * stats.completedPointsSum / stats.totalPointsSum)
|
||||||
else
|
else
|
||||||
@scope.stats.completedPercentage = 0
|
@scope.stats.completedPercentage = 0
|
||||||
|
|
||||||
|
@scope.stats.openTasks = stats.total_tasks - stats.completed_tasks
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
refreshTagsColors: ->
|
refreshTagsColors: ->
|
||||||
|
@ -157,6 +171,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
promise = @repo.resolve(params).then (data) =>
|
promise = @repo.resolve(params).then (data) =>
|
||||||
@scope.projectId = data.project
|
@scope.projectId = data.project
|
||||||
@scope.sprintId = data.milestone
|
@scope.sprintId = data.milestone
|
||||||
|
@.initializeSubscription()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
return promise.then(=> @.loadProject())
|
return promise.then(=> @.loadProject())
|
||||||
|
@ -204,7 +219,8 @@ TaskboardDirective = ($rootscope) ->
|
||||||
$el.on "click", ".toggle-analytics-visibility", (event) ->
|
$el.on "click", ".toggle-analytics-visibility", (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
target = angular.element(event.currentTarget)
|
target = angular.element(event.currentTarget)
|
||||||
toggleText(target, ["Hide statistics", "Show statistics"]) # TODO: i18n
|
target.toggleClass('active');
|
||||||
|
#toggleText(target, ["Hide statistics", "Show statistics"]) # TODO: i18n
|
||||||
$rootscope.$broadcast("taskboard:graph:toggle-visibility")
|
$rootscope.$broadcast("taskboard:graph:toggle-visibility")
|
||||||
|
|
||||||
tableBodyDom = $el.find(".taskboard-table-body")
|
tableBodyDom = $el.find(".taskboard-table-body")
|
||||||
|
|
|
@ -125,15 +125,6 @@ sizeFormat = (input, precision=1) ->
|
||||||
return "#{size} #{units[number]}"
|
return "#{size} #{units[number]}"
|
||||||
|
|
||||||
|
|
||||||
typeIsArray = Array.isArray || ( value ) -> return {}.toString.call( value ) is '[object Array]'
|
|
||||||
|
|
||||||
|
|
||||||
# Generic method for generate hash from a arbitrary length
|
|
||||||
# collection of parameters.
|
|
||||||
generateHash = (components=[]) ->
|
|
||||||
components = _.map(components, (x) -> JSON.stringify(x))
|
|
||||||
return hex_sha1(components.join(":"))
|
|
||||||
|
|
||||||
taiga = @.taiga
|
taiga = @.taiga
|
||||||
taiga.nl2br = nl2br
|
taiga.nl2br = nl2br
|
||||||
taiga.bindOnce = bindOnce
|
taiga.bindOnce = bindOnce
|
||||||
|
@ -151,5 +142,3 @@ taiga.joinStr = joinStr
|
||||||
taiga.debounce = debounce
|
taiga.debounce = debounce
|
||||||
taiga.startswith = startswith
|
taiga.startswith = startswith
|
||||||
taiga.sizeFormat = sizeFormat
|
taiga.sizeFormat = sizeFormat
|
||||||
taiga.typeIsArray = typeIsArray
|
|
||||||
taiga.generateHash = generateHash
|
|
||||||
|
|
|
@ -12,6 +12,8 @@ config = {
|
||||||
publicRegisterEnabled: true
|
publicRegisterEnabled: true
|
||||||
privacyPolicyUrl: null
|
privacyPolicyUrl: null
|
||||||
termsOfServiceUrl: null
|
termsOfServiceUrl: null
|
||||||
|
|
||||||
|
feedbackEnabled: true
|
||||||
}
|
}
|
||||||
|
|
||||||
angular.module("taigaLocalConfig", []).value("localconfig", config)
|
angular.module("taigaLocalConfig", []).value("localconfig", config)
|
||||||
|
|
Binary file not shown.
|
@ -37,11 +37,12 @@
|
||||||
<glyph unicode="B" d="M453 425c-1 9-8 16-17 16l-360 0 0 0c-9 0-16-7-17-16l0 0 0-288 0 0c2-8 9-14 17-14l0 0 59 0 0-35c0-9 8-17 17-17 6 0 11 3 14 8l45 44 225 0c8 0 15 6 17 14l0 0 0 288z"/>
|
<glyph unicode="B" d="M453 425c-1 9-8 16-17 16l-360 0 0 0c-9 0-16-7-17-16l0 0 0-288 0 0c2-8 9-14 17-14l0 0 59 0 0-35c0-9 8-17 17-17 6 0 11 3 14 8l45 44 225 0c8 0 15 6 17 14l0 0 0 288z"/>
|
||||||
<glyph unicode="C" d="M411 448l0 0 0 21c0 4-4 8-9 8l-168 0-133-132 0-302c0-4 4-8 9-8l292 0c5 0 9 4 9 8l0 13 0 0z m-259-362l0 241 91 0c5 0 9 4 9 9l0 90 108 0 0-340z"/>
|
<glyph unicode="C" d="M411 448l0 0 0 21c0 4-4 8-9 8l-168 0-133-132 0-302c0-4 4-8 9-8l292 0c5 0 9 4 9 8l0 13 0 0z m-259-362l0 241 91 0c5 0 9 4 9 9l0 90 108 0 0-340z"/>
|
||||||
<glyph unicode="D" d="M155 59c-14 0-32 5-50 23-24 24-27 47-25 62 2 17 11 34 27 50l150 151c43 42 71 26 83 13 16-15 26-42-14-82l-138-139-27 27 138 139c22 21 17 26 14 28-1 2-7 8-29-13l-150-151c-7-7-15-17-16-28-1-9 4-19 14-30 13-13 23-12 26-11 9 1 19 7 30 18 13 12 167 166 178 178 15 15 24 29 28 42 5 20-1 38-20 57-19 19-51 40-98-8l-165-165c-8-7-20-7-27 0-8 8-8 20 0 27l165 165c51 51 106 54 152 8 36-36 36-71 30-94-6-20-18-39-38-59-11-12-166-166-178-178-17-17-34-26-51-29-3 0-6-1-9-1z"/>
|
<glyph unicode="D" d="M155 59c-14 0-32 5-50 23-24 24-27 47-25 62 2 17 11 34 27 50l150 151c43 42 71 26 83 13 16-15 26-42-14-82l-138-139-27 27 138 139c22 21 17 26 14 28-1 2-7 8-29-13l-150-151c-7-7-15-17-16-28-1-9 4-19 14-30 13-13 23-12 26-11 9 1 19 7 30 18 13 12 167 166 178 178 15 15 24 29 28 42 5 20-1 38-20 57-19 19-51 40-98-8l-165-165c-8-7-20-7-27 0-8 8-8 20 0 27l165 165c51 51 106 54 152 8 36-36 36-71 30-94-6-20-18-39-38-59-11-12-166-166-178-178-17-17-34-26-51-29-3 0-6-1-9-1z"/>
|
||||||
<glyph unicode="F" d="M402 165c0-5-2-10-5-13-4-4-8-6-13-6l-256 0c-5 0-9 2-13 6-3 3-5 8-5 13 0 5 2 9 5 12l128 128c4 4 8 6 13 6 5 0 9-2 13-6l128-128c3-3 5-7 5-12z"/>
|
<glyph unicode="E" d="M402 165c0-5-2-10-5-13-4-4-8-6-13-6l-256 0c-5 0-9 2-13 6-3 3-5 8-5 13 0 5 2 9 5 12l128 128c4 4 8 6 13 6 5 0 9-2 13-6l128-128c3-3 5-7 5-12z"/>
|
||||||
<glyph unicode="G" d="M402 311c0-5-2-9-5-13l-128-128c-4-4-8-5-13-5-5 0-9 1-13 5l-128 128c-3 4-5 8-5 13 0 5 2 9 5 13 4 3 8 5 13 5l256 0c5 0 9-2 13-5 3-4 5-8 5-13z"/>
|
<glyph unicode="F" d="M402 311c0-5-2-9-5-13l-128-128c-4-4-8-5-13-5-5 0-9 1-13 5l-128 128c-3 4-5 8-5 13 0 5 2 9 5 13 4 3 8 5 13 5l256 0c5 0 9-2 13-5 3-4 5-8 5-13z"/>
|
||||||
<glyph unicode="H" d="M128 384l64 0 0-64-64 0z m0-96l64 0 0-64-64 0z m0-96l64 0 0-64-64 0z m128 0l128 0 0-64-128 0z m224 320l-448 0c-18 0-32-14-32-32l0-448c0-18 14-32 32-32l448 0c18 0 32 14 32 32l0 448c0 18-14 32-32 32z m-32-448l-384 0 0 384 384 0z m-192 224l128 0 0-64-128 0z m0 96l128 0 0-64-128 0z"/>
|
<glyph unicode="G" d="M128 384l64 0 0-64-64 0z m0-96l64 0 0-64-64 0z m0-96l64 0 0-64-64 0z m128 0l128 0 0-64-128 0z m224 320l-448 0c-18 0-32-14-32-32l0-448c0-18 14-32 32-32l448 0c18 0 32 14 32 32l0 448c0 18-14 32-32 32z m-32-448l-384 0 0 384 384 0z m-192 224l128 0 0-64-128 0z m0 96l128 0 0-64-128 0z"/>
|
||||||
<glyph unicode="I" d="M184 20c0 0 0 54 0 54 0 0 144 0 144 0 0 0 0-54 0-54-24-14-48-21-73-20-23-1-47 6-71 20m141 84c0 0-138 0-138 0 0 25-6 49-19 72-12 23-25 43-40 58-14 15-26 34-37 57-11 23-16 47-14 71 3 41 19 77 48 106 30 29 73 44 130 44 58 0 102-15 131-44 29-29 45-65 49-106 1-20-2-39-9-57-6-18-15-35-26-50-11-14-22-29-33-43-12-14-21-31-30-49-8-19-12-38-12-59m-193 254c-2-1-2-4 0-10 1-5 1-9 1-10-1-1 0-5 2-10 3-5 4-8 3-9 0-1 1-4 4-9 3-5 5-9 6-10 1-1 3-5 7-10 3-5 6-8 7-9 1-2 3-5 7-11 5-6 7-10 9-12 30-42 49-78 57-108 0 0 42 0 42 0 8 32 27 68 57 108 2 2 6 8 13 18 7 10 12 16 13 18 1 3 4 8 9 15 4 8 7 13 8 17 1 4 2 9 3 14 1 6 1 12 0 18-5 67-47 101-125 101-77 0-118-34-123-101"/>
|
<glyph unicode="H" d="M184 20c0 0 0 54 0 54 0 0 144 0 144 0 0 0 0-54 0-54-24-14-48-21-73-20-23-1-47 6-71 20m141 84c0 0-138 0-138 0 0 25-6 49-19 72-12 23-25 43-40 58-14 15-26 34-37 57-11 23-16 47-14 71 3 41 19 77 48 106 30 29 73 44 130 44 58 0 102-15 131-44 29-29 45-65 49-106 1-20-2-39-9-57-6-18-15-35-26-50-11-14-22-29-33-43-12-14-21-31-30-49-8-19-12-38-12-59m-193 254c-2-1-2-4 0-10 1-5 1-9 1-10-1-1 0-5 2-10 3-5 4-8 3-9 0-1 1-4 4-9 3-5 5-9 6-10 1-1 3-5 7-10 3-5 6-8 7-9 1-2 3-5 7-11 5-6 7-10 9-12 30-42 49-78 57-108 0 0 42 0 42 0 8 32 27 68 57 108 2 2 6 8 13 18 7 10 12 16 13 18 1 3 4 8 9 15 4 8 7 13 8 17 1 4 2 9 3 14 1 6 1 12 0 18-5 67-47 101-125 101-77 0-118-34-123-101"/>
|
||||||
<glyph unicode="E" d="M480 224l-64 0c-18 0-32 14-32 32 0 18 14 32 32 32l64 0c18 0 32-14 32-32 0-18-14-32-32-32z m-88 122c-13-12-33-12-45 0-13 13-13 33 0 46l45 45c12 12 33 12 45 0 13-13 13-33 0-45z m-136-346c-18 0-32 14-32 32l0 64c0 18 14 32 32 32 18 0 32-14 32-32l0-64c0-18-14-32-32-32z m0 384c-18 0-32 14-32 32l0 64c0 18 14 32 32 32 18 0 32-14 32-32l0-64c0-18-14-32-32-32z m-136-309c-12-13-32-13-45 0-12 12-12 33 0 45l45 45c13 13 33 13 46 0 12-12 12-32 0-45z m0 271l-45 46c-12 12-12 32 0 45 13 12 33 12 45 0l46-45c12-13 12-33 0-46-13-12-33-12-46 0z m8-90c0-18-14-32-32-32l-64 0c-18 0-32 14-32 32 0 18 14 32 32 32l64 0c18 0 32-14 32-32z m264-91l45-45c13-12 13-33 0-45-12-13-33-13-45 0l-45 45c-13 13-13 33 0 45 12 13 32 13 45 0z"/>
|
<glyph unicode="I" d="M480 224l-64 0c-18 0-32 14-32 32 0 18 14 32 32 32l64 0c18 0 32-14 32-32 0-18-14-32-32-32z m-88 122c-13-12-33-12-45 0-13 13-13 33 0 46l45 45c12 12 33 12 45 0 13-13 13-33 0-45z m-136-346c-18 0-32 14-32 32l0 64c0 18 14 32 32 32 18 0 32-14 32-32l0-64c0-18-14-32-32-32z m0 384c-18 0-32 14-32 32l0 64c0 18 14 32 32 32 18 0 32-14 32-32l0-64c0-18-14-32-32-32z m-136-309c-12-13-32-13-45 0-12 12-12 33 0 45l45 45c13 13 33 13 46 0 12-12 12-32 0-45z m0 271l-45 46c-12 12-12 32 0 45 13 12 33 12 45 0l46-45c12-13 12-33 0-46-13-12-33-12-46 0z m8-90c0-18-14-32-32-32l-64 0c-18 0-32 14-32 32 0 18 14 32 32 32l64 0c18 0 32-14 32-32z m264-91l45-45c13-12 13-33 0-45-12-13-33-13-45 0l-45 45c-13 13-13 33 0 45 12 13 32 13 45 0z"/>
|
||||||
<glyph unicode="J" d="M184 54l24 145c1 3 0 5-2 7 0 0 0 0 0 0-2 2-5 3-7 3l-145-25c-3 0-5-2-6-5-1-3 0-7 2-9l28-28-76-76c-3-3-3-8 0-11l53-53c3-3 8-3 11 0l76 76 28-28c3-2 6-3 9-2 3 1 5 3 5 6z m144 404l-24-145c-1-3 0-5 2-7 0 0 0 0 0 0 2-2 5-3 7-3l145 25c3 0 5 2 6 5 1 3 0 7-2 9l-28 28 76 76c3 3 3 8 0 11l-53 53c-3 3-8 3-11 0l-76-76-28 28c-3 2-6 3-9 2-3-1-5-3-5-6z m130-274l-145 24c-3 1-5 0-7-2 0 0 0 0 0 0-2-2-3-5-3-7l25-145c0-3 2-5 5-6 3-1 7 0 9 2l28 28 76-76c3-3 8-3 11 0l53 53c3 3 3 8 0 11l-76 76 28 28c2 3 3 6 2 9-1 3-3 5-6 5z m-404 144l145-24c3-1 5 0 7 2 0 0 0 0 0 0 2 2 3 5 3 7l-25 145c0 3-2 5-5 6-3 1-6 0-9-2l-28-28-76 76c-3 3-8 3-11 0l-53-53c-3-3-3-8 0-11l76-76-28-28c-2-3-3-6-2-9 1-3 3-5 6-5z"/>
|
<glyph unicode="J" d="M184 54l24 145c1 3 0 5-2 7 0 0 0 0 0 0-2 2-5 3-7 3l-145-25c-3 0-5-2-6-5-1-3 0-7 2-9l28-28-76-76c-3-3-3-8 0-11l53-53c3-3 8-3 11 0l76 76 28-28c3-2 6-3 9-2 3 1 5 3 5 6z m144 404l-24-145c-1-3 0-5 2-7 0 0 0 0 0 0 2-2 5-3 7-3l145 25c3 0 5 2 6 5 1 3 0 7-2 9l-28 28 76 76c3 3 3 8 0 11l-53 53c-3 3-8 3-11 0l-76-76-28 28c-3 2-6 3-9 2-3-1-5-3-5-6z m130-274l-145 24c-3 1-5 0-7-2 0 0 0 0 0 0-2-2-3-5-3-7l25-145c0-3 2-5 5-6 3-1 7 0 9 2l28 28 76-76c3-3 8-3 11 0l53 53c3 3 3 8 0 11l-76 76 28 28c2 3 3 6 2 9-1 3-3 5-6 5z m-404 144l145-24c3-1 5 0 7 2 0 0 0 0 0 0 2 2 3 5 3 7l-25 145c0 3-2 5-5 6-3 1-6 0-9-2l-28-28-76 76c-3 3-8 3-11 0l-53-53c-3-3-3-8 0-11l76-76-28-28c-2-3-3-6-2-9 1-3 3-5 6-5z"/>
|
||||||
<glyph unicode="K" d="M24 154l-24-144c0-3 1-6 2-8 0 0 0 0 0 0 2-1 5-2 8-2l144 25c3 0 6 2 6 5 1 3 1 6-2 8l-28 29 76 75c3 4 3 9 0 12l-52 52c-3 4-9 4-12 0l-76-75-28 28c-2 2-5 3-8 2-3-1-5-4-6-7z m464 204l24 144c0 3-1 6-2 8 0 0 0 0 0 0-2 1-5 2-8 2l-144-25c-3 0-6-2-6-5-1-3-1-6 2-8l28-29-76-75c-3-4-3-9 0-12l52-52c3-4 9-4 12 0l76 75 28-28c2-2 5-3 8-2 3 1 5 4 6 7z m-130-334l144-24c3 0 6 1 8 2 0 0 0 0 0 0 1 2 2 5 2 8l-25 144c0 3-2 6-5 6-3 1-6 1-8-2l-29-28-75 76c-4 3-9 3-12 0l-52-52c-4-3-4-9 0-12l75-76-28-28c-2-2-3-5-2-8 1-3 4-5 7-6z m-204 464l-144 24c-3 0-6-1-8-2 0 0 0 0 0 0-1-2-2-5-2-8l25-144c0-3 2-6 5-6 3-1 6-1 8 2l29 28 75-76c4-3 9-3 12 0l52 52c4 3 4 9 0 12l-75 76 28 28c2 2 3 5 2 8-1 3-4 5-7 6z"/>
|
<glyph unicode="K" d="M24 154l-24-144c0-3 1-6 2-8 0 0 0 0 0 0 2-1 5-2 8-2l144 25c3 0 6 2 6 5 1 3 1 6-2 8l-28 29 76 75c3 4 3 9 0 12l-52 52c-3 4-9 4-12 0l-76-75-28 28c-2 2-5 3-8 2-3-1-5-4-6-7z m464 204l24 144c0 3-1 6-2 8 0 0 0 0 0 0-2 1-5 2-8 2l-144-25c-3 0-6-2-6-5-1-3-1-6 2-8l28-29-76-75c-3-4-3-9 0-12l52-52c3-4 9-4 12 0l76 75 28-28c2-2 5-3 8-2 3 1 5 4 6 7z m-130-334l144-24c3 0 6 1 8 2 0 0 0 0 0 0 1 2 2 5 2 8l-25 144c0 3-2 6-5 6-3 1-6 1-8-2l-29-28-75 76c-4 3-9 3-12 0l-52-52c-4-3-4-9 0-12l75-76-28-28c-2-2-3-5-2-8 1-3 4-5 7-6z m-204 464l-144 24c-3 0-6-1-8-2 0 0 0 0 0 0-1-2-2-5-2-8l25-144c0-3 2-6 5-6 3-1 6-1 8 2l29 28 75-76c4-3 9-3 12 0l52 52c4 3 4 9 0 12l-75 76 28 28c2 2 3 5 2 8-1 3-4 5-7 6z"/>
|
||||||
|
<glyph unicode="L" d="M435 486c8 0 14-2 19-7 4-5 7-11 7-18 0 0 0-435 0-435 0 0-103 0-103 0 0 0 0 435 0 435 0 17 7 25 21 25 0 0 56 0 56 0m-153-153c7 0 13-3 18-8 5-5 7-11 7-18 0 0 0-281 0-281 0 0-102 0-102 0 0 0 0 281 0 281 0 17 7 26 20 26 0 0 57 0 57 0m-154-154c8 0 14-2 18-7 5-6 8-12 8-18 0 0 0-128 0-128 0 0-103 0-103 0 0 0 0 128 0 128 0 17 7 25 21 25 0 0 56 0 56 0"/>
|
||||||
</font></defs></svg>
|
</font></defs></svg>
|
||||||
|
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 3.5 KiB |
|
@ -31,6 +31,8 @@ html(lang="en", ng-app="taiga")
|
||||||
include partials/views/modules/lightbox-generic-error
|
include partials/views/modules/lightbox-generic-error
|
||||||
div.lightbox.lightbox-search(tg-search-box)
|
div.lightbox.lightbox-search(tg-search-box)
|
||||||
include partials/views/modules/lightbox-search
|
include partials/views/modules/lightbox-search
|
||||||
|
div.lightbox.lightbox-feedback.lightbox-generic-form(tg-lb-feedback)
|
||||||
|
include partials/views/modules/lightbox-feedback
|
||||||
|
|
||||||
include partials/views/modules/loader
|
include partials/views/modules/loader
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,6 @@ block content
|
||||||
div.login-container
|
div.login-container
|
||||||
img.logo-svg(src="/svg/logo.svg", alt="TAIGA")
|
img.logo-svg(src="/svg/logo.svg", alt="TAIGA")
|
||||||
h1.logo Taiga
|
h1.logo Taiga
|
||||||
h2.tagline ENJOY YOUR PROJECT,FINALLY
|
h2.tagline LOVE YOUR PROJECT
|
||||||
|
|
||||||
include views/modules/forgot-form
|
include views/modules/forgot-form
|
||||||
|
|
|
@ -9,6 +9,6 @@ block content
|
||||||
div.login-container
|
div.login-container
|
||||||
img.logo-svg(src="/svg/logo.svg", alt="TAIGA")
|
img.logo-svg(src="/svg/logo.svg", alt="TAIGA")
|
||||||
h1.logo Taiga
|
h1.logo Taiga
|
||||||
h2.tagline ENJOY YOUR PROJECT,FINALLY
|
h2.tagline LOVE YOUR PROJECT
|
||||||
|
|
||||||
include views/modules/login-form
|
include views/modules/login-form
|
||||||
|
|
|
@ -10,6 +10,6 @@ block content
|
||||||
object.logo-svg(type="image/svg+xml", data="/svg/logo.svg")
|
object.logo-svg(type="image/svg+xml", data="/svg/logo.svg")
|
||||||
img(src="/images/logo.png", alt="TAIGA")
|
img(src="/images/logo.png", alt="TAIGA")
|
||||||
h1.logo Taiga
|
h1.logo Taiga
|
||||||
h2.tagline ENJOY YOUR PROJECT,FINALLY
|
h2.tagline LOVE YOUR PROJECT
|
||||||
|
|
||||||
include views/modules/register-form
|
include views/modules/register-form
|
||||||
|
|
|
@ -6,7 +6,7 @@ div.row.us-item-row(ng-repeat="us in visibleUserstories track by us.id", tg-drag
|
||||||
a.clickable(tg-nav="project-userstories-detail:project=project.slug,ref=us.ref",
|
a.clickable(tg-nav="project-userstories-detail:project=project.slug,ref=us.ref",
|
||||||
title="#{{ us.ref }} {{ us.subject }}")
|
title="#{{ us.ref }} {{ us.subject }}")
|
||||||
span(tg-bo-ref="us.ref")
|
span(tg-bo-ref="us.ref")
|
||||||
span(tg-bo-bind="us.subject")
|
span(ng-bind="us.subject")
|
||||||
div.us-settings
|
div.us-settings
|
||||||
a.icon.icon-edit(tg-check-permission="modify_us", href="",
|
a.icon.icon-edit(tg-check-permission="modify_us", href="",
|
||||||
ng-click="ctrl.editUserStory(us)", title="Edit")
|
ng-click="ctrl.editUserStory(us)", title="Edit")
|
||||||
|
|
|
@ -35,4 +35,4 @@ div.summary.large-summary
|
||||||
span.icon.icon-iocaine
|
span.icon.icon-iocaine
|
||||||
span.number 10
|
span.number 10
|
||||||
span.description iocanie<br />doses
|
span.description iocanie<br />doses
|
||||||
a.button.button-green.toggle-analytics-visibility(href="", title="Show statistics") Show statistics
|
a.icon.icon-stats.toggle-analytics-visibility(href="", title="Show statistics")
|
||||||
|
|
|
@ -11,17 +11,11 @@ div.summary.large-summary
|
||||||
li
|
li
|
||||||
span.number(ng-bind="stats.completedPointsSum|default:'--'")
|
span.number(ng-bind="stats.completedPointsSum|default:'--'")
|
||||||
span.description completed<br />points
|
span.description completed<br />points
|
||||||
li
|
|
||||||
span.number(ng-bind="stats.remainingPointsSum|default:'--'")
|
|
||||||
span.description remaining<br />points
|
|
||||||
|
|
||||||
ul
|
ul
|
||||||
li
|
li
|
||||||
span.icon.icon-bulk
|
span.icon.icon-bulk
|
||||||
span.number(ng-bind="stats.total_tasks|default:'--'")
|
span.number(ng-bind="stats.openTasks|default:'--'")
|
||||||
span.description created<br />tasks
|
|
||||||
li
|
|
||||||
span.number(ng-bind="stats.remainingTasks|default:'--'")
|
|
||||||
span.description open<br />tasks
|
span.description open<br />tasks
|
||||||
li
|
li
|
||||||
span.number(ng-bind="stats.completed_tasks|default:'--'")
|
span.number(ng-bind="stats.completed_tasks|default:'--'")
|
||||||
|
@ -33,4 +27,4 @@ div.summary.large-summary
|
||||||
span.number(ng-bind="stats.iocaine_doses|default:'--'")
|
span.number(ng-bind="stats.iocaine_doses|default:'--'")
|
||||||
span.description iocaine<br />doses
|
span.description iocaine<br />doses
|
||||||
|
|
||||||
a.button.button-green.toggle-analytics-visibility(href="", title="Show statistics") Show statistics
|
a.icon.icon-stats.toggle-analytics-visibility(href="", title="Show statistics")
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
div.summary
|
div.summary
|
||||||
div.summary-progress-bar(tg-progress-bar="stats.completedPercentage")
|
div.summary-progress-bar(tg-backlog-progress-bar="stats")
|
||||||
div.current-progress(style="width: {{stats.completedPercentage}}")
|
|
||||||
div.data
|
div.data
|
||||||
span.number(tg-bind-html="stats.completedPercentage + '%'")
|
span.number(tg-bind-html="stats.completedPercentage + '%'")
|
||||||
ul
|
ul
|
||||||
|
@ -10,9 +10,9 @@ div.summary
|
||||||
li
|
li
|
||||||
span.number(tg-bind-html="stats.defined_points") --
|
span.number(tg-bind-html="stats.defined_points") --
|
||||||
span.description defined<br />points
|
span.description defined<br />points
|
||||||
li
|
|
||||||
span.number(tg-bind-html="stats.assigned_points") --
|
|
||||||
span.description assigned<br />points
|
|
||||||
li
|
li
|
||||||
span.number(tg-bind-html="stats.closed_points") --
|
span.number(tg-bind-html="stats.closed_points") --
|
||||||
span.description closed<br />points
|
span.description closed<br />points
|
||||||
|
li
|
||||||
|
span.number(tg-bind-html="stats.speed | number:0") --
|
||||||
|
span.description points /<br />sprint
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
section.admin-submenu
|
section.admin-submenu
|
||||||
header
|
header
|
||||||
h1 Custom Atrributes
|
h1 Custom Attributes
|
||||||
|
|
||||||
nav
|
nav
|
||||||
ul
|
ul
|
||||||
|
|
|
@ -14,7 +14,7 @@ section.issues-table.basic-table(ng-class="{empty: !issues.length}")
|
||||||
div.subject
|
div.subject
|
||||||
a(href="", tg-nav="project-issues-detail:project=project.slug,ref=issue.ref")
|
a(href="", tg-nav="project-issues-detail:project=project.slug,ref=issue.ref")
|
||||||
span(tg-bo-ref="issue.ref")
|
span(tg-bo-ref="issue.ref")
|
||||||
span(tg-bo-bind="issue.subject")
|
span(ng-bind="issue.subject")
|
||||||
|
|
||||||
div.issue-field(tg-issue-status-inline-edition="issue")
|
div.issue-field(tg-issue-status-inline-edition="issue")
|
||||||
a.issue-status(href="", title="Change status")
|
a.issue-status(href="", title="Change status")
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
a.close(href="", title="close")
|
||||||
|
span.icon.icon-delete
|
||||||
|
form
|
||||||
|
h2.title Tell us something...
|
||||||
|
fieldset
|
||||||
|
textarea(ng-model="feedback.comment", data-required="true",
|
||||||
|
placeholder="...a bug, some suggestions, something cool... or even your worst nightmare with Taiga")
|
||||||
|
fieldset
|
||||||
|
input.hidden(type="submit")
|
||||||
|
a.button.button-green(href="", title="Send feedback")
|
||||||
|
span Send feedback
|
|
@ -8,4 +8,4 @@ form
|
||||||
a.button.button-green(href="", title="Save")
|
a.button.button-green(href="", title="Save")
|
||||||
span Create
|
span Create
|
||||||
|
|
||||||
p.help-text We will add directly the users if they are registered, send an invitation to register if not
|
p.help-text If users are already registered on Taiga, they will be added automatically. Otherwise they will receive an invitation.
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
include ../components/spinner
|
include ../components/spinner
|
||||||
p Loading project...
|
p Loading project...
|
||||||
|
|
||||||
div.wizard-create-project.hidden(tg-lb-create-project)
|
div.wizard-create-project(tg-lb-create-project)
|
||||||
include wizard-create-project
|
include wizard-create-project
|
||||||
|
|
||||||
nav.projects-nav(ng-controller="ProjectsNavigationController", tg-projects-nav, tg-projects-pagination, projects="projects")
|
nav.projects-nav(ng-controller="ProjectsNavigationController", tg-projects-nav, tg-projects-pagination, projects="projects")
|
||||||
|
|
|
@ -14,5 +14,5 @@ section.admin-menu
|
||||||
span.icon.icon-arrow-right
|
span.icon.icon-arrow-right
|
||||||
li#usersettingsmenu-mail-notifications
|
li#usersettingsmenu-mail-notifications
|
||||||
a(href="", tg-nav="user-settings-mail-notifications:project=project.slug")
|
a(href="", tg-nav="user-settings-mail-notifications:project=project.slug")
|
||||||
span.title Email notificiations
|
span.title Email notifications
|
||||||
span.icon.icon-arrow-right
|
span.icon.icon-arrow-right
|
||||||
|
|
|
@ -1,27 +1,15 @@
|
||||||
.basic-table {
|
.basic-table {
|
||||||
align-content: stretch;
|
@include table-flex(stretch, center, flex, column, wrap, flex-start);
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: flex-start;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
.row {
|
.row {
|
||||||
align-content: stretch;
|
@include table-flex(stretch, center, flex, row, nowrap, flex-start);
|
||||||
align-items: center;
|
|
||||||
border-bottom: 1px solid $gray-light;
|
border-bottom: 1px solid $gray-light;
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
justify-content: flex-start;
|
|
||||||
padding: .5rem 0;
|
padding: .5rem 0;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@for $i from 1 through 8 {
|
@for $i from 1 through 8 {
|
||||||
.width-#{$i} {
|
.width-#{$i} {
|
||||||
flex-basis: 50px;
|
@include table-flex-child($i, 50px, 0);
|
||||||
flex-grow: $i;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:last-child {
|
&:last-child {
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
}
|
}
|
||||||
.data {
|
.data {
|
||||||
float: left;
|
float: left;
|
||||||
margin-right: 1.5em;
|
margin-right: 1em;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
.number {
|
.number {
|
||||||
color: $fresh-taiga;
|
color: $fresh-taiga;
|
||||||
|
@ -53,11 +53,26 @@
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 20%;
|
width: 15%;
|
||||||
.current-progress {
|
.current-progress {
|
||||||
background: $fresh-taiga;
|
background: $fresh-taiga;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
width: calc(30% - 4px);
|
}
|
||||||
|
.defined-points {
|
||||||
|
background: $red-light;
|
||||||
|
height: 24px;
|
||||||
|
position: absolute;
|
||||||
|
width: calc(100% - 6px);
|
||||||
|
}
|
||||||
|
.project-points-progress {
|
||||||
|
background: $white;
|
||||||
|
height: 24px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.closed-points-progress {
|
||||||
|
background: $fresh-taiga;
|
||||||
|
height: 24px;
|
||||||
|
position: absolute;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +82,7 @@
|
||||||
border-right: 1px solid $whitish;
|
border-right: 1px solid $whitish;
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
&:last-child {
|
&:last-of-type {
|
||||||
border: 0;
|
border: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
@ -75,8 +90,22 @@
|
||||||
.icon {
|
.icon {
|
||||||
@extend %xlarge;
|
@extend %xlarge;
|
||||||
margin-right: .4rem;
|
margin-right: .4rem;
|
||||||
|
&.icon-stats {
|
||||||
|
@include transition(color .3s linear);
|
||||||
|
color: $gray;
|
||||||
|
display: inline-block;
|
||||||
|
float: right;
|
||||||
|
&:hover {
|
||||||
|
@include transition(color .3s linear);
|
||||||
|
color: $fresh-taiga;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
color: $fresh-taiga;
|
||||||
|
&:hover {
|
||||||
|
@include transition(color .3s linear);
|
||||||
|
color: $gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.button {
|
|
||||||
color: $white;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,4 +42,7 @@
|
||||||
margin: 0 .5rem .5rem 0;
|
margin: 0 .5rem .5rem 0;
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
}
|
}
|
||||||
|
.save {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
.wysiwyg {
|
.wysiwyg {
|
||||||
|
overflow-x: scroll;
|
||||||
h1 {
|
h1 {
|
||||||
@extend %xlarge;
|
@extend %xlarge;
|
||||||
@extend %text;
|
@extend %text;
|
||||||
|
|
|
@ -75,6 +75,11 @@ ol { list-style: decimal; }
|
||||||
.pika-button {
|
.pika-button {
|
||||||
color: $green-taiga;
|
color: $green-taiga;
|
||||||
}
|
}
|
||||||
|
&.is-selected {
|
||||||
|
button {
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.pika-button {
|
.pika-button {
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|
|
@ -11,12 +11,12 @@
|
||||||
|
|
||||||
// Table Flex - http://devbryce.com/site/flexbox/
|
// Table Flex - http://devbryce.com/site/flexbox/
|
||||||
@mixin table-flex($align-content: stretch, $align-items: stretch, $display: flex, $flex-direction: row, $flex-wrap: wrap, $justify-content: flex-start) {
|
@mixin table-flex($align-content: stretch, $align-items: stretch, $display: flex, $flex-direction: row, $flex-wrap: wrap, $justify-content: flex-start) {
|
||||||
align-content: $align-content; // flex-start, flex-end, center, space-between, space-around, stretch
|
@include display($display);
|
||||||
align-items: $align-items; //flex-start, flex-end, center, baseline, stretch
|
@include align-content($align-content);
|
||||||
display: $display;
|
@include align-items($align-items);
|
||||||
flex-direction: $flex-direction; //row | row-reverse | column | column-reverse
|
@include flex-direction($flex-direction);
|
||||||
flex-wrap: $flex-wrap; // nowrap | wrap | wrap-reverse
|
@include flex-wrap($flex-wrap);
|
||||||
justify-content: $justify-content; //flex-start | flex-end | center | space-between | space-around
|
@include justify-content($justify-content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin table-flex-child($flex-grow: 1, $flex-basis: 300px, $flex-shrink: 0, $width:'') {
|
@mixin table-flex-child($flex-grow: 1, $flex-basis: 300px, $flex-shrink: 0, $width:'') {
|
||||||
|
|
|
@ -43,7 +43,6 @@ h6 {
|
||||||
h1 {
|
h1 {
|
||||||
@extend %xxlarge;
|
@extend %xxlarge;
|
||||||
@extend %title;
|
@extend %title;
|
||||||
//-display: flex; // Lo borro porque poner el flex a los h1 se carga los h1 que están centrados.
|
|
||||||
line-height: 3.4rem;
|
line-height: 3.4rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -233,19 +232,19 @@ a:visited {
|
||||||
content: 'D';
|
content: 'D';
|
||||||
}
|
}
|
||||||
.icon-caret-up:before {
|
.icon-caret-up:before {
|
||||||
content: 'F';
|
content: 'E';
|
||||||
}
|
}
|
||||||
.icon-caret-down:before {
|
.icon-caret-down:before {
|
||||||
content: 'G';
|
content: 'F';
|
||||||
}
|
}
|
||||||
.icon-bulk:before {
|
.icon-bulk:before {
|
||||||
content: 'H';
|
content: 'G';
|
||||||
}
|
}
|
||||||
.icon-idea:before {
|
.icon-idea:before {
|
||||||
content: 'I';
|
content: 'H';
|
||||||
}
|
}
|
||||||
.icon-spinner:before {
|
.icon-spinner:before {
|
||||||
content: 'E';
|
content: 'I';
|
||||||
}
|
}
|
||||||
.icon-minimize:before {
|
.icon-minimize:before {
|
||||||
content: 'J';
|
content: 'J';
|
||||||
|
@ -253,3 +252,7 @@ a:visited {
|
||||||
.icon-maximize:before {
|
.icon-maximize:before {
|
||||||
content: 'K';
|
content: 'K';
|
||||||
}
|
}
|
||||||
|
.icon-stats:before {
|
||||||
|
content: 'L';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -125,12 +125,10 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-with-actions {
|
.header-with-actions {
|
||||||
align-items: center;
|
@include table-flex(stretch, center, flex, row, wrap, space-between);
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
flex-shrink: 0;
|
@include flex-shrink(0);
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
@extend %bold;
|
@extend %bold;
|
||||||
}
|
}
|
||||||
.avatar {
|
.avatar {
|
||||||
align-items: center;
|
@include table-flex(stretch, center, flex, row, wrap, flex-start);
|
||||||
display: flex;
|
|
||||||
figcaption {
|
figcaption {
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
flex-basis: 35px;
|
@include table-flex-child(1, 35px, 0);
|
||||||
|
max-width: 35px;
|
||||||
}
|
}
|
||||||
.name,
|
.name,
|
||||||
.email {
|
.email {
|
||||||
|
@ -54,12 +54,11 @@
|
||||||
padding-right: 1rem;
|
padding-right: 1rem;
|
||||||
}
|
}
|
||||||
.row-status {
|
.row-status {
|
||||||
display: flex;
|
@include table-flex();
|
||||||
.delete {
|
.delete {
|
||||||
@extend %large;
|
@extend %large;
|
||||||
align-items: center;
|
@include table-flex(stretch, center, flex, row, wrap, flex-start);
|
||||||
color: $gray-light;
|
color: $gray-light;
|
||||||
display: flex;
|
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -79,13 +78,13 @@
|
||||||
.row-role,
|
.row-role,
|
||||||
.header-member,
|
.header-member,
|
||||||
.header-role {
|
.header-role {
|
||||||
flex: 3 0 50px;
|
@include table-flex-child(3, 35px, 0);
|
||||||
}
|
}
|
||||||
.row-status,
|
.row-status,
|
||||||
.row-admin,
|
.row-admin,
|
||||||
.header-admin,
|
.header-admin,
|
||||||
.header-status {
|
.header-status {
|
||||||
flex: 1 0 50px;
|
@include table-flex-child(1, 50px, 0);
|
||||||
}
|
}
|
||||||
.check {
|
.check {
|
||||||
background-color: darken($whitish, 10%);
|
background-color: darken($whitish, 10%);
|
||||||
|
|
|
@ -140,7 +140,7 @@
|
||||||
background: lighten($green-taiga, 60%);
|
background: lighten($green-taiga, 60%);
|
||||||
}
|
}
|
||||||
.user-story-name {
|
.user-story-name {
|
||||||
display: flex;
|
@include table-flex();
|
||||||
input {
|
input {
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
vertical-align: super;
|
vertical-align: super;
|
||||||
|
@ -171,6 +171,7 @@
|
||||||
}
|
}
|
||||||
.blocked {
|
.blocked {
|
||||||
background: $red-light;
|
background: $red-light;
|
||||||
|
border-bottom: 1px solid $white;
|
||||||
color: $white;
|
color: $white;
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $red;
|
background: $red;
|
||||||
|
@ -190,8 +191,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.doom-line {
|
.doom-line {
|
||||||
background: rgba($red, .5);
|
background: $red;
|
||||||
padding: .5rem 0;
|
margin: .2rem 0;
|
||||||
|
padding: .6rem 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
span {
|
span {
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
.lightbox,
|
.lightbox,
|
||||||
%lightbox {
|
%lightbox {
|
||||||
@include background-opacity($white, .95);
|
@include background-opacity($white, .95);
|
||||||
@include table-flex(center, center, flex, row, wrap, center);
|
|
||||||
@include transition (opacity .3s ease);
|
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
display: none;
|
display: none;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -17,6 +15,15 @@
|
||||||
right: 2rem;
|
right: 2rem;
|
||||||
top: 2rem;
|
top: 2rem;
|
||||||
}
|
}
|
||||||
|
&.open {
|
||||||
|
@include table-flex(center, center, flex, row, wrap, center);
|
||||||
|
@include transition (opacity .3s ease);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
&.close {
|
||||||
|
@include transition (opacity .3s ease);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
.title {
|
.title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
@ -34,10 +41,6 @@
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
&.open {
|
|
||||||
@include transition (opacity .3s ease);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-preview {
|
.markdown-preview {
|
||||||
|
@ -96,18 +99,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings {
|
.settings {
|
||||||
align-content: flex-start;
|
@include table-flex(flex-start, stretch, flex, row, wrap, flex-start);
|
||||||
align-items: stretch;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: flex-start;
|
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
flex-basis: 30%;
|
@include table-flex-child(1, 30%, 0);
|
||||||
flex-grow: 1;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-right: .5rem;
|
margin-right: .5rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
&:last-child {
|
&:last-child {
|
||||||
|
@ -231,6 +227,12 @@
|
||||||
}
|
}
|
||||||
.checksley-error-list {
|
.checksley-error-list {
|
||||||
right: .5rem;
|
right: .5rem;
|
||||||
|
li {
|
||||||
|
display: none;
|
||||||
|
&:first-child {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
.welcome-user {
|
.welcome-user {
|
||||||
display: flex;
|
@include table-flex();
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 1rem;
|
right: 1rem;
|
||||||
top: 1rem;
|
top: 1rem;
|
||||||
|
@ -93,6 +93,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.project-content {
|
||||||
|
h2 {
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
@extend %small;
|
||||||
|
line-height: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.all-projects {
|
.all-projects {
|
||||||
@include table-flex-child(1, 285px, 0, 285px);
|
@include table-flex-child(1, 285px, 0, 285px);
|
||||||
background-color: rgba(0, 0, 0, .5);
|
background-color: rgba(0, 0, 0, .5);
|
||||||
|
|
|
@ -33,21 +33,21 @@
|
||||||
border-bottom: 1px solid $gray-light;
|
border-bottom: 1px solid $gray-light;
|
||||||
}
|
}
|
||||||
.avatar {
|
.avatar {
|
||||||
align-items: center;
|
@include table-flex(stretch, center, flex, row, wrap, flex-start);
|
||||||
display: flex;
|
|
||||||
img {
|
img {
|
||||||
flex-basis: 35px;
|
width: 35px;
|
||||||
}
|
}
|
||||||
figcaption {
|
figcaption {
|
||||||
|
@include table-flex-child(1, 60%, 0);
|
||||||
margin-left: .5rem;
|
margin-left: .5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.level-field {
|
.level-field {
|
||||||
@include table-flex-child(1, 70px, 0, 70px);
|
@include table-flex-child(1, 70px, 0);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.subject {
|
.subject {
|
||||||
@include table-flex-child(7, 300px, 0, 300px);
|
@include table-flex-child(7, 300px, 0);
|
||||||
padding-right: 1rem;
|
padding-right: 1rem;
|
||||||
span {
|
span {
|
||||||
&:first-child {
|
&:first-child {
|
||||||
|
@ -58,13 +58,13 @@
|
||||||
.issue-field,
|
.issue-field,
|
||||||
.assigned-field,
|
.assigned-field,
|
||||||
.created-field {
|
.created-field {
|
||||||
@include table-flex-child(1, 100px, 0, 150px);
|
@include table-flex-child(1, 100px, 0);
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
.assigned-field {
|
.assigned-field {
|
||||||
@include table-flex-child(2, 100px, 0, 150px);
|
@include table-flex-child(2, 100px, 0);
|
||||||
}
|
}
|
||||||
.issue-assignedto {
|
.issue-assignedto {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -20,7 +20,7 @@ $column-margin: 0 10px 0 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
.task-colum_name {
|
.task-colum_name {
|
||||||
@include table-flex-child($column-flex, $column-width, $column-shrink, $column-width);
|
@include table-flex-child();
|
||||||
@extend %large;
|
@extend %large;
|
||||||
background: $whitish;
|
background: $whitish;
|
||||||
border-top: 3px solid $gray-light;
|
border-top: 3px solid $gray-light;
|
||||||
|
@ -60,7 +60,7 @@ $column-margin: 0 10px 0 0;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
.task-column {
|
.task-column {
|
||||||
@include table-flex-child($column-flex, $column-width, $column-shrink, $column-width);
|
@include table-flex-child();
|
||||||
margin: $column-margin;
|
margin: $column-margin;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
&:last-child {
|
&:last-child {
|
||||||
|
@ -80,5 +80,5 @@ $column-margin: 0 10px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kanban-table-inner {
|
.kanban-table-inner {
|
||||||
@include table-flex(stretch, stretch, flex, row, nowrap, flex-start);
|
@include table-flex($flex-wrap: nowrap);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,16 +14,16 @@
|
||||||
@include transition (background .2s ease-in);
|
@include transition (background .2s ease-in);
|
||||||
}
|
}
|
||||||
.user-stories {
|
.user-stories {
|
||||||
@include table-flex-child(5, auto, 1);
|
@include table-flex-child(5, 0, 1);
|
||||||
}
|
}
|
||||||
.status,
|
.status,
|
||||||
.points {
|
.points {
|
||||||
@include table-flex-child(0, 100px, 0);
|
@include table-flex-child(1, 100px, 0);
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.assigned-to {
|
.assigned-to {
|
||||||
@include table-flex-child(1, 250px, 0, 250px);
|
@include table-flex-child(1, 250px, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.row-selected {
|
.row-selected {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 134.2 134.3" preserveAspectRatio="xMidYMid meet">
|
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 134.2 134.3" preserveAspectRatio="xMidYMid meet">
|
||||||
<style>
|
<style>
|
||||||
.top {
|
.top {
|
||||||
transform-origin: 193px 164px;
|
transform-origin: 375px 532px;
|
||||||
-webkit-animation: rotate 4s cubic-bezier(.49,.05,.32,1.04) infinite alternate;
|
-webkit-animation: rotate 4s cubic-bezier(.49,.05,.32,1.04) infinite alternate;
|
||||||
-webkit-transform-origin: 50% 50%;
|
-webkit-transform-origin: 50% 50%;
|
||||||
animation: rotate 6s cubic-bezier(.49,.05,.32,1.04) infinite alternate;
|
animation: rotate 6s cubic-bezier(.49,.05,.32,1.04) infinite alternate;
|
||||||
|
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
@ -5,7 +5,7 @@
|
||||||
"duplicate-properties": false,
|
"duplicate-properties": false,
|
||||||
"font-sizes": false,
|
"font-sizes": false,
|
||||||
"ids": true,
|
"ids": true,
|
||||||
"known-properties": true,
|
"known-properties": false,
|
||||||
"overqualified-elements": true,
|
"overqualified-elements": true,
|
||||||
"shorthand": true,
|
"shorthand": true,
|
||||||
"text-indent": true,
|
"text-indent": true,
|
||||||
|
|
|
@ -12,11 +12,8 @@ minifyHTML = require("gulp-minify-html")
|
||||||
sass = require("gulp-ruby-sass")
|
sass = require("gulp-ruby-sass")
|
||||||
csslint = require("gulp-csslint")
|
csslint = require("gulp-csslint")
|
||||||
minifyCSS = require("gulp-minify-css")
|
minifyCSS = require("gulp-minify-css")
|
||||||
imagemin = require("gulp-imagemin")
|
|
||||||
watch = require("gulp-watch")
|
watch = require("gulp-watch")
|
||||||
size = require("gulp-filesize")
|
|
||||||
notify = require("gulp-notify")
|
notify = require("gulp-notify")
|
||||||
# connect = require("gulp-connect")
|
|
||||||
scsslint = require("gulp-scss-lint")
|
scsslint = require("gulp-scss-lint")
|
||||||
newer = require("gulp-newer")
|
newer = require("gulp-newer")
|
||||||
cache = require("gulp-cached")
|
cache = require("gulp-cached")
|
||||||
|
@ -137,12 +134,6 @@ gulp.task "css-lint-app", ["sass-watch"], ->
|
||||||
.pipe(csslint("csslintrc.json"))
|
.pipe(csslint("csslintrc.json"))
|
||||||
.pipe(csslint.reporter())
|
.pipe(csslint.reporter())
|
||||||
|
|
||||||
# gulp.task "imagemin", ->
|
|
||||||
# gulp.src(paths.images)
|
|
||||||
# .pipe(plumber())
|
|
||||||
# .pipe(imagemin({progressive: true}))
|
|
||||||
# .pipe(gulp.dest(paths.dist+"/images"))
|
|
||||||
|
|
||||||
gulp.task "styles-watch", ["sass-watch", "css-vendor", "css-lint-app"], ->
|
gulp.task "styles-watch", ["sass-watch", "css-vendor", "css-lint-app"], ->
|
||||||
gulp.src(paths.distStyles)
|
gulp.src(paths.distStyles)
|
||||||
.pipe(concat("main.css"))
|
.pipe(concat("main.css"))
|
||||||
|
|
14
package.json
14
package.json
|
@ -17,21 +17,17 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"coffee-script": "^1.7.1",
|
"coffee-script": "^1.8.0",
|
||||||
"express": "^4.8.5",
|
"express": "^4.9.5",
|
||||||
"gulp": "^3.8.7",
|
"gulp": "^3.8.8",
|
||||||
"gulp-cached": "0.0.3",
|
"gulp-cached": "0.0.3",
|
||||||
"gulp-changed": "^0.4.0",
|
"gulp-changed": "^0.4.0",
|
||||||
"gulp-clean": "^0.2.4",
|
"gulp-clean": "^0.2.4",
|
||||||
"gulp-coffee": "~1.4.1",
|
"gulp-coffee": "^2.2.0",
|
||||||
"gulp-coffeelint": "^0.2.2",
|
"gulp-coffeelint": "~0.4.0",
|
||||||
"gulp-compass": "^1.1.9",
|
|
||||||
"gulp-concat": "^2.1.7",
|
"gulp-concat": "^2.1.7",
|
||||||
"gulp-connect": "^2.0.5",
|
|
||||||
"gulp-csslint": "^0.1.5",
|
"gulp-csslint": "^0.1.5",
|
||||||
"gulp-filesize": "0.0.6",
|
|
||||||
"gulp-if": "0.0.5",
|
"gulp-if": "0.0.5",
|
||||||
"gulp-imagemin": "^0.5.0",
|
|
||||||
"gulp-jade": "^0.5.0",
|
"gulp-jade": "^0.5.0",
|
||||||
"gulp-jade-inheritance": "0.0.4",
|
"gulp-jade-inheritance": "0.0.4",
|
||||||
"gulp-minify-css": "^0.3.1",
|
"gulp-minify-css": "^0.3.1",
|
||||||
|
|
Loading…
Reference in New Issue