Merge branch 'master' into stable

stable
Jesús Espino 2014-10-07 15:45:35 +02:00
commit 9a53551b23
63 changed files with 774 additions and 286 deletions

10
CHANGELOG.md Normal file
View File

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

View File

@ -1,10 +1,8 @@
Taiga Front
===============
# Taiga Front #
![Kaleidos Project](http://kaleidos.net/static/img/badge.png "Kaleidos Project")
Setup initial environment
-------------------------
## Setup initial environment ##
Install requirements:
@ -13,21 +11,29 @@ Install requirements:
You can install Ruby through the apt package manager, rbenv, or rvm.
Install Sass through your **Terminal or Command Prompt**.
```bash
gem install sass
sass -v // should return Sass 3.3.8 (Maptastic Maple)
```
$ gem install sass scss-lint
$ 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**
```bash
sudo npm install -g gulp
npm install
sudo npm install -g bower
bower install
gulp
```
$ sudo npm install -g gulp
$ sudo npm install -g bower
$ npm install
$ bower install
$ gulp
```
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.

View File

@ -21,7 +21,21 @@
@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("/",
{templateUrl: "/partials/projects.html", resolve: {loader: tgLoaderProvider.add()}})
$routeProvider.when("/project/:pslug/",
@ -127,13 +141,18 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, tgLoade
defaultHeaders = {
"Content-Type": "application/json"
"Accept-Language": "en"
"X-Session-Id": taiga.sessionId
}
$httpProvider.defaults.headers.delete = defaultHeaders
$httpProvider.defaults.headers.patch = defaultHeaders
$httpProvider.defaults.headers.post = 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.
authHttpIntercept = ($q, $location, $confirm, $navUrls, $lightboxService) ->
@ -148,7 +167,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, tgLoade
$location.url($navUrls.resolve("login")).search("next=#{nextPath}")
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.interceptors.push('loaderInterceptor')
@ -166,10 +186,13 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, tgLoade
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"))
$log.debug("Initialize application")
if $auth.isAuthenticated()
$events.setupConnection()
# Default Value for taiga local config module.
angular.module("taigaLocalConfig", []).value("localconfig", {})
@ -181,6 +204,7 @@ modules = [
"taigaResources",
"taigaLocales",
"taigaAuth",
"taigaEvents",
# Specific Modules
"taigaRelatedTasks",
@ -196,6 +220,7 @@ modules = [
"taigaNavMenu",
"taigaProject",
"taigaUserSettings",
"taigaFeedback",
"taigaPlugins",
# Vendor modules
@ -211,6 +236,7 @@ module.config([
"$locationProvider",
"$httpProvider",
"$provide",
"$tgEventsProvider",
"tgLoaderProvider",
configure
])
@ -220,5 +246,7 @@ module.run([
"$tgI18n",
"$tgConfig",
"$rootScope",
"$tgAuth",
"$tgEvents",
init
])

View File

@ -38,6 +38,8 @@ class ConfigService extends taiga.Service
termsOfServiceUrl: null
privacyPolicyUrl: null
feedbackEnabled: true
}
initialize: (localconfig) ->

View File

@ -34,10 +34,10 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, lightboxService) ->
template = _.template("""
<div class="add-member-wrapper">
<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>
<select data-required="true">
<select <% if(required) { %> data-required="true" <% } %> data-required="true">
<% _.each(roleList, function(role) { %>
<option value="<%- role.id %>"><%- role.name %></option>
<% }); %>
@ -48,8 +48,8 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, lightboxService) ->
""") # i18n
link = ($scope, $el, $attrs) ->
createFieldSet = ->
ctx = {roleList: $scope.roles}
createFieldSet = (required = true)->
ctx = {roleList: $scope.roles, required: required}
return template(ctx)
resetForm = ->
@ -86,10 +86,11 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, lightboxService) ->
target.removeClass("icon-plus add-fieldset")
.addClass("icon-delete delete-fieldset")
newFieldSet = createFieldSet()
newFieldSet = createFieldSet(false)
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")
.addClass("icon-delete delete-fieldset")
@ -107,19 +108,31 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, lightboxService) ->
$rootScope.$broadcast("membersform:new:error")
form = $el.find("form").checksley()
#checksley find new fields
form.destroy()
form.initialize()
if not form.validate()
return
memberWrappers = $el.find("form > .add-member-wrapper")
memberWrappers = _.filter memberWrappers, (mw) ->
angular.element(mw).find("input").hasClass('checksley-ok')
invitations = _.map memberWrappers, (mw) ->
memberWrapper = angular.element(mw)
email = memberWrapper.find("input")
role = memberWrapper.find("select")
return {
email: memberWrapper.find("input").val()
role_id: memberWrapper.find("select").val()
email: email.val()
role_id: role.val()
}
$rs.memberships.bulkCreateMemberships($scope.project.id, invitations).then(onSuccess, onError)
if invitations.length
$rs.memberships.bulkCreateMemberships($scope.project.id, invitations).then(onSuccess, onError)
return {link: link}

View File

@ -241,12 +241,12 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm) ->
categories = []
milestonePermissions = [
{ key: "view_milestones", description: "View milestones" }
{ key: "add_milestone", description: "Add milestone" }
{ key: "modify_milestone", description: "Modify milestone" }
{ key: "delete_milestone", description: "Delete milestone" }
{ key: "view_milestones", description: "View sprints" }
{ key: "add_milestone", description: "Add sprint" }
{ key: "modify_milestone", description: "Modify sprint" }
{ key: "delete_milestone", description: "Delete sprint" }
]
categories.push({ name: "Milestones", permissions: setActivePermissions(milestonePermissions) })
categories.push({ name: "Sprints", permissions: setActivePermissions(milestonePermissions) })
userStoryPermissions = [
{ key: "view_us", description: "View user story" }

View File

@ -170,7 +170,7 @@ PublicRegisterMessageDirective = ($config, $navUrls) ->
module.directive("tgPublicRegisterMessage", ["$tgConfig", "$tgNavUrls", PublicRegisterMessageDirective])
LoginDirective = ($auth, $confirm, $location, $routeParams, $navUrls) ->
LoginDirective = ($auth, $confirm, $location, $config, $routeParams, $navUrls, $events) ->
link = ($scope, $el, $attrs) ->
$scope.data = {}
@ -180,6 +180,7 @@ LoginDirective = ($auth, $confirm, $location, $routeParams, $navUrls) ->
else
nextUrl = $navUrls.resolve("home")
$events.setupConnection()
$location.path(nextUrl)
onErrorSubmit = (response) ->
@ -203,8 +204,8 @@ LoginDirective = ($auth, $confirm, $location, $routeParams, $navUrls) ->
return {link:link}
module.directive("tgLogin", ["$tgAuth", "$tgConfirm", "$tgLocation", "$routeParams", "$tgNavUrls",
LoginDirective])
module.directive("tgLogin", ["$tgAuth", "$tgConfirm", "$tgLocation", "$tgConfig", "$routeParams",
"$tgNavUrls", "$tgEvents", LoginDirective])
#############################################################################
## Register Directive
@ -220,7 +221,7 @@ RegisterDirective = ($auth, $confirm, $location, $navUrls, $config) ->
form = $el.find("form").checksley()
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"))
onErrorSubmit = (response) ->
@ -307,7 +308,7 @@ ChangePasswordFromRecoveryDirective = ($auth, $confirm, $location, $params, $nav
onSuccessSubmit = (response) ->
$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
onErrorSubmit = (response) ->
@ -348,8 +349,8 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls) ->
promise.then null, (response) ->
$location.path($navUrls.resolve("login"))
$confirm.success("<strong>Ooops, we have a problems</strong><br />
Our Oompa Loompas can't find your invitations.") #TODO: i18n
$confirm.success("<strong>Ooops, we have a problem</strong><br />
Our Oompa Loompas can't find your invitation.") #TODO: i18n
# Login form
$scope.dataLogin = {token: token}
@ -357,12 +358,12 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls) ->
onSuccessSubmitLogin = (response) ->
$location.path($navUrls.resolve("project", {project: $scope.invitation.project_slug}))
$confirm.notify("success", "You've successfully joined to this project",
"Wellcome to #{$scope.invitation.project_name}")
$confirm.notify("success", "You've successfully joined this project",
"Welcome to #{$scope.invitation.project_name}")
onErrorSubmitLogin = (response) ->
$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 = ->
if not loginForm.validate()
@ -385,11 +386,11 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls) ->
onSuccessSubmitRegister = (response) ->
$location.path($navUrls.resolve("project", {project: $scope.invitation.project_slug}))
$confirm.notify("success", "You've successfully joined to this project",
"Wellcome to #{$scope.invitation.project_name}")
$confirm.notify("success", "You've successfully joined this project",
"Welcome to #{$scope.invitation.project_name}")
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
submitRegister = ->

View File

@ -46,11 +46,12 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
"$tgLocation",
"$appTitle",
"$tgNavUrls",
"$tgEvents",
"tgLoader"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @appTitle, @navUrls,
tgLoader) ->
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q,
@location, @appTitle, @navUrls, @events, tgLoader) ->
_.bindAll(@)
@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("usform:new: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:moved", @.loadSprints)
@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: ->
@scope.$apply () =>
@scope.$apply =>
@showTags = !@showTags
@rs.userstories.storeShowTags(@scope.projectId, @showTags)
@ -186,6 +199,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
# Resolve project slug
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project
@.initializeSubscription()
return data
return promise.then(=> @.loadProject())
@ -956,3 +970,52 @@ 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)

View File

@ -333,15 +333,19 @@ ListItemPriorityDirective = ->
"""
link = ($scope, $el, $attrs) ->
issue = $scope.$eval($attrs.tgListitemPriority)
bindOnce $scope, "priorityById", (priorityById) ->
render = (priorityById, issue) ->
priority = priorityById[issue.priority]
domNode = $el.find("div.level")
domNode = $el.find(".level")
domNode.css("background-color", priority.color)
domNode.addClass(priority.name.toLowerCase())
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 {
link: link
template: template
@ -354,15 +358,19 @@ ListItemSeverityDirective = ->
"""
link = ($scope, $el, $attrs) ->
issue = $scope.$eval($attrs.tgListitemSeverity)
bindOnce $scope, "severityById", (severityById) ->
render = (severityById, issue) ->
severity = severityById[issue.severity]
domNode = $el.find("div.level")
domNode = $el.find(".level")
domNode.css("background-color", severity.color)
domNode.addClass(severity.name.toLowerCase())
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 {
link: link
template: template
@ -374,15 +382,18 @@ ListItemTypeDirective = ->
"""
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) ->
type = issueTypeById[issue.type]
issue = $scope.$eval($attrs.tgListitemType)
render(issueTypeById, issue)
domNode = $el.find("div.level")
domNode.css("background-color", type.color)
domNode.addClass(type.name.toLowerCase())
domNode.attr("title", type.name)
$scope.$watch $attrs.tgListitemType, (issue) ->
render($scope.issueTypeById, issue)
return {
link: link

View File

@ -269,7 +269,7 @@ HistoryDirective = ($log, $loading) ->
team_requirement: "team requirement"
# Task
milestone: "spreint"
milestone: "sprint"
user_story: "user story"
is_iocaine: "is iocaine"

View File

@ -32,6 +32,7 @@ debounce = @.taiga.debounce
class LightboxService extends taiga.Service
open: ($el) ->
$el.css('display', 'flex')
$el.find('input,textarea').first().focus()
timeout(70, -> $el.addClass("open"))
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
$el.one "transitionend", =>
$el.css('display', 'none')
$el.removeAttr('style')
$el.removeClass("open").removeClass('close')
$el.removeClass("open")
$el.addClass('close')
closeAll: ->
docEl = angular.element(document)

View File

@ -42,9 +42,9 @@ UsStatusDirective = ($repo, popoverService) ->
NOTE: This directive need 'usStatusById' and 'project'.
###
selectionTemplate = _.template("""
template = _.template("""
<ul class="popover pop-status">
<% _.forEach(statuses, function(status) { %>
<% _.each(statuses, function(status) { %>
<li>
<a href="" class="status" title="<%- status.name %>" data-status-id="<%- status.id %>">
<%- status.name %>
@ -53,51 +53,56 @@ UsStatusDirective = ($repo, popoverService) ->
<% }); %>
</ul>""")
updateUsStatus = ($el, us, usStatusById) ->
usStatusDomParent = $el.find(".us-status")
usStatusDom = $el.find(".us-status .us-status-bind")
if usStatusById[us.status]
usStatusDom.text(usStatusById[us.status].name)
usStatusDomParent.prop("title", usStatusById[us.status].name)
usStatusDomParent.css('color', usStatusById[us.status].color)
link = ($scope, $el, $attrs) ->
$ctrl = $el.controller()
us = $scope.$eval($attrs.tgUsStatus)
render = (us) ->
usStatusDomParent = $el.find(".us-status")
usStatusDom = $el.find(".us-status .us-status-bind")
usStatusById = $scope.usStatusById
if usStatusById[us.status]
usStatusDom.text(usStatusById[us.status].name)
usStatusDomParent.css("color", usStatusById[us.status].color)
$el.on "click", ".us-status", (event) ->
event.preventDefault()
event.stopPropagation()
$el.find(".pop-status").popover().open()
# pop = $el.find(".pop-status")
# popoverService.open(pop)
$el.on "click", ".status", debounce 2000, (event) ->
event.preventDefault()
event.stopPropagation()
target = angular.element(event.currentTarget)
us = $scope.$eval($attrs.tgUsStatus)
us.status = target.data("status-id")
render(us)
$el.find(".pop-status").popover().close()
updateUsStatus($el, us, $scope.usStatusById)
$scope.$apply () ->
$repo.save(us).then ->
$scope.$eval($attrs.onUpdate)
taiga.bindOnce $scope, "project", (project) ->
$el.append(selectionTemplate({ 'statuses': project.us_statuses }))
updateUsStatus($el, us, $scope.usStatusById)
$scope.$on("userstories:loaded", -> render($scope.$eval($attrs.tgUsStatus)))
$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 project.my_permissions.indexOf("modify_us") == -1
if $scope.project.my_permissions.indexOf("modify_us") == -1
$el.unbind("click")
$el.find("a").addClass("not-clickable")
$scope.$on "$destroy", ->
$el.off()
return {link: link}

View File

@ -96,6 +96,7 @@ TagLineDirective = ($log, $rs) ->
template = """
<div class="tags-container"></div>
<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)
@ -137,6 +138,14 @@ TagLineDirective = ($log, $rs) ->
$scope.$apply ->
$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) ->
tags_colors = if $scope.project?.tags_colors? then $scope.project.tags_colors else []
renderTags($el, val, editable, tags_colors)
@ -171,13 +180,16 @@ TagLineDirective = ($log, $rs) ->
event.preventDefault()
$el.on "keyup", "input", (event) ->
return if event.keyCode != 13
event.preventDefault()
target = angular.element(event.currentTarget)
addValue(target.val())
target.val("")
$el.find("input").autocomplete("close")
if event.keyCode == 13
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) ->
event.preventDefault()

View File

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

View File

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

View File

@ -49,11 +49,12 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
"$tgLocation",
"$appTitle",
"$tgNavUrls",
"$tgEvents",
"tgLoader"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @urls, @params, @q, @location, @appTitle,
@navUrls, tgLoader) ->
@navUrls, @events, tgLoader) ->
@scope.sectionName = "Issues"
@scope.filters = {}
@ -82,6 +83,11 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
@.loadIssues()
@.loadFilters()
initializeSubscription: ->
routingKey = "changes.project.#{@scope.projectId}.issues"
@events.subscribe @scope, routingKey, (message) =>
@.loadIssues()
storeFilters: ->
@rs.issues.storeFilters(@params.pslug, @location.search())
@ -256,6 +262,7 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
loadInitialData: ->
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project
@.initializeSubscription()
return data
return promise.then(=> @.loadProject())
@ -755,6 +762,9 @@ IssueStatusInlineEditionDirective = ($repo, popoverService) ->
$el.unbind("click")
$el.find("a").addClass("not-clickable")
$scope.$watch $attrs.tgIssueStatusInlineEdition, (val) =>
updateIssueStatus($el, val, $scope.issueStatusById)
$scope.$on "$destroy", ->
$el.off()
@ -803,6 +813,9 @@ IssueAssignedToInlineEditionDirective = ($repo, $rootscope, popoverService) ->
$repo.save(updatedIssue)
updateIssue(updatedIssue)
$scope.$watch $attrs.tgIssueAssignedToInlineEdition, (val) =>
updateIssue(val)
$scope.$on "$destroy", ->
$el.off()

View File

@ -59,11 +59,12 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
"$tgLocation",
"$appTitle",
"$tgNavUrls",
"$tgEvents",
"tgLoader"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @appTitle, @navUrls,
tgLoader) ->
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
@appTitle, @navUrls, @events, tgLoader) ->
_.bindAll(@)
@scope.sectionName = "Kanban"
@scope.statusViewModes = {}
@ -162,10 +163,16 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
@scope.$emit("project:loaded", project)
return project
initializeSubscription: ->
routingKey1 = "changes.project.#{@scope.projectId}.userstories"
@events.subscribe @scope, routingKey1, (message) =>
@.loadUserstories()
loadInitialData: ->
# Resolve project slug
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project
@.initializeSubscription()
return data
return promise.then(=> @.loadProject())
@ -250,13 +257,16 @@ module.controller("KanbanController", KanbanController)
KanbanDirective = ($repo, $rootscope) ->
link = ($scope, $el, $attrs) ->
tableBodyDom = $el.find(".kanban-table-body")
tableBodyDom.on "scroll", (event) ->
target = angular.element(event.currentTarget)
tableHeaderDom = $el.find(".kanban-table-header .kanban-table-inner")
tableHeaderDom.css("left", -1 * target.scrollLeft())
$scope.$on "$destroy", ->
$el.off()
return {link: link}
module.directive("tgKanban", ["$tgRepo", "$rootScope", KanbanDirective])
@ -273,10 +283,14 @@ KanbanRowWidthFixerDirective = ->
size = (statuses.length * itemSize) - 10
$el.css("width", "#{size}px")
$scope.$on "$destroy", ->
$el.off()
return {link: link}
module.directive("tgKanbanRowWidthFixer", KanbanRowWidthFixerDirective)
#############################################################################
## Kanban Column Height Fixer Directive
#############################################################################
@ -294,6 +308,9 @@ KanbanColumnHeightFixerDirective = ->
link = ($scope, $el, $attrs) ->
timeout(500, -> renderSize($el))
$scope.$on "$destroy", ->
$el.off()
return {link:link}
@ -305,14 +322,23 @@ module.directive("tgKanbanColumnHeightFixer", KanbanColumnHeightFixerDirective)
KanbanUserstoryDirective = ($rootscope) ->
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) ->
if $el.find(".icon-edit").hasClass("noclick")
return
$scope.$apply ->
$rootscope.$broadcast("usform:edit", $model.$modelValue)
if $scope.us.is_blocked
$el.addClass("blocked")
$el.disableSelection()
$scope.$on "$destroy", ->
$el.off()
return {
templateUrl: "/partials/views/components/kanban-task.html"
@ -320,7 +346,6 @@ KanbanUserstoryDirective = ($rootscope) ->
require: "ngModel"
}
module.directive("tgKanbanUserstory", ["$rootScope", KanbanUserstoryDirective])
@ -344,6 +369,9 @@ KanbanWipLimitDirective = ->
$scope.$on "usform:new:success", redrawWipLimit
$scope.$on "usform:bulk:success", redrawWipLimit
$scope.$on "$destroy", ->
$el.off()
return {link: link}
module.directive("tgKanbanWipLimit", KanbanWipLimitDirective)
@ -405,7 +433,9 @@ KanbanUserDirective = ($log) ->
$ctrl = $el.controller()
$ctrl.changeUsAssignedTo(us)
$scope.$on "$destroy", ->
$el.off()
return {link: link, require:"ngModel"}
module.directive("tgKanbanUserAvatar", ["$log", KanbanUserDirective])

View File

@ -37,45 +37,49 @@ module = angular.module("taigaKanban")
KanbanSortableDirective = ($repo, $rs, $rootscope) ->
link = ($scope, $el, $attrs) ->
oldParentScope = null
newParentScope = null
itemEl = null
tdom = $el
bindOnce $scope, "project", (project) ->
if not (project.my_permissions.indexOf("modify_us") > -1)
return
deleteElement = (itemEl) ->
# Completelly remove item and its scope from dom
itemEl.scope().$destroy()
itemEl.off()
itemEl.remove()
oldParentScope = null
newParentScope = null
itemEl = null
tdom = $el
tdom.sortable({
handle: ".kanban-task-inner"
dropOnEmpty: true
connectWith: ".kanban-uses-box"
revert: 400
})
deleteElement = (itemEl) ->
# Completelly remove item and its scope from dom
itemEl.scope().$destroy()
itemEl.off()
itemEl.remove()
tdom.on "sortstop", (event, ui) ->
parentEl = ui.item.parent()
itemEl = ui.item
itemUs = itemEl.scope().us
itemIndex = itemEl.index()
newParentScope = parentEl.scope()
tdom.sortable({
handle: ".kanban-task-inner"
dropOnEmpty: true
connectWith: ".kanban-uses-box"
revert: 400
})
newStatusId = newParentScope.status.id
oldStatusId = oldParentScope.status.id
tdom.on "sortstop", (event, ui) ->
parentEl = ui.item.parent()
itemEl = ui.item
itemUs = itemEl.scope().us
itemIndex = itemEl.index()
newParentScope = parentEl.scope()
if newStatusId != oldStatusId
deleteElement(itemEl)
newStatusId = newParentScope.status.id
oldStatusId = oldParentScope.status.id
$scope.$apply ->
$rootscope.$broadcast("kanban:us:move", itemUs, newStatusId, itemIndex)
if newStatusId != oldStatusId
deleteElement(itemEl)
ui.item.find('a').removeClass('noclick')
$scope.$apply ->
$rootscope.$broadcast("kanban:us:move", itemUs, newStatusId, itemIndex)
tdom.on "sortstart", (event, ui) ->
oldParentScope = ui.item.parent().scope()
ui.item.find('a').addClass('noclick')
ui.item.find('a').removeClass('noclick')
tdom.on "sortstart", (event, ui) ->
oldParentScope = ui.item.parent().scope()
ui.item.find('a').addClass('noclick')
$scope.$on "$destroy", ->
$el.off()

View File

@ -59,7 +59,8 @@ class ProjectsNavigationController extends taiga.Controller
return projects
newProject: ->
@rootscope.$broadcast("projects:create")
@scope.$apply () =>
@rootscope.$broadcast("projects:create")
filterProjects: (text) ->
@scope.filteredProjects = _.filter @scope.projects, (project) ->
@ -200,7 +201,7 @@ module.directive("tgProjectsNav", ["$rootScope", "animationFrame", "$timeout", "
## Project
#############################################################################
ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $navUrls) ->
ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $navUrls, $config) ->
menuEntriesTemplate = _.template("""
<div class="menu-container">
<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="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>
<% if (feedbackEnabled) { %>
<li><a href="" class="feedback" title="Feedback"">Feedback</a></li>
<% } %>
<li><a href="" title="Logout" class="logout">Logout</a></li>
</ul>
<a href="" title="User preferences" class="avatar" id="nav-user-settings">
@ -275,8 +279,11 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $
mainTemplate = _.template("""
<div class="logo-container logo">
<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>
svg {
transform: scale(.99);
}
path {
fill:#f5f5f5;
opacity:0.7;
@ -322,7 +329,14 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $
renderMenuEntries = ($el, targetScope, project={}) ->
container = $el.find(".menu-container")
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("#nav-#{sectionName} > a").addClass("active")
@ -368,6 +382,10 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $
event.preventDefault()
$rootscope.$broadcast("search-box:show", project)
$el.on "click", ".feedback", (event) ->
event.preventDefault()
$rootscope.$broadcast("feedback:show")
$scope.$on "projects:loaded", (listener) ->
$el.addClass("hidden")
listener.stopPropagation()
@ -383,4 +401,4 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $
return {link: link}
module.directive("tgProjectMenu", ["$log", "$compile", "$tgAuth", "$rootScope", "$tgAuth", "$tgLocation",
"$tgNavUrls", ProjectMenuDirective])
"$tgNavUrls", "$tgConfig", ProjectMenuDirective])

View File

@ -67,8 +67,7 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
$scope.templates = result
$scope.data.creation_template = _.head(_.filter($scope.templates, (x) -> x.slug == "scrum")).id
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(".create-step1").addClass("active")

View File

@ -241,8 +241,7 @@ RelatedTaskCreateFormDirective = ($repo, $compile, $confirm, $tgmodel, $loading)
createTask(newTask).then ->
$el.html("")
$scope.$watch "us", (val) ->
return if not val
taiga.bindOnce $scope, "us", (val) ->
newTask["status"] = $scope.project.default_task_status
newTask["project"] = $scope.project.id
newTask["user_story"] = $scope.us.id
@ -299,8 +298,7 @@ RelatedTasksDirective = ($repo, $rs, $rootscope) ->
$scope.$on "related-tasks:add-new-clicked", ->
$scope.$broadcast("related-tasks:show-form")
$scope.$watch "us", (val) ->
return if not val
taiga.bindOnce $scope, "us", (val) ->
loadTasks()
$scope.$on "$destroy", ->

View File

@ -94,6 +94,9 @@ urls = {
"attachments/issue": "/api/v1/issues/attachments"
"attachments/task": "/api/v1/tasks/attachments"
"attachments/wiki_page": "/api/v1/wiki/attachments"
# Feedback
"feedback": "/api/v1/feedback"
}
# Initialize api urls service

View File

@ -46,11 +46,12 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
"$appTitle",
"$tgLocation",
"$tgNavUrls"
"$tgEvents"
"tgLoader"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @appTitle, @location, @navUrls,
tgLoader) ->
@events, tgLoader) ->
_.bindAll(@)
@scope.sectionName = "Taskboard"
@ -82,6 +83,17 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
promise.then null, ->
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: ->
return @rs.projects.get(@scope.projectId).then (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)
else
@scope.stats.completedPercentage = 0
@scope.stats.openTasks = stats.total_tasks - stats.completed_tasks
return stats
refreshTagsColors: ->
@ -157,6 +171,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
promise = @repo.resolve(params).then (data) =>
@scope.projectId = data.project
@scope.sprintId = data.milestone
@.initializeSubscription()
return data
return promise.then(=> @.loadProject())
@ -204,7 +219,8 @@ TaskboardDirective = ($rootscope) ->
$el.on "click", ".toggle-analytics-visibility", (event) ->
event.preventDefault()
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")
tableBodyDom = $el.find(".taskboard-table-body")

View File

@ -125,15 +125,6 @@ sizeFormat = (input, precision=1) ->
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.nl2br = nl2br
taiga.bindOnce = bindOnce
@ -151,5 +142,3 @@ taiga.joinStr = joinStr
taiga.debounce = debounce
taiga.startswith = startswith
taiga.sizeFormat = sizeFormat
taiga.typeIsArray = typeIsArray
taiga.generateHash = generateHash

View File

@ -12,6 +12,8 @@ config = {
publicRegisterEnabled: true
privacyPolicyUrl: null
termsOfServiceUrl: null
feedbackEnabled: true
}
angular.module("taigaLocalConfig", []).value("localconfig", config)

Binary file not shown.

View File

@ -37,11 +37,12 @@
<glyph unicode="&#66;" 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="&#67;" 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="&#68;" 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="&#70;" 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="&#71;" 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="&#72;" 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="&#73;" 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="&#69;" 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="&#69;" 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="&#70;" 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="&#71;" 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="&#72;" 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="&#73;" 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="&#74;" 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="&#75;" 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="&#76;" 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>

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

View File

@ -31,6 +31,8 @@ html(lang="en", ng-app="taiga")
include partials/views/modules/lightbox-generic-error
div.lightbox.lightbox-search(tg-search-box)
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

View File

@ -9,6 +9,6 @@ block content
div.login-container
img.logo-svg(src="/svg/logo.svg", alt="TAIGA")
h1.logo Taiga
h2.tagline ENJOY YOUR PROJECT,FINALLY
h2.tagline LOVE YOUR PROJECT
include views/modules/forgot-form

View File

@ -9,6 +9,6 @@ block content
div.login-container
img.logo-svg(src="/svg/logo.svg", alt="TAIGA")
h1.logo Taiga
h2.tagline ENJOY YOUR PROJECT,FINALLY
h2.tagline LOVE YOUR PROJECT
include views/modules/login-form

View File

@ -10,6 +10,6 @@ block content
object.logo-svg(type="image/svg+xml", data="/svg/logo.svg")
img(src="/images/logo.png", alt="TAIGA")
h1.logo Taiga
h2.tagline ENJOY YOUR PROJECT,FINALLY
h2.tagline LOVE YOUR PROJECT
include views/modules/register-form

View File

@ -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",
title="#{{ us.ref }} {{ us.subject }}")
span(tg-bo-ref="us.ref")
span(tg-bo-bind="us.subject")
span(ng-bind="us.subject")
div.us-settings
a.icon.icon-edit(tg-check-permission="modify_us", href="",
ng-click="ctrl.editUserStory(us)", title="Edit")

View File

@ -35,4 +35,4 @@ div.summary.large-summary
span.icon.icon-iocaine
span.number 10
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")

View File

@ -11,17 +11,11 @@ div.summary.large-summary
li
span.number(ng-bind="stats.completedPointsSum|default:'--'")
span.description completed<br />points
li
span.number(ng-bind="stats.remainingPointsSum|default:'--'")
span.description remaining<br />points
ul
li
span.icon.icon-bulk
span.number(ng-bind="stats.total_tasks|default:'--'")
span.description created<br />tasks
li
span.number(ng-bind="stats.remainingTasks|default:'--'")
span.number(ng-bind="stats.openTasks|default:'--'")
span.description open<br />tasks
li
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.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")

View File

@ -1,6 +1,6 @@
div.summary
div.summary-progress-bar(tg-progress-bar="stats.completedPercentage")
div.current-progress(style="width: {{stats.completedPercentage}}")
div.summary-progress-bar(tg-backlog-progress-bar="stats")
div.data
span.number(tg-bind-html="stats.completedPercentage + '%'")
ul
@ -10,9 +10,9 @@ div.summary
li
span.number(tg-bind-html="stats.defined_points") --
span.description defined<br />points
li
span.number(tg-bind-html="stats.assigned_points") --
span.description assigned<br />points
li
span.number(tg-bind-html="stats.closed_points") --
span.description closed<br />points
li
span.number(tg-bind-html="stats.speed | number:0") --
span.description points /<br />sprint

View File

@ -1,6 +1,6 @@
section.admin-submenu
header
h1 Custom Atrributes
h1 Custom Attributes
nav
ul

View File

@ -14,7 +14,7 @@ section.issues-table.basic-table(ng-class="{empty: !issues.length}")
div.subject
a(href="", tg-nav="project-issues-detail:project=project.slug,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")
a.issue-status(href="", title="Change status")

View File

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

View File

@ -8,4 +8,4 @@ form
a.button.button-green(href="", title="Save")
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.

View File

@ -3,7 +3,7 @@
include ../components/spinner
p Loading project...
div.wizard-create-project.hidden(tg-lb-create-project)
div.wizard-create-project(tg-lb-create-project)
include wizard-create-project
nav.projects-nav(ng-controller="ProjectsNavigationController", tg-projects-nav, tg-projects-pagination, projects="projects")

View File

@ -14,5 +14,5 @@ section.admin-menu
span.icon.icon-arrow-right
li#usersettingsmenu-mail-notifications
a(href="", tg-nav="user-settings-mail-notifications:project=project.slug")
span.title Email notificiations
span.title Email notifications
span.icon.icon-arrow-right

View File

@ -1,27 +1,15 @@
.basic-table {
align-content: stretch;
align-items: center;
display: flex;
flex-direction: column;
flex-wrap: wrap;
justify-content: flex-start;
@include table-flex(stretch, center, flex, column, wrap, flex-start);
width: 100%;
.row {
align-content: stretch;
align-items: center;
@include table-flex(stretch, center, flex, row, nowrap, flex-start);
border-bottom: 1px solid $gray-light;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
padding: .5rem 0;
text-align: left;
width: 100%;
@for $i from 1 through 8 {
.width-#{$i} {
flex-basis: 50px;
flex-grow: $i;
flex-shrink: 0;
@include table-flex-child($i, 50px, 0);
}
}
&:last-child {

View File

@ -15,7 +15,7 @@
}
.data {
float: left;
margin-right: 1.5em;
margin-right: 1em;
margin-top: 4px;
.number {
color: $fresh-taiga;
@ -53,11 +53,26 @@
margin-right: 10px;
padding: 3px;
position: relative;
width: 20%;
width: 15%;
.current-progress {
background: $fresh-taiga;
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;
margin-right: 1rem;
vertical-align: top;
&:last-child {
&:last-of-type {
border: 0;
margin: 0;
}
@ -75,8 +90,22 @@
.icon {
@extend %xlarge;
margin-right: .4rem;
}
.button {
color: $white;
&.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;
}
}
}
}
}

View File

@ -42,4 +42,7 @@
margin: 0 .5rem .5rem 0;
padding: .5rem;
}
.save {
display: none;
}
}

View File

@ -1,4 +1,5 @@
.wysiwyg {
overflow-x: scroll;
h1 {
@extend %xlarge;
@extend %text;

View File

@ -75,6 +75,11 @@ ol { list-style: decimal; }
.pika-button {
color: $green-taiga;
}
&.is-selected {
button {
color: $white;
}
}
}
.pika-button {
&:hover {

View File

@ -11,12 +11,12 @@
// 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) {
align-content: $align-content; // flex-start, flex-end, center, space-between, space-around, stretch
align-items: $align-items; //flex-start, flex-end, center, baseline, stretch
display: $display;
flex-direction: $flex-direction; //row | row-reverse | column | column-reverse
flex-wrap: $flex-wrap; // nowrap | wrap | wrap-reverse
justify-content: $justify-content; //flex-start | flex-end | center | space-between | space-around
@include display($display);
@include align-content($align-content);
@include align-items($align-items);
@include flex-direction($flex-direction);
@include flex-wrap($flex-wrap);
@include justify-content($justify-content);
}
@mixin table-flex-child($flex-grow: 1, $flex-basis: 300px, $flex-shrink: 0, $width:'') {

View File

@ -43,7 +43,6 @@ h6 {
h1 {
@extend %xxlarge;
@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;
margin-bottom: 1rem;
text-transform: uppercase;
@ -233,19 +232,19 @@ a:visited {
content: 'D';
}
.icon-caret-up:before {
content: 'F';
content: 'E';
}
.icon-caret-down:before {
content: 'G';
content: 'F';
}
.icon-bulk:before {
content: 'H';
content: 'G';
}
.icon-idea:before {
content: 'I';
content: 'H';
}
.icon-spinner:before {
content: 'E';
content: 'I';
}
.icon-minimize:before {
content: 'J';
@ -253,3 +252,7 @@ a:visited {
.icon-maximize:before {
content: 'K';
}
.icon-stats:before {
content: 'L';
}

View File

@ -125,12 +125,10 @@ body {
}
.header-with-actions {
align-items: center;
display: flex;
justify-content: space-between;
@include table-flex(stretch, center, flex, row, wrap, space-between);
margin-bottom: 1rem;
.action-buttons {
flex-shrink: 0;
@include flex-shrink(0);
padding-left: 1rem;
}
h1 {

View File

@ -3,13 +3,13 @@
@extend %bold;
}
.avatar {
align-items: center;
display: flex;
@include table-flex(stretch, center, flex, row, wrap, flex-start);
figcaption {
margin-left: 1rem;
}
img {
flex-basis: 35px;
@include table-flex-child(1, 35px, 0);
max-width: 35px;
}
.name,
.email {
@ -54,12 +54,11 @@
padding-right: 1rem;
}
.row-status {
display: flex;
@include table-flex();
.delete {
@extend %large;
align-items: center;
@include table-flex(stretch, center, flex, row, wrap, flex-start);
color: $gray-light;
display: flex;
margin-left: 15px;
padding: 0 15px;
&:hover {
@ -79,13 +78,13 @@
.row-role,
.header-member,
.header-role {
flex: 3 0 50px;
@include table-flex-child(3, 35px, 0);
}
.row-status,
.row-admin,
.header-admin,
.header-status {
flex: 1 0 50px;
@include table-flex-child(1, 50px, 0);
}
.check {
background-color: darken($whitish, 10%);

View File

@ -140,7 +140,7 @@
background: lighten($green-taiga, 60%);
}
.user-story-name {
display: flex;
@include table-flex();
input {
margin-right: 1rem;
vertical-align: super;
@ -171,6 +171,7 @@
}
.blocked {
background: $red-light;
border-bottom: 1px solid $white;
color: $white;
&:hover {
background: $red;
@ -190,8 +191,9 @@
}
}
.doom-line {
background: rgba($red, .5);
padding: .5rem 0;
background: $red;
margin: .2rem 0;
padding: .6rem 0;
position: relative;
width: 100%;
span {

View File

@ -1,8 +1,6 @@
.lightbox,
%lightbox {
@include background-opacity($white, .95);
@include table-flex(center, center, flex, row, wrap, center);
@include transition (opacity .3s ease);
bottom: 0;
display: none;
left: 0;
@ -17,6 +15,15 @@
right: 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 {
text-align: center;
}
@ -34,10 +41,6 @@
padding: 12px;
text-align: center;
}
&.open {
@include transition (opacity .3s ease);
opacity: 1;
}
}
.markdown-preview {
@ -96,18 +99,11 @@
}
.settings {
align-content: flex-start;
align-items: stretch;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
@include table-flex(flex-start, stretch, flex, row, wrap, flex-start);
margin-bottom: 1rem;
fieldset {
flex-basis: 30%;
flex-grow: 1;
flex-shrink: 0;
@include table-flex-child(1, 30%, 0);
margin-right: .5rem;
text-align: center;
&:last-child {
@ -231,6 +227,12 @@
}
.checksley-error-list {
right: .5rem;
li {
display: none;
&:first-child {
display: block;
}
}
}
}

View File

@ -12,7 +12,7 @@
top: 0;
width: 100%;
.welcome-user {
display: flex;
@include table-flex();
position: absolute;
right: 1rem;
top: 1rem;
@ -93,6 +93,16 @@
}
}
.project-content {
h2 {
margin-bottom: .5rem;
}
p {
@extend %small;
line-height: 1rem;
}
}
.all-projects {
@include table-flex-child(1, 285px, 0, 285px);
background-color: rgba(0, 0, 0, .5);

View File

@ -33,21 +33,21 @@
border-bottom: 1px solid $gray-light;
}
.avatar {
align-items: center;
display: flex;
@include table-flex(stretch, center, flex, row, wrap, flex-start);
img {
flex-basis: 35px;
width: 35px;
}
figcaption {
@include table-flex-child(1, 60%, 0);
margin-left: .5rem;
}
}
.level-field {
@include table-flex-child(1, 70px, 0, 70px);
@include table-flex-child(1, 70px, 0);
text-align: center;
}
.subject {
@include table-flex-child(7, 300px, 0, 300px);
@include table-flex-child(7, 300px, 0);
padding-right: 1rem;
span {
&:first-child {
@ -58,13 +58,13 @@
.issue-field,
.assigned-field,
.created-field {
@include table-flex-child(1, 100px, 0, 150px);
@include table-flex-child(1, 100px, 0);
padding: 0 1rem;
position: relative;
text-align: left;
}
.assigned-field {
@include table-flex-child(2, 100px, 0, 150px);
@include table-flex-child(2, 100px, 0);
}
.issue-assignedto {
cursor: pointer;

View File

@ -20,7 +20,7 @@ $column-margin: 0 10px 0 0;
position: absolute;
}
.task-colum_name {
@include table-flex-child($column-flex, $column-width, $column-shrink, $column-width);
@include table-flex-child();
@extend %large;
background: $whitish;
border-top: 3px solid $gray-light;
@ -60,7 +60,7 @@ $column-margin: 0 10px 0 0;
overflow-x: auto;
width: 100%;
.task-column {
@include table-flex-child($column-flex, $column-width, $column-shrink, $column-width);
@include table-flex-child();
margin: $column-margin;
overflow-y: auto;
&:last-child {
@ -80,5 +80,5 @@ $column-margin: 0 10px 0 0;
}
.kanban-table-inner {
@include table-flex(stretch, stretch, flex, row, nowrap, flex-start);
@include table-flex($flex-wrap: nowrap);
}

View File

@ -14,16 +14,16 @@
@include transition (background .2s ease-in);
}
.user-stories {
@include table-flex-child(5, auto, 1);
@include table-flex-child(5, 0, 1);
}
.status,
.points {
@include table-flex-child(0, 100px, 0);
@include table-flex-child(1, 100px, 0);
padding: 0 1rem;
text-align: center;
}
.assigned-to {
@include table-flex-child(1, 250px, 0, 250px);
@include table-flex-child(1, 250px, 0);
}
}
.row-selected {

View File

@ -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">
<style>
.top {
transform-origin: 193px 164px;
transform-origin: 375px 532px;
-webkit-animation: rotate 4s cubic-bezier(.49,.05,.32,1.04) infinite alternate;
-webkit-transform-origin: 50% 50%;
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

View File

@ -5,7 +5,7 @@
"duplicate-properties": false,
"font-sizes": false,
"ids": true,
"known-properties": true,
"known-properties": false,
"overqualified-elements": true,
"shorthand": true,
"text-indent": true,

View File

@ -12,11 +12,8 @@ minifyHTML = require("gulp-minify-html")
sass = require("gulp-ruby-sass")
csslint = require("gulp-csslint")
minifyCSS = require("gulp-minify-css")
imagemin = require("gulp-imagemin")
watch = require("gulp-watch")
size = require("gulp-filesize")
notify = require("gulp-notify")
# connect = require("gulp-connect")
scsslint = require("gulp-scss-lint")
newer = require("gulp-newer")
cache = require("gulp-cached")
@ -137,12 +134,6 @@ gulp.task "css-lint-app", ["sass-watch"], ->
.pipe(csslint("csslintrc.json"))
.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.src(paths.distStyles)
.pipe(concat("main.css"))

View File

@ -17,21 +17,17 @@
}
],
"devDependencies": {
"coffee-script": "^1.7.1",
"express": "^4.8.5",
"gulp": "^3.8.7",
"coffee-script": "^1.8.0",
"express": "^4.9.5",
"gulp": "^3.8.8",
"gulp-cached": "0.0.3",
"gulp-changed": "^0.4.0",
"gulp-clean": "^0.2.4",
"gulp-coffee": "~1.4.1",
"gulp-coffeelint": "^0.2.2",
"gulp-compass": "^1.1.9",
"gulp-coffee": "^2.2.0",
"gulp-coffeelint": "~0.4.0",
"gulp-concat": "^2.1.7",
"gulp-connect": "^2.0.5",
"gulp-csslint": "^0.1.5",
"gulp-filesize": "0.0.6",
"gulp-if": "0.0.5",
"gulp-imagemin": "^0.5.0",
"gulp-jade": "^0.5.0",
"gulp-jade-inheritance": "0.0.4",
"gulp-minify-css": "^0.3.1",