Merge branch 'master' into stable

stable
Alejandro Alonso 2015-06-18 12:10:42 +02:00
commit 06957a1544
317 changed files with 18947 additions and 17426 deletions

1
.gitignore vendored
View File

@ -8,6 +8,7 @@ bower_components
app/coffee/modules/locales/locale*.coffee
*.swp
*.swo
.#*
tags
tmp/
app/config/main.coffee

14
.travis.yml Normal file
View File

@ -0,0 +1,14 @@
before_install:
- export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- npm install -g bower
- npm install -g gulp
install:
- npm install
- bower install
before_script:
- gulp deploy
language: node_js
node_js:
- "0.12"

View File

@ -26,3 +26,4 @@ answer newbie questions, and generally made Taiga that much better:
- Daniel Koch
- Florian Bezagu
- Ryan Swanstrom
- Chris Wilson <chris.wilson@aridhia.com>

View File

@ -1,7 +1,45 @@
# Changelog #
## 1.7.0 Empetrum Nigrum (unreleased)
## 1.8.0 Saracenia Purpurea (2015-06-18)
### Features
- Menus
- New User menu
- New project menu design
- Home
- Change home page for logged users, show a user dashboard with `working on` and `watching` sections.
- Proyects privacity
- Enabled public projects
- Improve SEO, fix meta tags and added social meta tags
- About project detail
- New projects list design
- New project detail page design
- Add project timeline
- User profile
- Now, access to edit user settings is out of a project
- New User profile view
- Add activity timeline to user profiles
- With the activity of my contacts on mine
- With the activity of the user on others
- Add user contacts to user profile
- Add project list to user profile
- Backlog panel
- Improve the drag & drop behavior of USs in backlog panel
- Select multiple US with `shift` in the backlog panel
- Global searches:
- Show the reference of entities in search results (thanks to [@artlepool](https://github.com/artlepool))
- Autofocus on search modal
- i18n.
- Add deutsch (de) translation.
- Add nederlands (nl) translation.
### Misc
- Improve performance: remove some unnecessary calls to the api.
- Lots of small and not so small bugfixes.
## 1.7.0 Empetrum Nigrum (2015-05-21)
### Features
- Make Taiga translatable (i18n support).

View File

@ -2,6 +2,7 @@
![Kaleidos Project](http://kaleidos.net/static/img/badge.png "Kaleidos Project")
[![Managed with Taiga](https://taiga.io/media/support/attachments/article-22/banner-gh.png)](https://taiga.io "Managed with Taiga")
[![Build Status](https://travis-ci.org/taigaio/taiga-front.svg?branch=public-header-bar)](https://travis-ci.org/taigaio/taiga-front)
## Get the compiled version ##

View File

@ -28,129 +28,348 @@ 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,
configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEventsProvider,
$compileProvider, $translateProvider) ->
# wait until the trasnlation is ready to resolve the page
originalWhen = $routeProvider.when
$routeProvider.when = (path, route) ->
route.resolve || (route.resolve = {})
angular.extend(route.resolve, {
languageLoad: ["$q", "$translate", ($q, $translate) ->
deferred = $q.defer()
$translate().then () -> deferred.resolve()
return deferred.promise
]
})
return originalWhen.call($routeProvider, path, route)
$routeProvider.when("/",
{templateUrl: "project/projects.html"})
{
templateUrl: "home/home.html",
access: {
requiresLogin: true
},
title: "HOME.PAGE_TITLE",
description: "HOME.PAGE_DESCRIPTION",
loader: true
}
)
$routeProvider.when("/projects/",
{
templateUrl: "projects/listing/projects-listing.html",
access: {
requiresLogin: true
},
title: "PROJECTS.PAGE_TITLE",
description: "PROJECTS.PAGE_DESCRIPTION",
loader: true,
controller: "ProjectsListing",
controllerAs: "vm"
}
)
$routeProvider.when("/project/:pslug/",
{templateUrl: "project/project.html"})
{
templateUrl: "projects/project/project.html",
loader: true,
controller: "Project",
controllerAs: "vm"
section: "project-timeline"
}
)
$routeProvider.when("/project/:pslug/search",
{templateUrl: "search/search.html", reloadOnSearch: false})
{
templateUrl: "search/search.html",
reloadOnSearch: false,
section: "search"
}
)
$routeProvider.when("/project/:pslug/backlog",
{templateUrl: "backlog/backlog.html", resolve: {loader: tgLoaderProvider.add()}})
{
templateUrl: "backlog/backlog.html",
loader: true,
section: "backlog"
}
)
$routeProvider.when("/project/:pslug/kanban",
{templateUrl: "kanban/kanban.html", resolve: {loader: tgLoaderProvider.add()}})
{
templateUrl: "kanban/kanban.html",
loader: true,
section: "kanban"
}
)
# Milestone
$routeProvider.when("/project/:pslug/taskboard/:sslug",
{templateUrl: "taskboard/taskboard.html", resolve: {loader: tgLoaderProvider.add()}})
{
templateUrl: "taskboard/taskboard.html",
loader: true,
section: "backlog"
}
)
# User stories
$routeProvider.when("/project/:pslug/us/:usref",
{templateUrl: "us/us-detail.html", resolve: {loader: tgLoaderProvider.add()}})
{
templateUrl: "us/us-detail.html",
loader: true,
section: "backlog-kanban"
}
)
# Tasks
$routeProvider.when("/project/:pslug/task/:taskref",
{templateUrl: "task/task-detail.html", resolve: {loader: tgLoaderProvider.add()}})
{
templateUrl: "task/task-detail.html",
loader: true,
section: "backlog-kanban"
}
)
# Wiki
$routeProvider.when("/project/:pslug/wiki",
{redirectTo: (params) -> "/project/#{params.pslug}/wiki/home"}, )
$routeProvider.when("/project/:pslug/wiki/:slug",
{templateUrl: "wiki/wiki.html", resolve: {loader: tgLoaderProvider.add()}})
{
templateUrl: "wiki/wiki.html",
loader: true,
section: "wiki"
}
)
# Team
$routeProvider.when("/project/:pslug/team",
{templateUrl: "team/team.html", resolve: {loader: tgLoaderProvider.add()}})
{
templateUrl: "team/team.html",
loader: true,
section: "team"
}
)
# Issues
$routeProvider.when("/project/:pslug/issues",
{templateUrl: "issue/issues.html", resolve: {loader: tgLoaderProvider.add()}})
{
templateUrl: "issue/issues.html",
loader: true,
section: "issues"
}
)
$routeProvider.when("/project/:pslug/issue/:issueref",
{templateUrl: "issue/issues-detail.html", resolve: {loader: tgLoaderProvider.add()}})
{
templateUrl: "issue/issues-detail.html",
loader: true,
section: "issues"
}
)
# Admin - Project Profile
$routeProvider.when("/project/:pslug/admin/project-profile/details",
{templateUrl: "admin/admin-project-profile.html"})
{
templateUrl: "admin/admin-project-profile.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-profile/default-values",
{templateUrl: "admin/admin-project-default-values.html"})
{
templateUrl: "admin/admin-project-default-values.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-profile/modules",
{templateUrl: "admin/admin-project-modules.html"})
{
templateUrl: "admin/admin-project-modules.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-profile/export",
{templateUrl: "admin/admin-project-export.html"})
{
templateUrl: "admin/admin-project-export.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-profile/reports",
{templateUrl: "admin/admin-project-reports.html"})
{
templateUrl: "admin/admin-project-reports.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-values/status",
{templateUrl: "admin/admin-project-values-status.html"})
{
templateUrl: "admin/admin-project-values-status.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-values/points",
{templateUrl: "admin/admin-project-values-points.html"})
{
templateUrl: "admin/admin-project-values-points.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-values/priorities",
{templateUrl: "admin/admin-project-values-priorities.html"})
{
templateUrl: "admin/admin-project-values-priorities.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-values/severities",
{templateUrl: "admin/admin-project-values-severities.html"})
{
templateUrl: "admin/admin-project-values-severities.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-values/types",
{templateUrl: "admin/admin-project-values-types.html"})
{
templateUrl: "admin/admin-project-values-types.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-values/custom-fields",
{templateUrl: "admin/admin-project-values-custom-fields.html"})
{
templateUrl: "admin/admin-project-values-custom-fields.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/memberships",
{templateUrl: "admin/admin-memberships.html"})
{
templateUrl: "admin/admin-memberships.html",
section: "admin"
}
)
# Admin - Roles
$routeProvider.when("/project/:pslug/admin/roles",
{templateUrl: "admin/admin-roles.html"})
{
templateUrl: "admin/admin-roles.html",
section: "admin"
}
)
# Admin - Third Parties
$routeProvider.when("/project/:pslug/admin/third-parties/webhooks",
{templateUrl: "admin/admin-third-parties-webhooks.html"})
{
templateUrl: "admin/admin-third-parties-webhooks.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/third-parties/github",
{templateUrl: "admin/admin-third-parties-github.html"})
{
templateUrl: "admin/admin-third-parties-github.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/third-parties/gitlab",
{templateUrl: "admin/admin-third-parties-gitlab.html"})
{
templateUrl: "admin/admin-third-parties-gitlab.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/third-parties/bitbucket",
{templateUrl: "admin/admin-third-parties-bitbucket.html"})
{
templateUrl: "admin/admin-third-parties-bitbucket.html",
section: "admin"
}
)
# Admin - Contrib Plugins
$routeProvider.when("/project/:pslug/admin/contrib/:plugin",
{templateUrl: "contrib/main.html"})
# User settings
$routeProvider.when("/project/:pslug/user-settings/user-profile",
$routeProvider.when("/user-settings/user-profile",
{templateUrl: "user/user-profile.html"})
$routeProvider.when("/project/:pslug/user-settings/user-change-password",
$routeProvider.when("/user-settings/user-change-password",
{templateUrl: "user/user-change-password.html"})
$routeProvider.when("/project/:pslug/user-settings/user-avatar",
{templateUrl: "user/user-avatar.html"})
$routeProvider.when("/project/:pslug/user-settings/mail-notifications",
$routeProvider.when("/user-settings/mail-notifications",
{templateUrl: "user/mail-notifications.html"})
$routeProvider.when("/change-email/:email_token",
{templateUrl: "user/change-email.html"})
$routeProvider.when("/cancel-account/:cancel_token",
{templateUrl: "user/cancel-account.html"})
# User profile
$routeProvider.when("/profile",
{
templateUrl: "profile/profile.html",
loader: true,
access: {
requiresLogin: true
},
controller: "Profile",
controllerAs: "vm"
}
)
$routeProvider.when("/profile/:slug",
{
templateUrl: "profile/profile.html",
loader: true,
controller: "Profile",
controllerAs: "vm"
}
)
# Auth
$routeProvider.when("/login",
{templateUrl: "auth/login.html"})
{
templateUrl: "auth/login.html",
title: "LOGIN.PAGE_TITLE"
description: "LOGIN.PAGE_DESCRIPTION"
}
)
$routeProvider.when("/register",
{templateUrl: "auth/register.html"})
{
templateUrl: "auth/register.html",
title: "REGISTER.PAGE_TITLE",
description: "REGISTER.PAGE_DESCRIPTION"
}
)
$routeProvider.when("/forgot-password",
{templateUrl: "auth/forgot-password.html"})
{
templateUrl: "auth/forgot-password.html",
title: "FORGOT_PASSWORD.PAGE_TITLE",
description: "FORGOT_PASSWORD.PAGE_DESCRIPTION"
}
)
$routeProvider.when("/change-password",
{templateUrl: "auth/change-password-from-recovery.html"})
{
templateUrl: "auth/change-password-from-recovery.html",
title: "CHANGE_PASSWORD.PAGE_TITLE",
description: "CHANGE_PASSWORD.PAGE_TITLE",
}
)
$routeProvider.when("/change-password/:token",
{templateUrl: "auth/change-password-from-recovery.html"})
{
templateUrl: "auth/change-password-from-recovery.html",
title: "CHANGE_PASSWORD.PAGE_TITLE",
description: "CHANGE_PASSWORD.PAGE_TITLE",
}
)
$routeProvider.when("/invitation/:token",
{templateUrl: "auth/invitation.html"})
{
templateUrl: "auth/invitation.html",
title: "INVITATION.PAGE_TITLE",
description: "INVITATION.PAGE_DESCRIPTION"
}
)
# Errors/Exceptions
$routeProvider.when("/error",
@ -177,6 +396,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
"X-Session-Id": taiga.sessionId
}
$httpProvider.useApplyAsync(true)
$tgEventsProvider.setSessionId(taiga.sessionId)
# Add next param when user try to access to a secction need auth permissions.
@ -201,6 +422,35 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
$httpProvider.interceptors.push("authHttpIntercept")
loaderIntercept = ($q, loaderService) ->
return {
request: (config) ->
loaderService.logRequest()
return config
requestError: (rejection) ->
loaderService.logResponse()
return $q.reject(rejection)
responseError: (rejection) ->
loaderService.logResponse()
return $q.reject(rejection)
response: (response) ->
loaderService.logResponse()
return response
}
$provide.factory("loaderIntercept", ["$q", "tgLoader", loaderIntercept])
$httpProvider.interceptors.push("loaderIntercept")
# If there is an error in the version throw a notify error.
# IMPROVEiMENT: Move this version error handler to USs, issues and tasks repository
versionCheckHttpIntercept = ($q) ->
@ -286,7 +536,7 @@ i18nInit = (lang, $translate) ->
checksley.updateMessages('default', messages)
init = ($log, $config, $rootscope, $auth, $events, $analytics, $translate) ->
init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $navUrls, appMetaService, projectService, loaderService) ->
$log.debug("Initialize application")
# Taiga Plugins
@ -297,6 +547,10 @@ init = ($log, $config, $rootscope, $auth, $events, $analytics, $translate) ->
lang = ctx.language
i18nInit(lang, $translate)
# bluebird
Promise.setScheduler (cb) ->
$rootscope.$evalAsync(cb)
# Load user
if $auth.isAuthenticated()
$events.setupConnection()
@ -305,16 +559,50 @@ init = ($log, $config, $rootscope, $auth, $events, $analytics, $translate) ->
# Analytics
$analytics.initialize()
# On the first page load the loader is painted in `$routeChangeSuccess`
# because we need to hide the tg-navigation-bar.
# In the other cases the loader is in `$routeChangeSuccess`
# because `location.noreload` prevent to execute this event.
un = $rootscope.$on '$routeChangeStart', (event, next) ->
if next.loader
loaderService.start(true)
un()
$rootscope.$on '$routeChangeSuccess', (event, next) ->
if next.loader
loaderService.start(true)
if next.access && next.access.requiresLogin
if !$auth.isAuthenticated()
$location.path($navUrls.resolve("login"))
projectService.setSection(next.section)
if next.params.pslug
projectService.setProject(next.params.pslug)
else
projectService.cleanProject()
if next.title or next.description
title = $translate.instant(next.title or "")
description = $translate.instant(next.description or "")
appMetaService.setAll(title, description)
modules = [
# Main Global Modules
"taigaBase",
"taigaCommon",
"taigaResources",
"taigaResources2",
"taigaAuth",
"taigaEvents",
# Specific Modules
"taigaHome",
"taigaNavigationBar",
"taigaProjects",
"taigaRelatedTasks",
"taigaBacklog",
"taigaTaskboard",
@ -326,13 +614,16 @@ modules = [
"taigaWiki",
"taigaSearch",
"taigaAdmin",
"taigaNavMenu",
"taigaProject",
"taigaUserSettings",
"taigaFeedback",
"taigaPlugins",
"taigaIntegrations",
"taigaComponents",
# new modules
"taigaProfile",
"taigaHome",
"taigaUserTimeline",
# template cache
"templates",
@ -340,7 +631,9 @@ modules = [
# Vendor modules
"ngRoute",
"ngAnimate",
"pascalprecht.translate"
"pascalprecht.translate",
"infinite-scroll",
"tgRepeat"
].concat(_.map(@.taigaContribPlugins, (plugin) -> plugin.module))
# Main module definition
@ -352,7 +645,6 @@ module.config([
"$httpProvider",
"$provide",
"$tgEventsProvider",
"tgLoaderProvider",
"$compileProvider",
"$translateProvider",
configure
@ -360,11 +652,15 @@ module.config([
module.run([
"$log",
"$tgConfig",
"$rootScope",
"$tgAuth",
"$tgEvents",
"$tgAnalytics",
"$translate"
"$translate",
"$tgLocation",
"$tgNavUrls",
"tgAppMetaService",
"tgProjectService",
"tgLoader",
init
])

View File

@ -43,11 +43,12 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai
"$tgLocation",
"$tgNavUrls",
"$tgAnalytics",
"$appTitle"
"tgAppMetaService",
"$translate"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q,
@location, @navUrls, @analytics, @appTitle) ->
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @analytics,
@appMetaService, @translate) ->
bindMethods(@)
@scope.project = {}
@ -56,7 +57,9 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai
promise = @.loadInitialData()
promise.then =>
@appTitle.set("Membership - " + @scope.project.name)
title = @translate.instant("ADMIN.MEMBERSHIPS.PAGE_TITLE", {projectName: @scope.project.name})
description = @scope.project.description
@appMetaService.setAll(title, description)
promise.then null, @.onInitialDataError.bind(@)
@ -65,10 +68,11 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai
@analytics.trackEvent("membership", "create", "create memberships on admin", 1)
loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) =>
return @rs.projects.getBySlug(@params.pslug).then (project) =>
if not project.i_am_owner
@location.path(@navUrls.resolve("permission-denied"))
@scope.projectId = project.id
@scope.project = project
@scope.$emit('project:loaded', project)
return project
@ -76,20 +80,20 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai
loadMembers: ->
httpFilters = @.getUrlFilters()
return @rs.memberships.list(@scope.projectId, httpFilters).then (data) =>
@scope.memberships = _.filter(data.models, (membership) -> membership.user == null or membership.is_user_active)
@scope.memberships = _.filter(data.models, (membership) ->
membership.user == null or membership.is_user_active)
@scope.page = data.current
@scope.count = data.count
@scope.paginatedBy = data.paginatedBy
return data
loadInitialData: ->
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project
return data
promise = @.loadProject()
promise.then =>
@.loadUsersAndRoles()
@.loadMembers()
return promise.then(=> @.loadProject())
.then(=> @.loadUsersAndRoles())
.then(=> @.loadMembers())
return promise
getUrlFilters: ->
filters = _.pick(@location.search(), "page")
@ -377,7 +381,9 @@ MembershipsRowActionsDirective = ($log, $repo, $rs, $confirm, $compile, $transla
$el.on "click", ".pending", (event) ->
event.preventDefault()
onSuccess = ->
text = $translate.instant("ADMIN.MEMBERSHIP.SUCCESS_SEND_INVITATION", {email: $scope.member.email})
text = $translate.instant("ADMIN.MEMBERSHIP.SUCCESS_SEND_INVITATION", {
email: $scope.member.email
})
$confirm.notify("success", text)
onError = ->
text = $translate.instant("ADMIM.MEMBERSHIP.ERROR_SEND_INVITATION")
@ -414,4 +420,5 @@ MembershipsRowActionsDirective = ($log, $repo, $rs, $confirm, $compile, $transla
return {link: link}
module.directive("tgMembershipsRowActions", ["$log", "$tgRepo", "$tgResources", "$tgConfirm", "$compile", "$translate", MembershipsRowActionsDirective])
module.directive("tgMembershipsRowActions", ["$log", "$tgRepo", "$tgResources", "$tgConfirm", "$compile",
"$translate", MembershipsRowActionsDirective])

View File

@ -47,34 +47,38 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin)
"$q",
"$tgLocation",
"$tgNavUrls",
"$appTitle",
"tgAppMetaService",
"$translate"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @appTitle, @translate) ->
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls,
@appMetaService, @translate) ->
@scope.project = {}
promise = @.loadInitialData()
promise.then =>
sectionName = @translate.instant( @scope.sectionName)
appTitle = @translate.instant("ADMIN.PROJECT_PROFILE.PAGE_TITLE", {
title = @translate.instant("ADMIN.PROJECT_PROFILE.PAGE_TITLE", {
sectionName: sectionName, projectName: @scope.project.name})
@appTitle.set(appTitle)
description = @scope.project.description
@appMetaService.setAll(title, description)
promise.then null, @.onInitialDataError.bind(@)
@scope.$on "project:loaded", =>
sectionName = @translate.instant(@scope.sectionName)
appTitle = @translate.instant("ADMIN.PROJECT_PROFILE.PAGE_TITLE", {
title = @translate.instant("ADMIN.PROJECT_PROFILE.PAGE_TITLE", {
sectionName: sectionName, projectName: @scope.project.name})
@appTitle.set(appTitle)
description = @scope.project.description
@appMetaService.setAll(title, description)
loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) =>
return @rs.projects.getBySlug(@params.pslug).then (project) =>
if not project.i_am_owner
@location.path(@navUrls.resolve("permission-denied"))
@scope.projectId = project.id
@scope.project = project
@scope.pointsList = _.sortBy(project.points, "order")
@scope.usStatusList = _.sortBy(project.us_statuses, "order")
@ -86,22 +90,9 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin)
@scope.$emit('project:loaded', project)
return project
loadTagsColors: ->
return @rs.projects.tagsColors(@scope.projectId).then (tags_colors) =>
@scope.project.tags_colors = tags_colors
loadProjectProfile: ->
return @q.all([
@.loadProject(),
@.loadTagsColors()
])
loadInitialData: ->
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project
return data
return promise.then(=> @.loadProjectProfile())
promise = @.loadProject()
return promise
openDeleteLightbox: ->
@rootscope.$broadcast("deletelightbox:new", @scope.project)
@ -113,8 +104,10 @@ module.controller("ProjectProfileController", ProjectProfileController)
## Project Profile Directive
#############################################################################
ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location) ->
ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location, projectService) ->
link = ($scope, $el, $attrs) ->
$ctrl = $el.controller()
form = $el.find("form").checksley({"onlyOneErrorElement": true})
submit = debounce 2000, (event) =>
event.preventDefault()
@ -127,9 +120,14 @@ ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location) ->
promise.then ->
$loading.finish(submitButton)
$confirm.notify("success")
newUrl = $navurls.resolve("project-admin-project-profile-details", {project: $scope.project.slug})
newUrl = $navurls.resolve("project-admin-project-profile-details", {
project: $scope.project.slug
})
$location.path(newUrl)
$scope.$emit("project:loaded", $scope.project)
$ctrl.loadInitialData()
projectService.fetchProject()
promise.then null, (data) ->
$loading.finish(submitButton)
@ -144,7 +142,8 @@ ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location) ->
return {link:link}
module.directive("tgProjectProfile", ["$tgRepo", "$tgConfirm", "$tgLoading", "$tgNavUrls", "$tgLocation",
ProjectProfileDirective])
"tgProjectService", ProjectProfileDirective])
#############################################################################
## Project Default Values Directive
@ -187,7 +186,7 @@ module.directive("tgProjectDefaultValues", ["$tgRepo", "$tgConfirm", "$tgLoading
## Project Modules Directive
#############################################################################
ProjectModulesDirective = ($repo, $confirm, $loading) ->
ProjectModulesDirective = ($repo, $confirm, $loading, projectService) ->
link = ($scope, $el, $attrs) ->
form = $el.find("form").checksley()
submit = =>
@ -201,6 +200,8 @@ ProjectModulesDirective = ($repo, $confirm, $loading) ->
$confirm.notify("success")
$scope.$emit("project:loaded", $scope.project)
projectService.fetchProject()
promise.then null, (data) ->
$loading.finish(target)
$confirm.notify("error", data._error_message)
@ -229,7 +230,8 @@ ProjectModulesDirective = ($repo, $confirm, $loading) ->
return {link:link}
module.directive("tgProjectModules", ["$tgRepo", "$tgConfirm", "$tgLoading", ProjectModulesDirective])
module.directive("tgProjectModules", ["$tgRepo", "$tgConfirm", "$tgLoading", "tgProjectService",
ProjectModulesDirective])
#############################################################################

View File

@ -46,11 +46,12 @@ class ProjectValuesSectionController extends mixOf(taiga.Controller, taiga.PageM
"$q",
"$tgLocation",
"$tgNavUrls",
"$appTitle",
"tgAppMetaService",
"$translate"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @appTitle, @translate) ->
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls,
@appMetaService, @translate) ->
@scope.project = {}
promise = @.loadInitialData()
@ -58,30 +59,28 @@ class ProjectValuesSectionController extends mixOf(taiga.Controller, taiga.PageM
promise.then () =>
sectionName = @translate.instant(@scope.sectionName)
title = @translate.instant("ADMIN.PROJECT_VALUES.APP_TITLE", {
title = @translate.instant("ADMIN.PROJECT_VALUES.PAGE_TITLE", {
"sectionName": sectionName,
"projectName": @scope.project.name
})
@appTitle.set(title)
description = @scope.project.description
@appMetaService.setAll(title, description)
promise.then null, @.onInitialDataError.bind(@)
loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) =>
return @rs.projects.getBySlug(@params.pslug).then (project) =>
if not project.i_am_owner
@location.path(@navUrls.resolve("permission-denied"))
@scope.projectId = project.id
@scope.project = project
@scope.$emit('project:loaded', project)
return project
loadInitialData: ->
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project
return data
return promise.then => @.loadProject()
promise = @.loadProject()
return promise
module.controller("ProjectValuesSectionController", ProjectValuesSectionController)
@ -126,7 +125,7 @@ module.controller("ProjectValuesController", ProjectValuesController)
## Project values directive
#############################################################################
ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame, @translate, $rootscope) ->
ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame, $translate, $rootscope) ->
## Drag & Drop Link
linkDragAndDrop = ($scope, $el, $attrs) ->
@ -167,7 +166,7 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame, @tra
}
initializeTextTranslations = ->
$scope.addNewElementText = @translate.instant("ADMIN.PROJECT_VALUES_#{objName.toUpperCase()}.ACTION_ADD")
$scope.addNewElementText = $translate.instant("ADMIN.PROJECT_VALUES_#{objName.toUpperCase()}.ACTION_ADD")
initializeNewValue()
initializeTextTranslations()
@ -296,7 +295,10 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame, @tra
if _.keys(choices).length == 0
return $confirm.error("ADMIN.PROJECT_VALUES.ERROR_DELETE_ALL")
$confirm.askChoice("PROJECT.TITLE_ACTION_DELETE_VALUE", subtitle, choices, "ADMIN.PROJECT_VALUES.REPLACEMENT").then (response) ->
title = $translate.instant("ADMIN.COMMON.TITLE_ACTION_DELETE_VALUE")
text = $translate.instant("ADMIN.PROJECT_VALUES.REPLACEMENT")
$confirm.askChoice(title, subtitle, choices, text).then (response) ->
onSucces = ->
$ctrl.loadValues().finally ->
response.finish()
@ -382,15 +384,24 @@ class ProjectCustomAttributesController extends mixOf(taiga.Controller, taiga.Pa
"$q",
"$tgLocation",
"$tgNavUrls",
"$appTitle",
"tgAppMetaService",
"$translate"
]
constructor: (@scope, @rootscope, @repo, @rs, @params, @q, @location, @navUrls, @appTitle) ->
constructor: (@scope, @rootscope, @repo, @rs, @params, @q, @location, @navUrls, @appMetaService,
@translate) ->
@scope.project = {}
@rootscope.$on "project:loaded", =>
@.loadCustomAttributes()
@appTitle.set("Project Custom Attributes - " + @scope.sectionName + " - " + @scope.project.name)
sectionName = @translate.instant(@scope.sectionName)
title = @translate.instant("ADMIN.CUSTOM_ATTRIBUTES.PAGE_TITLE", {
"sectionName": sectionName,
"projectName": @scope.project.name
})
description = @scope.project.description
@appMetaService.setAll(title, description)
#########################
# Custom Attribute
@ -430,7 +441,7 @@ module.controller("ProjectCustomAttributesController", ProjectCustomAttributesCo
## Custom Attributes Directive
#############################################################################
ProjectCustomAttributesDirective = ($log, $confirm, animationFrame) ->
ProjectCustomAttributesDirective = ($log, $confirm, animationFrame, $translate) ->
link = ($scope, $el, $attrs) ->
$ctrl = $el.controller()
@ -616,7 +627,10 @@ ProjectCustomAttributesDirective = ($log, $confirm, animationFrame) ->
attr = formEl.scope().attr
message = attr.name
$confirm.ask("COMMON.CUSTOM_ATTRIBUTES.DELETE", "COMMON.CUSTOM_ATTRIBUTES.CONFIRM_DELETE", message).then (finish) ->
title = $translate.instant("COMMON.CUSTOM_ATTRIBUTES.DELETE")
text = $translate.instant("COMMON.CUSTOM_ATTRIBUTES.CONFIRM_DELETE")
$confirm.ask(title, text, message).then (finish) ->
onSucces = ->
$ctrl.loadCustomAttributes().finally ->
finish()
@ -636,4 +650,5 @@ ProjectCustomAttributesDirective = ($log, $confirm, animationFrame) ->
return {link: link}
module.directive("tgProjectCustomAttributes", ["$log", "$tgConfirm", "animationFrame", ProjectCustomAttributesDirective])
module.directive("tgProjectCustomAttributes", ["$log", "$tgConfirm", "animationFrame", "$translate",
ProjectCustomAttributesDirective])

View File

@ -44,12 +44,12 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil
"$q",
"$tgLocation",
"$tgNavUrls",
"$appTitle",
"tgAppMetaService",
"$translate"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @appTitle,
@translate) ->
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls,
@appMetaService, @translate) ->
bindMethods(@)
@scope.sectionName = "ADMIN.MENU.PERMISSIONS"
@ -59,16 +59,18 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil
promise = @.loadInitialData()
promise.then () =>
title = @translate.instant("ADMIN.ROLES.SECTION_NAME", {projectName: @scope.project.name})
@appTitle.set(title)
title = @translate.instant("ADMIN.ROLES.PAGE_TITLE", {projectName: @scope.project.name})
description = @scope.project.description
@appMetaService.setAll(title, description)
promise.then null, @.onInitialDataError.bind(@)
loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) =>
return @rs.projects.getBySlug(@params.pslug).then (project) =>
if not project.i_am_owner
@location.path(@navUrls.resolve("permission-denied"))
@scope.projectId = project.id
@scope.project = project
@scope.$emit('project:loaded', project)
@ -76,39 +78,29 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil
return project
loadExternalUserRole: (roles) ->
roles = roles.map (role) ->
role.external_user = false
return role
public_permission = {
"name": @translate.instant("ADMIN.ROLES.EXTERNAL_USER"),
"permissions": @scope.project.public_permissions,
"external_user": true
}
roles.push(public_permission)
return roles
loadRoles: ->
return @rs.roles.list(@scope.projectId)
.then @loadExternalUserRole
.then (roles) =>
@scope.roles = roles
@scope.role = @scope.roles[0]
return @rs.roles.list(@scope.projectId).then (roles) =>
roles = roles.map (role) ->
role.external_user = false
return roles
return role
public_permission = {
"name": @translate.instant("ADMIN.ROLES.EXTERNAL_USER"),
"permissions": @scope.project.public_permissions,
"external_user": true
}
roles.push(public_permission)
@scope.roles = roles
@scope.role = @scope.roles[0]
return roles
loadInitialData: ->
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project
return data
return promise.then(=> @.loadProject())
.then(=> @.loadUsersAndRoles())
.then(=> @.loadRoles())
promise = @.loadProject()
promise.then(=> @.loadRoles())
return promise
setRole: (role) ->
@scope.role = role
@ -236,7 +228,8 @@ NewRoleDirective = ($tgrepo, $confirm) ->
$el.find(".new").val('')
onSuccess = (role) ->
$scope.roles.push(role)
insertPosition = $scope.roles.length - 1
$scope.roles.splice(insertPosition, 0, role)
$ctrl.setRole(role)
$el.find(".add-button").show()
$ctrl.loadProject()

View File

@ -28,6 +28,7 @@ timeout = @.taiga.timeout
module = angular.module("taigaAdmin")
#############################################################################
## Webhooks
#############################################################################
@ -40,11 +41,11 @@ class WebhooksController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.
"$routeParams",
"$tgLocation",
"$tgNavUrls",
"$appTitle",
"tgAppMetaService",
"$translate"
]
constructor: (@scope, @repo, @rs, @params, @location, @navUrls, @appTitle, @translate) ->
constructor: (@scope, @repo, @rs, @params, @location, @navUrls, @appMetaService, @translate) ->
bindMethods(@)
@scope.sectionName = "ADMIN.WEBHOOKS.SECTION_NAME"
@ -53,8 +54,9 @@ class WebhooksController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.
promise = @.loadInitialData()
promise.then () =>
text = @translate.instant("ADMIN.WEBHOOKS.APP_TITLE", {"projectName": @scope.project.name})
@appTitle.set(text)
title = @translate.instant("ADMIN.WEBHOOKS.PAGE_TITLE", {projectName: @scope.project.name})
description = @scope.project.description
@appMetaService.setAll(title, description)
promise.then null, @.onInitialDataError.bind(@)
@ -65,24 +67,25 @@ class WebhooksController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.
@scope.webhooks = webhooks
loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) =>
return @rs.projects.getBySlug(@params.pslug).then (project) =>
if not project.i_am_owner
@location.path(@navUrls.resolve("permission-denied"))
@scope.projectId = project.id
@scope.project = project
@scope.$emit('project:loaded', project)
return project
loadInitialData: ->
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project
return data
promise = @.loadProject()
promise.then =>
@.loadWebhooks()
return promise.then(=> @.loadProject())
.then(=> @.loadWebhooks())
return promise
module.controller("WebhooksController", WebhooksController)
#############################################################################
## Webhook Directive
#############################################################################
@ -213,7 +216,8 @@ WebhookDirective = ($rs, $repo, $confirm, $loading, $translate) ->
return {link:link}
module.directive("tgWebhook", ["$tgResources", "$tgRepo", "$tgConfirm", "$tgLoading", "$translate", WebhookDirective])
module.directive("tgWebhook", ["$tgResources", "$tgRepo", "$tgConfirm", "$tgLoading", "$translate",
WebhookDirective])
#############################################################################
@ -289,11 +293,11 @@ class GithubController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
"$tgRepo",
"$tgResources",
"$routeParams",
"$appTitle",
"tgAppMetaService",
"$translate"
]
constructor: (@scope, @repo, @rs, @params, @appTitle, @translate) ->
constructor: (@scope, @repo, @rs, @params, @appMetaService, @translate) ->
bindMethods(@)
@scope.sectionName = @translate.instant("ADMIN.GITHUB.SECTION_NAME")
@ -302,8 +306,9 @@ class GithubController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
promise = @.loadInitialData()
promise.then () =>
title = @translate.instant("ADMIN.GITHUB.APP_TITLE", {projectName: @scope.project.name})
@appTitle.set(title)
title = @translate.instant("ADMIN.GITHUB.PAGE_TITLE", {projectName: @scope.project.name})
description = @scope.project.description
@appMetaService.setAll(title, description)
promise.then null, @.onInitialDataError.bind(@)
@ -312,19 +317,16 @@ class GithubController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
@scope.github = github
loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) =>
return @rs.projects.getBySlug(@params.pslug).then (project) =>
@scope.projectId = project.id
@scope.project = project
@scope.$emit('project:loaded', project)
return project
loadInitialData: ->
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project
return data
return promise.then(=> @.loadProject())
.then(=> @.loadModules())
promise = @.loadProject()
promise.then(=> @.loadModules())
return promise
module.controller("GithubController", GithubController)
@ -339,11 +341,11 @@ class GitlabController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
"$tgRepo",
"$tgResources",
"$routeParams",
"$appTitle",
"tgAppMetaService",
"$translate"
]
constructor: (@scope, @repo, @rs, @params, @appTitle, @translate) ->
constructor: (@scope, @repo, @rs, @params, @appMetaService, @translate) ->
bindMethods(@)
@scope.sectionName = @translate.instant("ADMIN.GITLAB.SECTION_NAME")
@ -351,8 +353,9 @@ class GitlabController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
promise = @.loadInitialData()
promise.then () =>
title = @translate.instant("ADMIN.GITLAB.APP_TITLE", {projectName: @scope.project.name})
@appTitle.set(title)
title = @translate.instant("ADMIN.GITLAB.PAGE_TITLE", {projectName: @scope.project.name})
description = @scope.project.description
@appMetaService.setAll(title, description)
promise.then null, @.onInitialDataError.bind(@)
@ -364,19 +367,16 @@ class GitlabController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
@scope.gitlab = gitlab
loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) =>
return @rs.projects.getBySlug(@params.pslug).then (project) =>
@scope.projectId = project.id
@scope.project = project
@scope.$emit('project:loaded', project)
return project
loadInitialData: ->
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project
return data
return promise.then(=> @.loadProject())
.then(=> @.loadModules())
promise = @.loadProject()
promise.then(=> @.loadModules())
return promise
module.controller("GitlabController", GitlabController)
@ -391,11 +391,11 @@ class BitbucketController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
"$tgRepo",
"$tgResources",
"$routeParams",
"$appTitle",
"tgAppMetaService",
"$translate"
]
constructor: (@scope, @repo, @rs, @params, @appTitle, @translate) ->
constructor: (@scope, @repo, @rs, @params, @appMetaService, @translate) ->
bindMethods(@)
@scope.sectionName = @translate.instant("ADMIN.BITBUCKET.SECTION_NAME")
@ -403,8 +403,9 @@ class BitbucketController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
promise = @.loadInitialData()
promise.then () =>
title = @translate.instant("ADMIN.BITBUCKET.APP_TITLE", {projectName: @scope.project.name})
@appTitle.set(title)
title = @translate.instant("ADMIN.BITBUCKET.PAGE_TITLE", {projectName: @scope.project.name})
description = @scope.project.description
@appMetaService.setAll(title, description)
promise.then null, @.onInitialDataError.bind(@)
@ -416,18 +417,16 @@ class BitbucketController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
@scope.bitbucket = bitbucket
loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) =>
return @rs.projects.getBySlug(@params.pslug).then (project) =>
@scope.projectId = project.id
@scope.project = project
@scope.$emit('project:loaded', project)
return project
loadInitialData: ->
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project
return data
return promise.then(=> @.loadProject())
.then(=> @.loadModules())
promise = @.loadProject()
promise.then(=> @.loadModules())
return promise
module.controller("BitbucketController", BitbucketController)
@ -552,12 +551,12 @@ module.directive("tgBitbucketWebhooks", ["$tgRepo", "$tgConfirm", "$tgLoading",
#############################################################################
ValidOriginIpsDirective = ->
link = ($scope, $el, $attrs, $ngModel) ->
$ngModel.$parsers.push (value) ->
value = $.trim(value)
if value == ""
return []
$ngModel.$parsers.push (value) ->
value = $.trim(value)
if value == ""
return []
return value.split(",")
return value.split(",")
return {
link: link

View File

@ -36,14 +36,26 @@ class AuthService extends taiga.Service
"$tgHttp",
"$tgUrls",
"$tgConfig",
"$translate"]
"$translate",
"tgCurrentUserService"]
constructor: (@rootscope, @storage, @model, @rs, @http, @urls, @config, @translate) ->
constructor: (@rootscope, @storage, @model, @rs, @http, @urls, @config, @translate, @currentUserService) ->
super()
userModel = @.getUser()
@.setUserdata(userModel)
setUserdata: (userModel) ->
if userModel
@.userData = Immutable.fromJS(userModel.getAttrs())
@currentUserService.setUser(@.userData)
else
@.userData = null
_setLocales: ->
lang = @rootscope.user.lang || @config.get("defaultLanguage") || "en"
@translate.use(lang)
@translate.preferredLanguage(lang) # Needed for calls to the api in the correct language
@translate.use(lang) # Needed for change the interface in runtime
getUser: ->
if @rootscope.user
@ -63,6 +75,8 @@ class AuthService extends taiga.Service
@storage.set("userInfo", user.getAttrs())
@rootscope.user = user
@.setUserdata(user)
@._setLocales()
clear: ->
@ -104,6 +118,8 @@ class AuthService extends taiga.Service
@.removeToken()
@.clear()
@currentUserService.removeUser()
register: (data, type, existing) ->
url = @urls.resolve("auth-register")
@ -176,7 +192,8 @@ PublicRegisterMessageDirective = ($config, $navUrls, templates) ->
template: templateFn
}
module.directive("tgPublicRegisterMessage", ["$tgConfig", "$tgNavUrls", "$tgTemplate", PublicRegisterMessageDirective])
module.directive("tgPublicRegisterMessage", ["$tgConfig", "$tgNavUrls", "$tgTemplate",
PublicRegisterMessageDirective])
LoginDirective = ($auth, $confirm, $location, $config, $routeParams, $navUrls, $events, $translate) ->
@ -212,11 +229,17 @@ LoginDirective = ($auth, $confirm, $location, $config, $routeParams, $navUrls, $
$el.on "submit", "form", submit
window.prerenderReady = true
$scope.$on "$destroy", ->
$el.off()
return {link:link}
module.directive("tgLogin", ["$tgAuth", "$tgConfirm", "$tgLocation", "$tgConfig", "$routeParams",
"$tgNavUrls", "$tgEvents", "$translate", LoginDirective])
#############################################################################
## Register Directive
#############################################################################
@ -238,9 +261,9 @@ RegisterDirective = ($auth, $confirm, $location, $navUrls, $config, $analytics,
$location.path($navUrls.resolve("home"))
onErrorSubmit = (response) ->
if response.data._error_message?
text = $translate.instant("LOGIN_FORM.ERROR_GENERIC") + " " + response.data._error_message
$confirm.notify("light-error", text + " " + response.data._error_message)
if response.data._error_message
text = $translate.instant("COMMON.GENERIC_ERROR", {error: response.data._error_message})
$confirm.notify("light-error", text)
form.setErrors(response.data)
@ -255,11 +278,17 @@ RegisterDirective = ($auth, $confirm, $location, $navUrls, $config, $analytics,
$el.on "submit", "form", submit
$scope.$on "$destroy", ->
$el.off()
window.prerenderReady = true
return {link:link}
module.directive("tgRegister", ["$tgAuth", "$tgConfirm", "$tgLocation", "$tgNavUrls", "$tgConfig",
"$tgAnalytics", "$translate", RegisterDirective])
#############################################################################
## Forgot Password Directive
#############################################################################
@ -291,11 +320,17 @@ ForgotPasswordDirective = ($auth, $confirm, $location, $navUrls, $translate) ->
$el.on "submit", "form", submit
$scope.$on "$destroy", ->
$el.off()
window.prerenderReady = true
return {link:link}
module.directive("tgForgotPassword", ["$tgAuth", "$tgConfirm", "$tgLocation", "$tgNavUrls", "$translate",
ForgotPasswordDirective])
#############################################################################
## Change Password from Recovery Directive
#############################################################################
@ -316,12 +351,10 @@ ChangePasswordFromRecoveryDirective = ($auth, $confirm, $location, $params, $nav
$location.path($navUrls.resolve("login"))
text = $translate.instant("CHANGE_PASSWORD_RECOVERY_FORM.SUCCESS")
$confirm.success(text)
onErrorSubmit = (response) ->
text = $translate.instant("COMMON.GENERIC_ERROR", {error: response.data._error_message})
$confirm.notify("light-error", text)
submit = debounce 2000, (event) =>
@ -335,10 +368,15 @@ ChangePasswordFromRecoveryDirective = ($auth, $confirm, $location, $params, $nav
$el.on "submit", "form", submit
$scope.$on "$destroy", ->
$el.off()
return {link:link}
module.directive("tgChangePasswordFromRecovery", ["$tgAuth", "$tgConfirm", "$tgLocation", "$routeParams",
"$tgNavUrls", "$translate", ChangePasswordFromRecoveryDirective])
"$tgNavUrls", "$translate",
ChangePasswordFromRecoveryDirective])
#############################################################################
## Invitation
@ -365,7 +403,9 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics
onSuccessSubmitLogin = (response) ->
$analytics.trackEvent("auth", "invitationAccept", "invitation accept with existing user", 1)
$location.path($navUrls.resolve("project", {project: $scope.invitation.project_slug}))
text = $translate.instant("INVITATION_LOGIN_FORM.SUCCESS", {"project_name": $scope.invitation.project_name})
text = $translate.instant("INVITATION_LOGIN_FORM.SUCCESS", {
"project_name": $scope.invitation.project_name
})
$confirm.notify("success", text)
@ -388,7 +428,7 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics
# Register form
$scope.dataRegister = {token: token}
registerForm = $el.find("form.register-form").checksley()
registerForm = $el.find("form.register-form").checksley({onlyOneErrorElement: true})
onSuccessSubmitRegister = (response) ->
$analytics.trackEvent("auth", "invitationAccept", "invitation accept with new user", 1)
@ -397,9 +437,11 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics
"Welcome to #{_.escape($scope.invitation.project_name)}")
onErrorSubmitRegister = (response) ->
text = $translate.instant("LOGIN_FORM.ERROR_AUTH_INCORRECT")
if response.data._error_message
text = $translate.instant("COMMON.GENERIC_ERROR", {error: response.data._error_message})
$confirm.notify("light-error", text)
$confirm.notify("light-error", text)
registerForm.setErrors(response.data)
submitRegister = debounce 2000, (event) =>
event.preventDefault()
@ -413,11 +455,15 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics
$el.on "submit", "form.register-form", submitRegister
$el.on "click", ".button-register", submitRegister
$scope.$on "$destroy", ->
$el.off()
return {link:link}
module.directive("tgInvitation", ["$tgAuth", "$tgConfirm", "$tgLocation", "$routeParams",
"$tgNavUrls", "$tgAnalytics", "$translate", InvitationDirective])
#############################################################################
## Change Email
#############################################################################
@ -429,12 +475,15 @@ ChangeEmailDirective = ($repo, $model, $auth, $confirm, $location, $params, $nav
form = $el.find("form").checksley()
onSuccessSubmit = (response) ->
$repo.queryOne("users", $auth.getUser().id).then (data) =>
$auth.setUser(data)
$location.path($navUrls.resolve("home"))
if $auth.isAuthenticated()
$repo.queryOne("users", $auth.getUser().id).then (data) =>
$auth.setUser(data)
$location.path($navUrls.resolve("home"))
else
$location.path($navUrls.resolve("login"))
text = $translate.instant("CHANGE_EMAIL_FORM.SUCCESS")
$confirm.success(text)
text = $translate.instant("CHANGE_EMAIL_FORM.SUCCESS")
$confirm.success(text)
onErrorSubmit = (response) ->
text = $translate.instant("COMMON.GENERIC_ERROR", {error: response.data._error_message})
@ -456,10 +505,14 @@ ChangeEmailDirective = ($repo, $model, $auth, $confirm, $location, $params, $nav
event.preventDefault()
submit()
$scope.$on "$destroy", ->
$el.off()
return {link:link}
module.directive("tgChangeEmail", ["$tgRepo", "$tgModel", "$tgAuth", "$tgConfirm", "$tgLocation", "$routeParams",
"$tgNavUrls", "$translate", ChangeEmailDirective])
module.directive("tgChangeEmail", ["$tgRepo", "$tgModel", "$tgAuth", "$tgConfirm", "$tgLocation",
"$routeParams", "$tgNavUrls", "$translate", ChangeEmailDirective])
#############################################################################
## Cancel account
@ -495,7 +548,10 @@ CancelAccountDirective = ($repo, $model, $auth, $confirm, $location, $params, $n
$el.on "submit", "form", submit
$scope.$on "$destroy", ->
$el.off()
return {link:link}
module.directive("tgCancelAccount", ["$tgRepo", "$tgModel", "$tgAuth", "$tgConfirm", "$tgLocation", "$routeParams",
"$tgNavUrls", CancelAccountDirective])
module.directive("tgCancelAccount", ["$tgRepo", "$tgModel", "$tgAuth", "$tgConfirm", "$tgLocation",
"$routeParams","$tgNavUrls", CancelAccountDirective])

View File

@ -45,16 +45,15 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
"$routeParams",
"$q",
"$tgLocation",
"$appTitle",
"tgAppMetaService",
"$tgNavUrls",
"$tgEvents",
"$tgAnalytics",
"tgLoader",
"$translate"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q,
@location, @appTitle, @navUrls, @events, @analytics, tgLoader, @translate) ->
@location, @appMetaService, @navUrls, @events, @analytics, @translate) ->
bindMethods(@)
@scope.sectionName = @translate.instant("BACKLOG.SECTION_NAME")
@ -67,7 +66,12 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
# On Success
promise.then =>
@appTitle.set("Backlog - " + @scope.project.name)
title = @translate.instant("BACKLOG.PAGE_TITLE", {projectName: @scope.project.name})
description = @translate.instant("BACKLOG.PAGE_DESCRIPTION", {
projectName: @scope.project.name,
projectDescription: @scope.project.description
})
@appMetaService.setAll(title, description)
if @rs.userstories.getShowTags(@scope.projectId)
@showTags = true
@ -77,9 +81,6 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
# On Error
promise.then null, @.onInitialDataError.bind(@)
# Finally
promise.finally tgLoader.pageLoaded
initializeEventHandlers: ->
@scope.$on "usform:bulk:success", =>
@.loadUserstories()
@ -607,9 +608,11 @@ BacklogDirective = ($repo, $rootscope, $translate) ->
$ctrl.loadProjectStats()
# Enable move to current sprint only when there are selected us's
$el.on "change", ".backlog-table-body .user-stories input:checkbox", (event) ->
target = angular.element(event.currentTarget)
shiftPressed = false
lastChecked = null
checkSelected = (target) ->
lastChecked = target.closest(".us-item-row")
moveToCurrentSprintDom = $el.find("#move-to-current-sprint")
selectedUsDom = $el.find(".backlog-table-body .user-stories input:checkbox:checked")
@ -620,6 +623,33 @@ BacklogDirective = ($repo, $rootscope, $translate) ->
target.closest('.us-item-row').toggleClass('ui-multisortable-multiple')
$(window).on "keydown.shift-pressed keyup.shift-pressed", (event) ->
shiftPressed = !!event.shiftKey
return true
# Enable move to current sprint only when there are selected us's
$el.on "change", ".backlog-table-body .user-stories input:checkbox", (event) ->
# check elements between the last two if shift is pressed
if lastChecked && shiftPressed
elements = []
current = $(event.currentTarget).closest(".us-item-row")
nextAll = lastChecked.nextAll()
prevAll = lastChecked.prevAll()
if _.some(nextAll, (next) -> next == current[0])
elements = lastChecked.nextUntil(current)
else if _.some(prevAll, (prev) -> prev == current[0])
elements = lastChecked.prevUntil(current)
_.map elements, (elm) ->
input = $(elm).find("input:checkbox")
input.prop('checked', true);
checkSelected(input)
target = angular.element(event.currentTarget)
checkSelected(target)
$el.on "click", "#move-to-current-sprint", (event) =>
# Calculating the us's to be modified
ussDom = $el.find(".backlog-table-body .user-stories input:checkbox:checked")
@ -705,6 +735,7 @@ BacklogDirective = ($repo, $rootscope, $translate) ->
$scope.$on "$destroy", ->
$el.off()
$(window).off(".shift-pressed")
return {link: link}

View File

@ -61,10 +61,10 @@ BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm, $translate) ->
items: ".us-item-row",
cancel: ".popover"
connectWith: ".sprint"
containment: ".wrapper"
dropOnEmpty: true
placeholder: "row us-item-row us-item-drag sortable-placeholder"
scroll: true
disableHorizontalScroll: true
# A consequence of length of backlog user story item
# the default tolerance ("intersection") not works properly.
tolerance: "pointer"
@ -73,8 +73,11 @@ BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm, $translate) ->
# works unexpectly (in some circumstances calculates wrong
# position for revert).
revert: false
cursorAt: {right: 15}
start: () ->
$(document.body).addClass("drag-active")
stop: () ->
$(document.body).removeClass("drag-active")
if $el.hasClass("active-filters")
$el.sortable("cancel")
filterError()
@ -167,8 +170,11 @@ SprintSortableDirective = ($repo, $rs, $rootscope) ->
$el.sortable({
scroll: true
dropOnEmpty: true
items: ".sprint-table .milestone-us-item-row",
items: ".sprint-table .milestone-us-item-row"
disableHorizontalScroll: true
connectWith: ".sprint,.backlog-table-body,.empty-backlog"
placeholder: "row us-item-row sortable-placeholder"
forcePlaceholderSize:true
})
$el.on "multiplesortreceive", (event, ui) ->

View File

@ -44,6 +44,7 @@ module.directive("tgMain", ["$rootScope", "$window", TaigaMainDirective])
urls = {
"home": "/"
"projects": "/projects"
"error": "/error"
"not-found": "/not-found"
"permission-denied": "/permission-denied"
@ -57,7 +58,8 @@ urls = {
"invitation": "/invitation/:token"
"create-project": "/create-project"
"profile": "/:user"
"profile": "/profile"
"user-profile": "/profile/:username"
"project": "/project/:project"
"project-backlog": "/project/:project/backlog"
@ -67,9 +69,7 @@ urls = {
"project-search": "/project/:project/search"
"project-userstories-detail": "/project/:project/us/:ref"
"project-tasks-detail": "/project/:project/task/:ref"
"project-issues-detail": "/project/:project/issue/:ref"
"project-wiki": "/project/:project/wiki"
@ -102,10 +102,10 @@ urls = {
"project-admin-contrib": "/project/:project/admin/contrib/:plugin"
# User settings
"user-settings-user-profile": "/project/:project/user-settings/user-profile"
"user-settings-user-change-password": "/project/:project/user-settings/user-change-password"
"user-settings-user-avatar": "/project/:project/user-settings/user-avatar"
"user-settings-mail-notifications": "/project/:project/user-settings/mail-notifications"
"user-settings-user-profile": "/user-settings/user-profile"
"user-settings-user-change-password": "/user-settings/user-change-password"
"user-settings-user-avatar": "/user-settings/user-avatar"
"user-settings-mail-notifications": "/user-settings/mail-notifications"
}

View File

@ -22,9 +22,16 @@
taigaContribPlugins = @.taigaContribPlugins = @.taigaContribPlugins or []
class ContribController extends taiga.Controller
@.$inject = ["$rootScope", "$scope", "$routeParams", "$tgRepo", "$tgResources", "$tgConfirm", "$appTitle"]
@.$inject = [
"$rootScope",
"$scope",
"$routeParams",
"$tgRepo",
"$tgResources",
"$tgConfirm"
]
constructor: (@rootScope, @scope, @params, @repo, @rs, @confirm, @appTitle) ->
constructor: (@rootScope, @scope, @params, @repo, @rs, @confirm) ->
@scope.adminPlugins = _.where(@rootScope.contribPlugins, {"type": "admin"})
@scope.currentPlugin = _.first(_.where(@scope.adminPlugins, {"slug": @params.plugin}))
@scope.pluginTemplate = "contrib/#{@scope.currentPlugin.slug}"
@ -32,25 +39,19 @@ class ContribController extends taiga.Controller
promise = @.loadInitialData()
promise.then () =>
@appTitle.set(@scope.project.name)
promise.then null, =>
@confirm.notify("error")
loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) =>
return @rs.projects.getBySlug(@params.pslug).then (project) =>
@scope.projectId = project.id
@scope.project = project
@scope.$emit('project:loaded', project)
@scope.$broadcast('project:loaded', project)
return project
loadInitialData: ->
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project
return data
return promise.then(=> @.loadProject())
return @.loadProject()
module = angular.module("taigaBase")
module.controller("ContribController", ContribController)

View File

@ -22,9 +22,9 @@
taiga = @.taiga
class HttpService extends taiga.Service
@.$inject = ["$http", "$q", "$tgStorage", "$rootScope", "$cacheFactory"]
@.$inject = ["$http", "$q", "$tgStorage", "$rootScope", "$cacheFactory", "$translate"]
constructor: (@http, @q, @storage, @rootScope, @cacheFactory) ->
constructor: (@http, @q, @storage, @rootScope, @cacheFactory, @translate) ->
super()
@.cache = @cacheFactory("httpget");
@ -37,7 +37,7 @@ class HttpService extends taiga.Service
headers["Authorization"] = "Bearer #{token}"
# Accept-Language
lang = @rootScope.user?.lang
lang = @translate.preferredLanguage()
if lang
headers["Accept-Language"] = lang

View File

@ -30,7 +30,7 @@ locationFactory = ($location, $route, $rootscope) ->
return $location
$location.isInCurrentRouteParams = (name, value) ->
params = _.merge($route.current.params, $location.search())
params = $location.search() || {}
return params[name] == value

View File

@ -111,6 +111,9 @@ NavigationUrlsDirective = ($navurls, $auth, $q, $location) ->
target.attr("href", fullUrl)
$el.on "click", (event) ->
if event.metaKey || event.ctrlKey
return
event.preventDefault()
target = $(event.currentTarget)

View File

@ -194,6 +194,21 @@ class RepositoryService extends taiga.Service
result.paginatedBy = parseInt(headers["x-paginated-by"], 10)
return result
queryOnePaginatedRaw: (name, id, params, options={}) ->
url = @urls.resolve(name)
url = "#{url}/#{id}" if id
httpOptions = _.merge({headers: {}}, options)
return @http.get(url, params, httpOptions).then (data) =>
headers = data.headers()
result = {}
result.data = data.data
result.count = parseInt(headers["x-pagination-count"], 10)
result.current = parseInt(headers["x-pagination-current"] or 1, 10)
result.paginatedBy = parseInt(headers["x-paginated-by"], 10)
return result
resolve: (options) ->
params = {}
params.project = options.pslug if options.pslug?

View File

@ -138,17 +138,6 @@ ToggleCommentDirective = () ->
module.directive("tgToggleComment", ToggleCommentDirective)
#############################################################################
## Set the page title
#############################################################################
AppTitle = () ->
set = (text) ->
$("title").text(text)
return {set: set}
module.factory("$appTitle", AppTitle)
#############################################################################
## Get the appropiate section url for a project

View File

@ -47,7 +47,7 @@ class AnalyticsService extends taiga.Service
@win.ga("require", "displayfeatures")
if @.trackRoutes and (not @.ignoreFirstPageLoad)
@win.ga("send", "pageview", @.getUrl());
@win.ga("send", "pageview", @.getUrl())
# activates page tracking
if @.trackRoutes

View File

@ -277,6 +277,8 @@ AttachmentDirective = ($template, $compile, $translate) ->
if attachment.is_deprecated
$el.addClass("deprecated")
$el.find("input:checkbox").prop('checked', true)
else
$el.removeClass("deprecated")
saveAttachment = ->
attachment.description = $el.find("input[name='description']").val()

View File

@ -0,0 +1,13 @@
CompileHtmlDirective = ($compile) ->
link = (scope, element, attrs) ->
scope.$watch attrs.tgCompileHtml, (newValue, oldValue) ->
element.html(newValue)
$compile(element.contents())(scope)
return {
link: link
}
CompileHtmlDirective.$inject = ["$compile"]
angular.module("taigaCommon").directive("tgCompileHtml", CompileHtmlDirective)

View File

@ -220,7 +220,7 @@ WatchersDirective = ($rootscope, $confirm, $repo, $qqueue, $template, $compile,
$confirm.notify("success")
watchers = _.map(watchers, (watcherId) -> $scope.usersById[watcherId])
renderWatchers(watchers)
$rootscope.$broadcast("history:reload")
$rootscope.$broadcast("object:updated")
promise.then null, ->
$model.$modelValue.revert()
@ -235,7 +235,7 @@ WatchersDirective = ($rootscope, $confirm, $repo, $qqueue, $template, $compile,
$confirm.notify("success")
watchers = _.map(item.watchers, (watcherId) -> $scope.usersById[watcherId])
renderWatchers(watchers)
$rootscope.$broadcast("history:reload")
$rootscope.$broadcast("object:updated")
promise.then null, ->
item.revert()
$confirm.notify("error")
@ -321,7 +321,7 @@ AssignedToDirective = ($rootscope, $confirm, $repo, $loading, $qqueue, $template
$loading.finish($el)
$confirm.notify("success")
renderAssignedTo($model.$modelValue)
$rootscope.$broadcast("history:reload")
$rootscope.$broadcast("object:updated")
promise.then null, ->
$model.$modelValue.revert()
$confirm.notify("error")
@ -474,6 +474,10 @@ EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $qqueue, $tem
link = ($scope, $el, $attrs, $model) ->
$scope.$on "object:updated", () ->
$el.find('.edit-subject').hide()
$el.find('.view-subject').show()
isEditable = ->
return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1
@ -485,7 +489,7 @@ EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $qqueue, $tem
promise = $repo.save($model.$modelValue)
promise.then ->
$confirm.notify("success")
$rootscope.$broadcast("history:reload")
$rootscope.$broadcast("object:updated")
$el.find('.edit-subject').hide()
$el.find('.view-subject').show()
promise.then null, ->
@ -501,7 +505,9 @@ EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $qqueue, $tem
$el.find('.view-subject').hide()
$el.find('input').focus()
$el.on "click", ".save", ->
$el.on "click", ".save", (e) ->
e.preventDefault()
subject = $scope.item.subject
save(subject)
@ -553,6 +559,10 @@ EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading,
$el.find('.edit-description').hide()
$el.find('.view-description .edit').hide()
$scope.$on "object:updated", () ->
$el.find('.edit-description').hide()
$el.find('.view-description').show()
isEditable = ->
return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1
@ -563,7 +573,7 @@ EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading,
promise = $repo.save($model.$modelValue)
promise.then ->
$confirm.notify("success")
$rootscope.$broadcast("history:reload")
$rootscope.$broadcast("object:updated")
$el.find('.edit-description').hide()
$el.find('.view-description').show()
promise.then null, ->
@ -782,9 +792,7 @@ module.directive("tgProgressBar", ["$tgTemplate", TgProgressBarDirective])
TgMainTitleDirective = ($translate) ->
link = ($scope, $el, $attrs) ->
$attrs.$observe "i18nSectionName", (i18nSectionName) ->
trans = $translate(i18nSectionName)
trans.then (sectionName) -> $scope.sectionName = sectionName
trans.catch (sectionName) -> $scope.sectionName = sectionName
$scope.sectionName = $translate.instant(i18nSectionName)
$scope.$on "$destroy", ->
$el.off()

View File

@ -92,7 +92,7 @@ UsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $confirm, $qq
estimationProcess = $tgEstimationsService.create($el, us, $scope.project)
estimationProcess.onSelectedPointForRole = (roleId, pointId) ->
@save(roleId, pointId).then ->
$rootScope.$broadcast("history:reload")
$rootScope.$broadcast("object:updated")
estimationProcess.render = () ->
ctx = {

View File

@ -276,8 +276,9 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm, $translate, $c
deleteCommentUser: comment.delete_comment_user.name
deleteComment: comment.comment_html
activityId: comment.id
canRestoreComment: (comment.delete_comment_user.pk == $scope.user.id or
$scope.project.my_permissions.indexOf("modify_project") > -1)
canRestoreComment: ($scope.user and
(comment.delete_comment_user.pk == $scope.user.id or
$scope.project.my_permissions.indexOf("modify_project") > -1))
})
html = $compile(html)($scope)
@ -371,15 +372,14 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm, $translate, $c
$scope.$watch("comments", renderComments)
$scope.$watch("history", renderActivity)
$scope.$on("history:reload", -> $ctrl.loadHistory(type, objectId))
$scope.$on("object:updated", -> $ctrl.loadHistory(type, objectId))
# Events
$el.on "click", ".add-comment a.button-green", debounce 2000, (event) ->
$el.on "click", ".add-comment input.button-green", debounce 2000, (event) ->
event.preventDefault()
target = angular.element(event.currentTarget)
save(target)
$el.on "click", ".show-more", (event) ->

View File

@ -41,11 +41,12 @@ class LightboxService extends taiga.Service
$el.css('display', 'flex')
$el.find('input,textarea').first().focus()
@animationFrame.add =>
$el.addClass("open")
@animationFrame.add ->
$el.find('input,textarea').first().focus()
@animationFrame.add =>
lightboxContent.show()
defered.resolve()
@ -67,6 +68,11 @@ class LightboxService extends taiga.Service
$el.addClass('close')
if $el.hasClass("remove-on-close")
scope = $el.data("scope")
scope.$destroy()
$el.remove()
closeAll: ->
docEl = angular.element(document)
for lightboxEl in docEl.find(".lightbox.open")
@ -148,14 +154,14 @@ module.directive("lightbox", ["lightboxService", LightboxDirective])
BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loading, $qqueue, $translate) ->
link = ($scope, $el, $attrs, $model) ->
$translate($attrs.title).then (title) ->
$el.find("h2.title").text(title)
title = $translate.instant($attrs.title)
$el.find("h2.title").text(title)
unblock = $qqueue.bindAdd (item, finishCallback) =>
promise = $tgrepo.save(item)
promise.then ->
$confirm.notify("success")
$rootscope.$broadcast("history:reload")
$rootscope.$broadcast("object:updated")
$model.$setViewValue(item)
finishCallback()
@ -177,7 +183,7 @@ BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loadi
promise = $tgrepo.save($model.$modelValue)
promise.then ->
$confirm.notify("success")
$rootscope.$broadcast("history:reload")
$rootscope.$broadcast("object:updated")
promise.then null, ->
$confirm.notify("error")
@ -467,7 +473,6 @@ AssignedToLightboxDirective = (lightboxService, lightboxKeyboardNavigationServic
html = $compile(html)($scope)
$el.find("div.watchers").html(html)
lightboxKeyboardNavigationService.init($el)
closeLightbox = () ->
lightboxKeyboardNavigationService.stop()
@ -481,7 +486,7 @@ AssignedToLightboxDirective = (lightboxService, lightboxKeyboardNavigationServic
render(selectedUser)
lightboxService.open($el).then ->
$el.find('input').focus()
lightboxKeyboardNavigationService.init($el)
$scope.$watch "usersSearch", (searchingText) ->
if searchingText?
@ -562,7 +567,6 @@ WatchersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationS
html = usersTemplate(ctx)
$el.find("div.watchers").html(html)
lightboxKeyboardNavigationService.init($el)
closeLightbox = () ->
lightboxKeyboardNavigationService.stop()
@ -576,7 +580,7 @@ WatchersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationS
lightboxService.open($el).then ->
$el.find("input").focus()
lightboxKeyboardNavigationService.init($el)
lightboxKeyboardNavigationService.init($el)
$scope.$watch "usersSearch", (searchingText) ->
if not searchingText?

View File

@ -40,78 +40,84 @@ LoaderDirective = (tgLoader, $rootscope) ->
$(document.body).removeClass("loader-active")
$el.removeClass("active")
$rootscope.$on "$routeChangeSuccess", (e) ->
tgLoader.startCurrentPageLoader()
$rootscope.$on "$locationChangeSuccess", (e) ->
tgLoader.reset()
return {
link: link
}
module.directive("tgLoader", ["tgLoader", "$rootScope", LoaderDirective])
Loader = () ->
forceDisabled = false
defaultConfig = {
enabled: false,
Loader = ($rootscope) ->
config = {
minTime: 300
}
config = _.merge({}, defaultConfig)
open = false
startLoadTime = 0
requestCount = 0
lastResponseDate = 0
@.add = () ->
return () ->
if !forceDisabled
config.enabled = true
pageLoaded = (force = false) ->
if startLoadTime
timeoutValue = 0
if !force
endTime = new Date().getTime()
diff = endTime - startLoadTime
if diff < config.minTime
timeoutValue = config.minTime - diff
timeout timeoutValue, ->
$rootscope.$broadcast("loader:end")
open = false
window.prerenderReady = true # Needed by Prerender Server
@.$get = ["$rootScope", ($rootscope) ->
startLoadTime = 0
requestCount = 0
lastResponseDate = 0
reset = () ->
config = _.merge({}, defaultConfig)
autoClose = () ->
maxAuto = 5000
timeoutAuto = setTimeout (() ->
pageLoaded()
pageLoaded = (force = false) ->
if startLoadTime
timeoutValue = 0
clearInterval(intervalAuto)
), maxAuto
if !force
endTime = new Date().getTime()
diff = endTime - startLoadTime
intervalAuto = setInterval (() ->
if lastResponseDate && requestCount == 0
pageLoaded()
if diff < config.minTime
timeoutValue = config.minTime - diff
clearInterval(intervalAuto)
clearTimeout(timeoutAuto)
), 50
timeout(timeoutValue, -> $rootscope.$broadcast("loader:end"))
start = () ->
startLoadTime = new Date().getTime()
$rootscope.$broadcast("loader:start")
open = true
start = () ->
startLoadTime = new Date().getTime()
$rootscope.$broadcast("loader:start")
return {
pageLoaded: pageLoaded
start: (auto=false) ->
if !open
start()
autoClose() if auto
onStart: (fn) ->
$rootscope.$on("loader:start", fn)
return {
reset: reset
pageLoaded: pageLoaded
start: start
startCurrentPageLoader: () ->
if config.enabled
start()
onEnd: (fn) ->
$rootscope.$on("loader:end", fn)
onStart: (fn) ->
$rootscope.$on("loader:start", fn)
logRequest: () ->
requestCount++
onEnd: (fn) ->
$rootscope.$on("loader:end", fn)
logResponse: () ->
requestCount--
lastResponseDate = new Date().getTime()
}
preventLoading: () ->
forceDisabled = true
disablePreventLoading: () ->
forceDisabled = false
}
]
Loader.$inject = ["$rootScope"]
return
module.provider("tgLoader", [Loader])
module.factory("tgLoader", Loader)

View File

@ -285,7 +285,7 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $qqueue, $template, $compi
$model.$setViewValue(model)
onSuccess = ->
$rootScope.$broadcast("history:reload")
$rootScope.$broadcast("object:updated")
onError = ->
$confirm.notify("error")
model.revert()
@ -306,7 +306,7 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $qqueue, $template, $compi
$model.$setViewValue(model)
onSuccess = ->
$rootScope.$broadcast("history:reload")
$rootScope.$broadcast("object:updated")
onError = ->
$confirm.notify("error")
model.revert()

View File

@ -29,7 +29,7 @@ trim = @.taiga.trim
module = angular.module("taigaFeedback", [])
FeedbackDirective = ($lightboxService, $repo, $confirm, $loading)->
FeedbackDirective = ($lightboxService, $repo, $confirm, $loading, feedbackService)->
link = ($scope, $el, $attrs) ->
form = $el.find("form").checksley()
@ -56,16 +56,23 @@ FeedbackDirective = ($lightboxService, $repo, $confirm, $loading)->
$el.on "submit", "form", submit
$scope.$on "feedback:show", ->
$scope.$apply ->
$scope.feedback = {}
openLightbox = ->
$scope.feedback = {}
$lightboxService.open($el)
$el.find("textarea").focus()
$scope.$on "$destroy", ->
$el.off()
return {link:link}
openLightbox()
module.directive("tgLbFeedback", ["lightboxService", "$tgRepo", "$tgConfirm", "$tgLoading", FeedbackDirective])
directive = {
link: link,
templateUrl: "common/lightbox-feedback.html"
scope: {}
}
return directive
module.directive("tgLbFeedback", ["lightboxService", "$tgRepo", "$tgConfirm",
"$tgLoading", "tgFeedbackService", FeedbackDirective])

View File

@ -44,15 +44,14 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
"$q",
"$tgLocation",
"$log",
"$appTitle",
"tgAppMetaService",
"$tgAnalytics",
"$tgNavUrls",
"$translate",
"tgLoader"
"$translate"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
@log, @appTitle, @analytics, @navUrls, @translate, tgLoader) ->
@log, @appMetaService, @analytics, @navUrls, @translate) ->
@scope.issueRef = @params.issueref
@scope.sectionName = @translate.instant("ISSUES.SECTION_NAME")
@.initializeEventHandlers()
@ -61,33 +60,45 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
# On Success
promise.then =>
@appTitle.set(@scope.issue.subject + " - " + @scope.project.name)
@._setMeta()
@.initializeOnDeleteGoToUrl()
# On Error
promise.then null, @.onInitialDataError.bind(@)
# Finally
promise.finally tgLoader.pageLoaded
_setMeta: ->
title = @translate.instant("ISSUE.PAGE_TITLE", {
issueRef: "##{@scope.issue.ref}"
issueSubject: @scope.issue.subject
projectName: @scope.project.name
})
description = @translate.instant("ISSUE.PAGE_DESCRIPTION", {
issueStatus: @scope.statusById[@scope.issue.status]?.name or "--"
issueType: @scope.typeById[@scope.issue.type]?.name or "--"
issueSeverity: @scope.severityById[@scope.issue.severity]?.name or "--"
issuePriority: @scope.priorityById[@scope.issue.priority]?.name or "--"
issueDescription: angular.element(@scope.issue.description_html or "").text()
})
@appMetaService.setAll(title, description)
initializeEventHandlers: ->
@scope.$on "attachment:create", =>
@rootscope.$broadcast("history:reload")
@rootscope.$broadcast("object:updated")
@analytics.trackEvent("attachment", "create", "create attachment on issue", 1)
@scope.$on "attachment:edit", =>
@rootscope.$broadcast("history:reload")
@rootscope.$broadcast("object:updated")
@scope.$on "attachment:delete", =>
@rootscope.$broadcast("history:reload")
@rootscope.$broadcast("object:updated")
@scope.$on "promote-issue-to-us:success", =>
@analytics.trackEvent("issue", "promoteToUserstory", "promote issue to userstory", 1)
@rootscope.$broadcast("history:reload")
@rootscope.$broadcast("object:updated")
@.loadIssue()
@scope.$on "custom-attributes-values:edit", =>
@rootscope.$broadcast("history:reload")
@rootscope.$broadcast("object:updated")
initializeOnDeleteGoToUrl: ->
ctx = {project: @scope.project.slug}
@ -229,7 +240,7 @@ IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $t
onSuccess = ->
$confirm.notify("success")
$model.$setViewValue(issue)
$rootScope.$broadcast("history:reload")
$rootScope.$broadcast("object:updated")
$loading.finish($el.find(".level-name"))
onError = ->
$confirm.notify("error")
@ -313,7 +324,7 @@ IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $tem
onSuccess = ->
$confirm.notify("success")
$model.$setViewValue(issue)
$rootScope.$broadcast("history:reload")
$rootScope.$broadcast("object:updated")
$loading.finish($el.find(".level-name"))
onError = ->
@ -399,7 +410,7 @@ IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue,
onSuccess = ->
$confirm.notify("success")
$model.$setViewValue(issue)
$rootScope.$broadcast("history:reload")
$rootScope.$broadcast("object:updated")
$loading.finish($el.find(".level-name"))
onError = ->
$confirm.notify("error")
@ -486,7 +497,7 @@ IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue,
onSuccess = ->
$confirm.notify("success")
$model.$setViewValue(issue)
$rootScope.$broadcast("history:reload")
$rootScope.$broadcast("object:updated")
$loading.finish($el.find(".level-name"))
onError = ->
$confirm.notify("error")

View File

@ -47,18 +47,16 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
"$routeParams",
"$q",
"$tgLocation",
"$appTitle",
"tgAppMetaService",
"$tgNavUrls",
"$tgEvents",
"$tgAnalytics",
"tgLoader",
"$translate"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @urls, @params, @q, @location, @appTitle,
@navUrls, @events, @analytics, tgLoader, @translate) ->
@scope.sectionName = @translate.instant("ISSUES.LIST_SECTION_NAME")
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @urls, @params, @q, @location, @appMetaService,
@navUrls, @events, @analytics, @translate) ->
@scope.sectionName = "Issues"
@scope.filters = {}
if _.isEmpty(@location.search())
@ -72,14 +70,16 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
# On Success
promise.then =>
@appTitle.set("Issues - " + @scope.project.name)
title = @translate.instant("ISSUES.PAGE_TITLE", {projectName: @scope.project.name})
description = @translate.instant("ISSUES.PAGE_DESCRIPTION", {
projectName: @scope.project.name,
projectDescription: @scope.project.description
})
@appMetaService.setAll(title, description)
# On Error
promise.then null, @.onInitialDataError.bind(@)
# Finally
promise.finally tgLoader.pageLoaded
@scope.$on "issueform:new:success", =>
@analytics.trackEvent("issue", "create", "create issue on issues list", 1)
@.loadIssues()
@ -440,7 +440,7 @@ module.directive("tgIssues", ["$log", "$tgLocation", "$tgTemplate", "$compile",
## Issues Filters Directive
#############################################################################
IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template, $translate, $compile) ->
IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template, $translate, $compile, $auth) ->
template = $template.get("issue/issues-filters.html", true)
templateSelected = $template.get("issue/issues-filters-selected.html", true)
@ -477,7 +477,7 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template, $
html = $compile(html)($scope)
$el.find(".filters-applied").html(html)
if selectedFilters.length > 0
if $auth.isAuthenticated() && selectedFilters.length > 0
$el.find(".save-filters").show()
else
$el.find(".save-filters").hide()
@ -663,7 +663,7 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template, $
return {link:link}
module.directive("tgIssuesFilters", ["$log", "$tgLocation", "$tgResources", "$tgConfirm", "$tgLoading",
"$tgTemplate", "$translate", "$compile", IssuesFiltersDirective])
"$tgTemplate", "$translate", "$compile", "$tgAuth", IssuesFiltersDirective])
#############################################################################
@ -719,6 +719,7 @@ IssueStatusInlineEditionDirective = ($repo, $template, $rootscope) ->
$scope.$apply () ->
$repo.save(issue).then ->
$ctrl.loadIssues()
for filter in $scope.filters.statuses
if filter.id == issue.status
@ -726,21 +727,6 @@ IssueStatusInlineEditionDirective = ($repo, $template, $rootscope) ->
$rootscope.$broadcast("filters:issueupdate", $scope.filters)
filtering = false
for filter in $scope.filters.statuses
if filter.selected == true
filtering = true
if filter.id == issue.status
return
if not filtering
return
for el, i in $scope.issues
if el and el.id == issue.id
$scope.issues.splice(i, 1)
for filter in $scope.filters.statuses
if filter.id == issue.status
filter.count++

View File

@ -58,16 +58,15 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
"$routeParams",
"$q",
"$tgLocation",
"$appTitle",
"tgAppMetaService",
"$tgNavUrls",
"$tgEvents",
"$tgAnalytics",
"tgLoader",
"$translate"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
@appTitle, @navUrls, @events, @analytics, tgLoader, @translate) ->
@appMetaService, @navUrls, @events, @analytics, @translate) ->
bindMethods(@)
@ -79,14 +78,16 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
# On Success
promise.then =>
@appTitle.set("Kanban - " + @scope.project.name)
title = @translate.instant("KANBAN.PAGE_TITLE", {projectName: @scope.project.name})
description = @translate.instant("KANBAN.PAGE_DESCRIPTION", {
projectName: @scope.project.name,
projectDescription: @scope.project.description
})
@appMetaService.setAll(title, description)
# On Error
promise.then null, @.onInitialDataError.bind(@)
# Finally
promise.finally tgLoader.pageLoaded
initializeEventHandlers: ->
@scope.$on "usform:new:success", =>
@.loadUserstories()

View File

@ -1,316 +0,0 @@
###
# 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/nav.coffee
###
taiga = @.taiga
groupBy = @.taiga.groupBy
bindOnce = @.taiga.bindOnce
timeout = @.taiga.timeout
module = angular.module("taigaNavMenu", [])
#############################################################################
## Projects Navigation
#############################################################################
class ProjectsNavigationController extends taiga.Controller
@.$inject = ["$scope", "$rootScope", "$tgResources", "$tgNavUrls", "$projectUrl"]
constructor: (@scope, @rootscope, @rs, @navurls, @projectUrl) ->
promise = @.loadInitialData()
promise.then null, ->
console.log "FAIL"
# TODO
# Listen when someone wants to reload all the projects
@scope.$on "projects:reload", =>
@.loadInitialData()
# Listen when someone has reloaded a project
@scope.$on "project:loaded", (ctx, project) =>
@.loadInitialData()
loadInitialData: ->
return @rs.projects.listByMember(@rootscope.user?.id).then (projects) =>
for project in projects
project.url = @projectUrl.get(project)
@scope.projects = projects
@scope.filteredProjects = projects
@scope.filterText = ""
return projects
newProject: ->
@scope.$apply () =>
@rootscope.$broadcast("projects:create")
filterProjects: (text) ->
@scope.filteredProjects = _.filter @scope.projects, (project) ->
project.name.toLowerCase().indexOf(text) > -1
@scope.filterText = text
@rootscope.$broadcast("projects:filtered")
module.controller("ProjectsNavigationController", ProjectsNavigationController)
ProjectsNavigationDirective = ($rootscope, animationFrame, $timeout, tgLoader, $location, $compile, $template) ->
baseTemplate = $template.get("project/project-navigation-base.html", true)
projectsTemplate = $template.get("project/project-navigation-list.html", true)
overlay = $(".projects-nav-overlay")
loadingStart = 0
hideMenu = () ->
if overlay.is(':visible')
difftime = new Date().getTime() - loadingStart
timeoutValue = 0
if (difftime < 1000)
timeoutValue = 1000 - timeoutValue
timeout timeoutValue, ->
overlay.one 'transitionend', () ->
$(document.body)
.removeClass("loading-project open-projects-nav closed-projects-nav")
.css("overflow-x", "visible")
overlay.hide()
$(document.body).addClass("closed-projects-nav")
tgLoader.disablePreventLoading()
link = ($scope, $el, $attrs, $ctrls) ->
$ctrl = $ctrls[0]
$rootscope.$on("project:loaded", hideMenu)
renderProjects = (projects) ->
html = projectsTemplate({projects: projects})
$el.find(".projects-list").html(html)
$scope.$emit("regenerate:project-pagination")
render = (projects) ->
$el.html($compile(baseTemplate())($scope))
renderProjects(projects)
overlay.on 'click', () ->
hideMenu()
$(document).on 'keydown', (e) =>
code = if e.keyCode then e.keyCode else e.which
if code == 27
hideMenu()
$scope.$on "nav:projects-list:open", ->
if !$(document.body).hasClass("open-projects-nav")
animationFrame.add () => overlay.show()
animationFrame.add(
() => $(document.body).css("overflow-x", "hidden")
() => $(document.body).toggleClass("open-projects-nav")
)
$el.on "click", ".projects-list > li > a", (event) ->
# HACK: to solve a problem with the loader when the next url
# is equal to the current one
target = angular.element(event.currentTarget)
nextUrl = target.prop("href")
currentUrl = $location.absUrl()
if nextUrl == currentUrl
hideMenu()
return
# END HACK
$(document.body).addClass('loading-project')
tgLoader.preventLoading()
loadingStart = new Date().getTime()
$el.on "click", ".create-project-button", (event) ->
event.preventDefault()
$ctrl.newProject()
$el.on "keyup", ".search-project", (event) ->
target = angular.element(event.currentTarget)
$ctrl.filterProjects(target.val())
$scope.$on "projects:filtered", ->
renderProjects($scope.filteredProjects)
$scope.$watch "projects", (projects) ->
render(projects) if projects?
return {
require: ["tgProjectsNav"]
controller: ProjectsNavigationController
link: link
}
module.directive("tgProjectsNav", ["$rootScope", "animationFrame", "$timeout", "tgLoader", "$tgLocation", "$compile",
"$tgTemplate", ProjectsNavigationDirective])
#############################################################################
## Project
#############################################################################
ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $navUrls, $config, $template) ->
menuEntriesTemplate = $template.get("project/project-menu.html", true)
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">
<style>
path {
fill:#f5f5f5;
opacity:0.7;
}
</style>
<g transform="translate(-307.87667,-465.22863)">
<g class="bottom">
<path transform="matrix(-0.14066483,0.99005727,-0.99005727,0.14066483,0,0)" d="m561.8-506.6 42 0 0 42-42 0z" />
<path transform="matrix(0.14066483,-0.99005727,0.99005727,-0.14066483,0,0)" d="m-645.7 422.6 42 0 0 42-42 0z" />
<path transform="matrix(0.99005727,0.14066483,0.14066483,0.99005727,0,0)" d="m266.6 451.9 42 0 0 42-42 0z" />
<path transform="matrix(-0.99005727,-0.14066483,-0.14066483,-0.99005727,0,0)" d="m-350.6-535.9 42 0 0 42-42 0z" />
</g>
<g class="top">
<path transform="matrix(-0.60061118,-0.79954125,0.60061118,-0.79954125,0,0)" d="m-687.1-62.7 42 0 0 42-42 0z" />
<path transform="matrix(-0.79954125,0.60061118,-0.79954125,-0.60061118,0,0)" d="m166.6-719.6 42 0 0 42-42 0z" />
<path transform="matrix(0.60061118,0.79954125,-0.60061118,0.79954125,0,0)" d="m603.1-21.3 42 0 0 42-42 0z" />
<path transform="matrix(0.79954125,-0.60061118,0.79954125,0.60061118,0,0)" d="m-250.7 635.8 42 0 0 42-42 0z" />
<path transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)" d="m630.3 100 22.6 0 0 22.6-22.6 0z" />
</g>
</g>
</svg>
<span class="item">taiga<sup>[beta]</sup></span>
</div>
<div class="menu-container"></div>
""")
# If the last page was kanban or backlog and
# the new one is the task detail or the us details
# this method preserve the last section name.
getSectionName = ($el, sectionName, project) ->
oldSectionName = $el.find("a.active").parent().attr("id")?.replace("nav-", "")
if sectionName == "backlog-kanban"
if oldSectionName in ["backlog", "kanban"]
sectionName = oldSectionName
else if project.is_backlog_activated && !project.is_kanban_activated
sectionName = "backlog"
else if !project.is_backlog_activated && project.is_kanban_activated
sectionName = "kanban"
return sectionName
renderMainMenu = ($el) ->
html = mainTemplate({})
$el.html(html)
# WARNING: this code has traces of slighty hacky parts
# This rerenders and compiles the navigation when ng-view
# content loaded signal is raised using inner scope.
renderMenuEntries = ($el, targetScope, project={}) ->
container = $el.find(".menu-container")
sectionName = getSectionName($el, targetScope.section, project)
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")
container.replaceWith(dom)
videoConferenceUrl = (project) ->
urlFixer = (url) -> return url
if project.videoconferences == "appear-in"
baseUrl = "https://appear.in/"
else if project.videoconferences == "talky"
baseUrl = "https://talky.io/"
else if project.videoconferences == "jitsi"
baseUrl = "https://meet.jit.si/"
urlFixer = (url) -> return url.replace(/ /g, "").replace(/-/g, "")
else
return ""
if project.videoconferences_salt
url = "#{project.slug}-#{project.videoconferences_salt}"
else
url = "#{project.slug}"
url = urlFixer(url)
return baseUrl + url
link = ($scope, $el, $attrs, $ctrl) ->
renderMainMenu($el)
project = null
$el.on "click", ".logo", (event) ->
event.preventDefault()
target = angular.element(event.currentTarget)
$rootscope.$broadcast("nav:projects-list:open")
$el.on "click", ".user-settings .avatar", (event) ->
event.preventDefault()
$el.find(".user-settings .popover").popover().open()
$el.on "click", ".logout", (event) ->
event.preventDefault()
$auth.logout()
$scope.$apply ->
$location.path($navUrls.resolve("login"))
$el.on "click", "#nav-search > a", (event) ->
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()
$scope.$on "project:loaded", (ctx, newProject) ->
project = newProject
if $el.hasClass("hidden")
$el.removeClass("hidden")
project.videoconferenceUrl = videoConferenceUrl(project)
renderMenuEntries($el, ctx.targetScope, project)
return {link: link}
module.directive("tgProjectMenu", ["$log", "$compile", "$tgAuth", "$rootScope", "$tgAuth", "$tgLocation",
"$tgNavUrls", "$tgConfig", "$tgTemplate", ProjectMenuDirective])

View File

@ -26,7 +26,7 @@ debounce = @.taiga.debounce
module = angular.module("taigaProject")
CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $projectUrl, $loading, lightboxService, $cacheFactory, $translate) ->
CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $projectUrl, $loading, lightboxService, $cacheFactory, $translate, currentUserService) ->
link = ($scope, $el, attrs) ->
$scope.data = {}
$scope.templates = []
@ -46,6 +46,7 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
$location.url($projectUrl.get(response))
lightboxService.close($el)
currentUserService._loadProjects()
onErrorSubmit = (response) ->
$loading.finish(submitButton)
@ -69,7 +70,7 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
promise = $repo.create("projects", $scope.data)
promise.then(onSuccessSubmit, onErrorSubmit)
$scope.$on "projects:create", ->
openLightbox = ->
$scope.data = {
total_story_points: 100
total_milestones: 5
@ -125,17 +126,30 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
event.preventDefault()
lightboxService.close($el)
return {link:link}
$scope.$on "$destroy", ->
$el.off()
module.directive("tgLbCreateProject", ["$rootScope", "$tgRepo", "$tgConfirm", "$location", "$tgNavUrls",
"$tgResources", "$projectUrl", "$tgLoading", "lightboxService", "$cacheFactory", "$translate", CreateProject])
openLightbox()
directive = {
link: link,
templateUrl: "project/wizard-create-project.html"
scope: {}
}
return directive
module.directive("tgLbCreateProject", ["$rootScope", "$tgRepo", "$tgConfirm",
"$location", "$tgNavUrls", "$tgResources", "$projectUrl", "$tgLoading",
"lightboxService", "$cacheFactory", "$translate", "tgCurrentUserService", CreateProject])
#############################################################################
## Delete Project Lightbox Directive
#############################################################################
DeleteProjectDirective = ($repo, $rootscope, $auth, $location, $navUrls, $confirm, lightboxService, tgLoader) ->
DeleteProjectDirective = ($repo, $rootscope, $auth, $location, $navUrls, $confirm, lightboxService, tgLoader, currentUserService) ->
link = ($scope, $el, $attrs) ->
projectToDelete = null
$scope.$on "deletelightbox:new", (ctx, project)->
@ -156,6 +170,7 @@ DeleteProjectDirective = ($repo, $rootscope, $auth, $location, $navUrls, $confir
$rootscope.$broadcast("projects:reload")
$location.path($navUrls.resolve("home"))
$confirm.notify("success")
currentUserService._loadProjects()
# FIXME: error handling?
promise.then null, ->
@ -173,4 +188,4 @@ DeleteProjectDirective = ($repo, $rootscope, $auth, $location, $navUrls, $confir
return {link:link}
module.directive("tgLbDeleteProject", ["$tgRepo", "$rootScope", "$tgAuth", "$tgLocation", "$tgNavUrls",
"$tgConfirm", "lightboxService", "tgLoader", DeleteProjectDirective])
"$tgConfirm", "lightboxService", "tgLoader", "tgCurrentUserService", DeleteProjectDirective])

View File

@ -1,269 +0,0 @@
###
# 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/common/attachments.coffee
###
taiga = @.taiga
module = angular.module("taigaProject")
bindOnce = @.taiga.bindOnce
class ProjectsController extends taiga.Controller
@.$inject = [
"$scope",
"$q",
"$tgResources",
"$rootScope",
"$tgNavUrls",
"$tgAuth",
"$tgLocation",
"$appTitle",
"$projectUrl",
"tgLoader"
]
constructor: (@scope, @q, @rs, @rootscope, @navUrls, @auth, @location, @appTitle, @projectUrl,
tgLoader) ->
@appTitle.set("Projects")
if !@auth.isAuthenticated()
@location.path(@navUrls.resolve("login"))
else
tgLoader.start()
@.user = @auth.getUser()
@.projects = []
promise = @.loadInitialData()
promise.then () =>
@scope.$emit("projects:loaded", @.projects)
promise.then null, @.onInitialDataError.bind(@)
# Finally
promise.finally tgLoader.pageLoaded
loadInitialData: ->
return @rs.projects.listByMember(@rootscope.user?.id).then (projects) =>
@.projects = {'recents': projects.slice(0, 8), 'all': projects}
for project in projects
project.url = @projectUrl.get(project)
return projects
newProject: ->
@rootscope.$broadcast("projects:create")
logout: ->
@auth.logout()
@location.path(@navUrls.resolve("login"))
module.controller("ProjectsController", ProjectsController)
class ProjectController extends taiga.Controller
@.$inject = [
"$scope",
"$tgResources",
"$tgRepo",
"$routeParams",
"$q",
"$rootScope",
"$appTitle",
"$tgLocation",
"$tgNavUrls"
]
constructor: (@scope, @rs, @repo, @params, @q, @rootscope, @appTitle, @location, @navUrls) ->
promise = @.loadInitialData()
promise.then () =>
@appTitle.set(@scope.project.name)
@scope.$emit("regenerate:project-pagination")
promise.then null, @.onInitialDataError.bind(@)
loadInitialData: ->
# Resolve project slug
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project
return data
return promise.then(=> @.loadPageData())
.then(=> @scope.$emit("project:loaded", @scope.project))
loadPageData: ->
return @q.all([
@.loadProjectStats(),
@.loadProject()])
loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) =>
@scope.project = project
return project
loadProjectStats: ->
return @rs.projects.stats(@scope.projectId).then (stats) =>
@scope.stats = stats
return stats
module.controller("ProjectController", ProjectController)
ProjectsPaginationDirective = ($timeout) ->
link = ($scope, $el, $attrs) ->
prevBtn = $el.find(".v-pagination-previous")
nextBtn = $el.find(".v-pagination-next")
container = $el.find("ul")
pageSize = 0
containerSize = 0
render = ->
pageSize = $el.find(".v-pagination-list").height()
if container.find("li").length
if hasPagination()
if hasNextPage()
visible(nextBtn)
else
hide(nextBtn)
if hasPrevPage()
visible(prevBtn)
else
hide(prevBtn)
else
remove()
else
remove()
hasPagination = ->
containerSize = container.height()
return containerSize > pageSize
hasPrevPage = (top) ->
if !top?
top = -parseInt(container.css('top'), 10) || 0
return top != 0
hasNextPage = (top) ->
containerSize = container.height()
if !top
top = -parseInt(container.css('top'), 10) || 0
return containerSize > pageSize && top + pageSize < containerSize
nextPage = (callback) ->
top = parseInt(container.css('top'), 10)
newTop = top - pageSize
lastLi = $el.find(".v-pagination-list li:last-child")
maxTop = -((lastLi.position().top + lastLi.outerHeight()) - pageSize)
newTop = maxTop if newTop < maxTop
container.animate({"top": newTop}, callback)
return newTop
prevPage = (callback) ->
top = parseInt(container.css('top'), 10)
newTop = top + pageSize
newTop = 0 if newTop > 0
container.animate({"top": newTop}, callback)
return newTop
visible = (element) ->
element.css('visibility', 'visible')
hide = (element) ->
element.css('visibility', 'hidden')
checkButtonVisibility = () ->
remove = () ->
container.css('top', 0)
hide(prevBtn)
hide(nextBtn)
$el.on "click", ".v-pagination-previous", (event) ->
event.preventDefault()
if container.is(':animated')
return
visible(nextBtn)
newTop = prevPage()
if !hasPrevPage(newTop)
hide(prevBtn)
$el.on "click", ".v-pagination-next", (event) ->
event.preventDefault()
if container.is(':animated')
return
visible(prevBtn)
newTop = -nextPage()
if !hasNextPage(newTop)
hide(nextBtn)
$scope.$on "regenerate:project-pagination", ->
remove()
render()
$(window).on "resize.projects-pagination", render
$scope.$on "$destroy", ->
$(window).off "resize.projects-pagination"
return {
link: link
}
module.directive("tgProjectsPagination", ['$timeout', ProjectsPaginationDirective])
ProjectsListDirective = ($compile, $template) ->
template = $template.get('project/project-list.html', true)
link = ($scope, $el, $attrs, $ctrls) ->
render = (projects) ->
$el.html($compile(template({projects: projects}))($scope))
$scope.$emit("regenerate:project-pagination")
$scope.$on "projects:loaded", (ctx, projects) ->
render(projects.all) if projects.all?
return {
link: link
}
module.directive("tgProjectsList", ["$compile", "$tgTemplate", ProjectsListDirective])

View File

@ -31,13 +31,17 @@ urls = {
# User
"users": "/users"
"by_username": "/users/by_username"
"users-password-recovery": "/users/password_recovery"
"users-change-password-from-recovery": "/users/change_password_from_recovery"
"users-change-password": "/users/change_password"
"users-change-email": "/users/change_email"
"users-cancel-account": "/users/cancel"
"contacts": "/users/%s/contacts"
"stats": "/users/%s/stats"
# User - Notification
"permissions": "/permissions"
"notify-policies": "/notify-policies"
# User - Storage
@ -58,6 +62,7 @@ urls = {
"projects": "/projects"
"project-templates": "/project-templates"
"project-modules": "/projects/%s/modules"
"bulk-update-projects-order": "/projects/bulk_update_order"
# Project Values - Choises
"userstory-statuses": "/userstory-statuses"
@ -125,6 +130,11 @@ urls = {
"tasks-csv": "/tasks/csv?uuid=%s"
"issues-csv": "/issues/csv?uuid=%s"
# Timeline
"timeline-profile": "/timeline/profile"
"timeline-user": "/timeline/user"
"timeline-project": "/timeline/project"
# Search
"search": "/search"
@ -183,5 +193,6 @@ module.run([
"$tgWebhooksResourcesProvider",
"$tgWebhookLogsResourcesProvider",
"$tgLocalesResourcesProvider",
"$tgUsersResourcesProvider",
initResources
])

View File

@ -41,6 +41,9 @@ resourceProvider = ($repo, $http, $urls, $storage, $q) ->
params.ref = ref
return $repo.queryOne("issues", "by_ref", params)
service.listInAllProjects = (filters) ->
return $repo.queryMany("issues", filters)
service.list = (projectId, filters, options) ->
params = {project: projectId}
params = _.extend({}, params, filters or {})

View File

@ -37,7 +37,7 @@ resourceProvider = ($config, $repo, $http, $urls, $auth, $q, $translate) ->
return $repo.queryMany("projects")
service.listByMember = (memberId) ->
params = {"member": memberId}
params = {"member": memberId, "order_by": "memberships__user_order"}
return $repo.queryMany("projects", params)
service.templates = ->
@ -54,6 +54,10 @@ resourceProvider = ($config, $repo, $http, $urls, $auth, $q, $translate) ->
service.stats = (projectId) ->
return $repo.queryOneRaw("projects", "#{projectId}/stats")
service.bulkUpdateOrder = (bulkData) ->
url = $urls.resolve("bulk-update-projects-order")
return $http.post(url, bulkData)
service.regenerate_userstories_csv_uuid = (projectId) ->
url = "#{$urls.resolve("projects")}/#{projectId}/regenerate_userstories_csv_uuid"
return $http.post(url)

View File

@ -41,6 +41,9 @@ resourceProvider = ($repo, $http, $urls, $storage) ->
params.ref = ref
return $repo.queryOne("tasks", "by_ref", params)
service.listInAllProjects = (filters) ->
return $repo.queryMany("tasks", filters)
service.list = (projectId, sprintId=null, userStoryId=null) ->
params = {project: projectId}
params.milestone = sprintId if sprintId

View File

@ -0,0 +1,47 @@
###
# 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/resources/user.coffee
###
taiga = @.taiga
sizeFormat = @.taiga.sizeFormat
resourceProvider = ($http, $urls) ->
service = {}
service.contacts = (userId, options={}) ->
url = $urls.resolve("contacts", userId)
httpOptions = {headers: {}}
if not options.enablePagination
httpOptions.headers["x-disable-pagination"] = "1"
return $http.get(url, {}, httpOptions)
.then (result) ->
return result.data
return (instance) ->
instance.users = service
module = angular.module("taigaResources")
module.factory("$tgUsersResourcesProvider", ["$tgHttp", "$tgUrls", "$q",
resourceProvider])

View File

@ -38,6 +38,9 @@ resourceProvider = ($repo, $http, $urls, $storage) ->
params.ref = ref
return $repo.queryOne("userstories", "by_ref", params)
service.listInAllProjects = (filters) ->
return $repo.queryMany("userstories", filters)
service.listUnassigned = (projectId, filters) ->
params = {"project": projectId, "milestone": "null"}
params = _.extend({}, params, filters or {})

View File

@ -43,18 +43,23 @@ class SearchController extends mixOf(taiga.Controller, taiga.PageMixin)
"$routeParams",
"$q",
"$tgLocation",
"$appTitle",
"tgAppMetaService",
"$tgNavUrls",
"tgLoader"
"$translate"
]
constructor: (@scope, @repo, @rs, @params, @q, @location, @appTitle, @navUrls, @tgLoader) ->
constructor: (@scope, @repo, @rs, @params, @q, @location, @appMetaService, @navUrls, @translate) ->
@scope.sectionName = "Search"
promise = @.loadInitialData()
promise.then () =>
@appTitle.set("Search")
title = @translate.instant("SEARCH.PAGE_TITLE", {projectName: @scope.project.name})
description = @translate.instant("SEARCH.PAGE_DESCRIPTION", {
projectName: @scope.project.name,
projectDescription: @scope.project.description
})
@appMetaService.setAll(title, description)
promise.then null, @.onInitialDataError.bind(@)
@ -63,9 +68,7 @@ class SearchController extends mixOf(taiga.Controller, taiga.PageMixin)
loadSearchData = debounceLeading(100, (t) => @.loadSearchData(t))
@scope.$watch "searchTerm", (term) =>
if not term
@tgLoader.pageLoaded()
else
if term
loadSearchData(term)
loadFilters: ->
@ -90,9 +93,6 @@ class SearchController extends mixOf(taiga.Controller, taiga.PageMixin)
@scope.searchResults = data
return data
promise.finally =>
@tgLoader.pageLoaded()
return promise
loadInitialData: ->
@ -107,7 +107,7 @@ module.controller("SearchController", SearchController)
## Search box directive
#############################################################################
SearchBoxDirective = ($lightboxService, $navurls, $location, $route)->
SearchBoxDirective = (projectService, $lightboxService, $navurls, $location, $route)->
link = ($scope, $el, $attrs) ->
project = null
@ -120,24 +120,40 @@ SearchBoxDirective = ($lightboxService, $navurls, $location, $route)->
text = $el.find("#search-text").val()
url = $navurls.resolve("project-search", {project: project.slug})
url = $navurls.resolve("project-search", {project: project.get("slug")})
$lightboxService.close($el)
$scope.$apply ->
$lightboxService.close($el)
$location.path(url)
$location.search("text", text).path(url)
$route.reload()
$scope.$on "search-box:show", (ctx, newProject)->
project = newProject
$lightboxService.open($el)
$el.find("#search-text").val("")
openLightbox = () ->
project = projectService.project
$lightboxService.open($el).then () ->
$el.find("#search-text").focus()
$el.on "submit", "form", submit
return {link:link}
openLightbox()
module.directive("tgSearchBox", ["lightboxService", "$tgNavUrls", "$tgLocation", "$route", SearchBoxDirective])
return {
templateUrl: "search/lightbox-search.html",
link:link
}
SearchBoxDirective.$inject = [
"tgProjectService",
"lightboxService",
"$tgNavUrls",
"$tgLocation",
"$route"
]
module.directive("tgSearchBox", SearchBoxDirective)
#############################################################################
@ -154,12 +170,15 @@ SearchDirective = ($log, $compile, $templatecache, $routeparams, $location) ->
selectedSectionName = null
selectedSectionData = null
for name, value of data
continue if name == "count"
if value.length > maxVal
maxVal = value.length
selectedSectionName = name
selectedSectionData = value
if data
for name in ["userstories", "issues", "tasks", "wikipages"]
value = data[name]
if value.length > maxVal
maxVal = value.length
selectedSectionName = name
selectedSectionData = value
break;
if maxVal == 0
return {name: "userstories", value: []}

View File

@ -44,17 +44,16 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
"$tgResources",
"$routeParams",
"$q",
"$appTitle",
"tgAppMetaService",
"$tgLocation",
"$tgNavUrls"
"$tgEvents"
"$tgAnalytics",
"tgLoader"
"$translate"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @appTitle, @location, @navUrls,
@events, @analytics, tgLoader, @translate) ->
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @appMetaService, @location, @navUrls,
@events, @analytics, @translate) ->
bindMethods(@)
@scope.sectionName = @translate.instant("TASKBOARD.SECTION_NAME")
@ -63,14 +62,30 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
promise = @.loadInitialData()
# On Success
promise.then =>
@appTitle.set("Taskboard - " + @scope.project.name)
promise.then => @._setMeta()
# On Error
promise.then null, @.onInitialDataError.bind(@)
# Finally
promise.finally tgLoader.pageLoaded
_setMeta: ->
prettyDate = @translate.instant("BACKLOG.SPRINTS.DATE")
title = @translate.instant("TASKBOARD.PAGE_TITLE", {
projectName: @scope.project.name
sprintName: @scope.sprint.name
})
description = @translate.instant("TASKBOARD.PAGE_DESCRIPTION", {
projectName: @scope.project.name
sprintName: @scope.sprint.name
startDate: moment(@scope.sprint.estimated_start).format(prettyDate)
endDate: moment(@scope.sprint.estimated_finish).format(prettyDate)
completedPercentage: @scope.stats.completedPercentage or "0"
completedPoints: @scope.stats.completedPointsSum or "--"
totalPoints: @scope.stats.totalPointsSum or "--"
openTasks: @scope.stats.openTasks or "--"
totalTasks: @scope.stats.total_tasks or "--"
})
@appMetaService.setAll(title, description)
initializeEventHandlers: ->
# TODO: Reload entire taskboard after create/edit tasks seems
@ -136,7 +151,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
@scope.stats.remainingPointsSum = remainingPointsSum
@scope.stats.remainingTasks = remainingTasks
if stats.totalPointsSum
@scope.stats.completedPercentage = Math.round(100 * stats.completedPointsSum / stats.totalPointsSum)
@scope.stats.completedPercentage = Math.round(100*stats.completedPointsSum/stats.totalPointsSum)
else
@scope.stats.completedPercentage = 0
@ -259,7 +274,7 @@ TaskboardDirective = ($rootscope) ->
$el.on "click", ".toggle-analytics-visibility", (event) ->
event.preventDefault()
target = angular.element(event.currentTarget)
target.toggleClass('active');
target.toggleClass('active')
$rootscope.$broadcast("taskboard:graph:toggle-visibility")
tableBodyDom = $el.find(".taskboard-table-body")

View File

@ -42,15 +42,14 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
"$q",
"$tgLocation",
"$log",
"$appTitle",
"tgAppMetaService",
"$tgNavUrls",
"$tgAnalytics",
"$translate",
"tgLoader"
"$translate"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
@log, @appTitle, @navUrls, @analytics, @translate, tgLoader) ->
@log, @appMetaService, @navUrls, @analytics, @translate) ->
@scope.taskRef = @params.taskref
@scope.sectionName = @translate.instant("TASK.SECTION_NAME")
@.initializeEventHandlers()
@ -58,23 +57,33 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
promise = @.loadInitialData()
promise.then () =>
@appTitle.set(@scope.task.subject + " - " + @scope.project.name)
@._setMeta()
@.initializeOnDeleteGoToUrl()
promise.then null, @.onInitialDataError.bind(@)
promise.finally tgLoader.pageLoaded
_setMeta: ->
title = @translate.instant("TASK.PAGE_TITLE", {
taskRef: "##{@scope.task.ref}"
taskSubject: @scope.task.subject
projectName: @scope.project.name
})
description = @translate.instant("TASK.PAGE_DESCRIPTION", {
taskStatus: @scope.statusById[@scope.task.status]?.name or "--"
taskDescription: angular.element(@scope.task.description_html or "").text()
})
@appMetaService.setAll(title, description)
initializeEventHandlers: ->
@scope.$on "attachment:create", =>
@analytics.trackEvent("attachment", "create", "create attachment on task", 1)
@rootscope.$broadcast("history:reload")
@rootscope.$broadcast("object:updated")
@scope.$on "attachment:edit", =>
@rootscope.$broadcast("history:reload")
@rootscope.$broadcast("object:updated")
@scope.$on "attachment:delete", =>
@rootscope.$broadcast("history:reload")
@rootscope.$broadcast("object:updated")
@scope.$on "custom-attributes-values:edit", =>
@rootscope.$broadcast("history:reload")
@rootscope.$broadcast("object:updated")
initializeOnDeleteGoToUrl: ->
ctx = {project: @scope.project.slug}
@ -169,7 +178,6 @@ TaskStatusDisplayDirective = ($template, $compile) ->
})
html = $compile(html)($scope)
$el.html(html)
$scope.$watch $attrs.ngModel, (task) ->
@ -241,7 +249,7 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $co
onSuccess = ->
$confirm.notify("success")
$rootScope.$broadcast("history:reload")
$rootScope.$broadcast("object:updated")
$loading.finish($el.find(".level-name"))
onError = ->
@ -326,7 +334,7 @@ TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue
promise.then ->
$confirm.notify("success")
$rootscope.$broadcast("history:reload")
$rootscope.$broadcast("object:updated")
promise.then null, ->
task.revert()

View File

@ -39,29 +39,30 @@ class TeamController extends mixOf(taiga.Controller, taiga.PageMixin)
"$q",
"$location",
"$tgNavUrls",
"$appTitle",
"tgAppMetaService",
"$tgAuth",
"tgLoader",
"$translate"
"$translate",
"tgProjectService"
]
constructor: (@scope, @rootscope, @repo, @rs, @params, @q, @location, @navUrls, @appTitle, @auth, tgLoader,
@translate) ->
constructor: (@scope, @rootscope, @repo, @rs, @params, @q, @location, @navUrls, @appMetaService, @auth,
@translate, @projectService) ->
@scope.sectionName = "TEAM.SECTION_NAME"
promise = @.loadInitialData()
# On Success
promise.then =>
text = @translate.instant("TEAM.APP_TITLE", {"projectName": @scope.project.name})
@appTitle.set(text)
title = @translate.instant("TEAM.PAGE_TITLE", {projectName: @scope.project.name})
description = @translate.instant("TEAM.PAGE_DESCRIPTION", {
projectName: @scope.project.name,
projectDescription: @scope.project.description
})
@appMetaService.setAll(title, description)
# On Error
promise.then null, @.onInitialDataError.bind(@)
# Finally
promise.finally tgLoader.pageLoaded
setRole: (role) ->
if role
@scope.filtersRole = role
@ -69,27 +70,30 @@ class TeamController extends mixOf(taiga.Controller, taiga.PageMixin)
@scope.filtersRole = null
loadMembers: ->
return @rs.memberships.list(@scope.projectId, {}, false).then (data) =>
currentUser = @auth.getUser()
if currentUser? and not currentUser.photo?
currentUser.photo = "/images/unnamed.png"
currentUser = @auth.getUser()
@scope.currentUser = _.find data, (membership) =>
return currentUser? and membership.user == currentUser.id
if currentUser? and not currentUser.photo?
currentUser.photo = "/images/unnamed.png"
@scope.totals = {}
_.forEach data, (membership) =>
@scope.totals[membership.user] = 0
memberships = @projectService.project.toJS().memberships
@scope.memberships = _.filter data, (membership) =>
if membership.user && (not currentUser? or membership.user != currentUser.id) && membership.is_user_active
return membership
@scope.currentUser = _.find memberships, (membership) =>
return currentUser? and membership.user == currentUser.id
for membership in @scope.memberships
if not membership.photo?
membership.photo = "/images/unnamed.png"
@scope.totals = {}
return data
_.forEach memberships, (membership) =>
@scope.totals[membership.user] = 0
@scope.memberships = _.filter memberships, (membership) =>
if membership.user && (not currentUser? or membership.user != currentUser.id)
return membership
@scope.memberships = _.filter memberships, (membership) => return membership.is_active
for membership in @scope.memberships
if not membership.photo?
membership.photo = "/images/unnamed.png"
loadProject: ->
return @rs.projects.getBySlug(@params.pslug).then (project) =>
@ -135,7 +139,9 @@ class TeamController extends mixOf(taiga.Controller, taiga.PageMixin)
promise = @.loadProject()
return promise.then (project) =>
@.fillUsersAndRoles(project.users, project.roles)
return @.loadMembers().then(=> @.loadMemberStats())
@.loadMembers()
return @.loadMemberStats()
module.controller("TeamController", TeamController)

View File

@ -46,28 +46,11 @@ class UserChangePasswordController extends mixOf(taiga.Controller, taiga.PageMix
"$translate"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @auth, @translate) ->
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls,
@auth, @translate) ->
@scope.sectionName = @translate.instant("CHANGE_PASSWORD.SECTION_NAME")
@scope.project = {}
@scope.user = @auth.getUser()
promise = @.loadInitialData()
promise.then null, @.onInitialDataError.bind(@)
loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) =>
@scope.project = project
@scope.$emit('project:loaded', project)
return project
loadInitialData: ->
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project
return data
return promise.then(=> @.loadProject())
module.controller("UserChangePasswordController", UserChangePasswordController)

View File

@ -45,41 +45,33 @@ class UserSettingsController extends mixOf(taiga.Controller, taiga.PageMixin)
"$translate"
]
constructor: (@scope, @rootscope, @config, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @auth, @translate) ->
constructor: (@scope, @rootscope, @config, @repo, @confirm, @rs, @params, @q, @location, @navUrls,
@auth, @translate) ->
@scope.sectionName = "USER_SETTINGS.MENU.SECTION_TITLE"
@scope.project = {}
@scope.user = @auth.getUser()
if !@scope.user
@location.path(@navUrls.resolve("permission-denied"))
@location.replace()
@scope.lang = @getLan()
maxFileSize = @config.get("maxUploadFileSize", null)
if maxFileSize
@translate("USER_SETTINGS.AVATAR_MAX_SIZE", {"maxFileSize": sizeFormat(maxFileSize)}).then (text) =>
@scope.maxFileSizeMsg = text
text = @translate.instant("USER_SETTINGS.AVATAR_MAX_SIZE", {"maxFileSize": sizeFormat(maxFileSize)})
@scope.maxFileSizeMsg = text
promise = @.loadInitialData()
promise.then null, @.onInitialDataError.bind(@)
loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) =>
@scope.project = project
@scope.$emit('project:loaded', project)
return project
loadLocales: ->
loadInitialData: ->
return @rs.locales.list().then (locales) =>
@scope.locales = locales
return locales
loadInitialData: ->
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project
return data
return @q.all([promise.then(=> @.loadProject()),
@.loadLocales()])
openDeleteLightbox: ->
@rootscope.$broadcast("deletelightbox:new", @scope.user)

View File

@ -46,33 +46,15 @@ class UserNotificationsController extends mixOf(taiga.Controller, taiga.PageMixi
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @auth) ->
@scope.sectionName = "USER_SETTINGS.NOTIFICATIONS.SECTION_NAME"
@scope.project = {}
@scope.user = @auth.getUser()
promise = @.loadInitialData()
promise.then null, @.onInitialDataError.bind(@)
loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) =>
@scope.project = project
@scope.$emit('project:loaded', project)
return project
loadNotifyPolicies: ->
loadInitialData: ->
return @rs.notifyPolicies.list().then (notifyPolicies) =>
@scope.notifyPolicies = notifyPolicies
return notifyPolicies
loadInitialData: ->
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project
return data
return promise.then(=> @.loadProject())
.then(=> @.loadNotifyPolicies())
module.controller("UserNotificationsController", UserNotificationsController)

View File

@ -42,15 +42,14 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
"$q",
"$tgLocation",
"$log",
"$appTitle",
"tgAppMetaService",
"$tgNavUrls",
"$tgAnalytics",
"$translate",
"tgLoader"
"$translate"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
@log, @appTitle, @navUrls, @analytics, @translate, tgLoader) ->
@log, @appMetaService, @navUrls, @analytics, @translate) ->
@scope.usRef = @params.usref
@scope.sectionName = @translate.instant("US.SECTION_NAME")
@.initializeEventHandlers()
@ -59,12 +58,32 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
# On Success
promise.then =>
@appTitle.set(@scope.us.subject + " - " + @scope.project.name)
@._setMeta()
@.initializeOnDeleteGoToUrl()
# On Error
promise.then null, @.onInitialDataError.bind(@)
promise.finally tgLoader.pageLoaded
_setMeta: ->
totalTasks = @scope.tasks.length
closedTasks = _.filter(@scope.tasks, (t) => @scope.taskStatusById[t.status].is_closed).length
progressPercentage = if totalTasks > 0 then Math.round(100 * closedTasks / totalTasks) else 0
title = @translate.instant("US.PAGE_TITLE", {
userStoryRef: "##{@scope.us.ref}"
userStorySubject: @scope.us.subject
projectName: @scope.project.name
})
description = @translate.instant("US.PAGE_DESCRIPTION", {
userStoryStatus: @scope.statusById[@scope.us.status]?.name or "--"
userStoryPoints: @scope.us.total_points
userStoryDescription: angular.element(@scope.us.description_html or "").text()
userStoryClosedTasks: closedTasks
userStoryTotalTasks: totalTasks
userStoryProgressPercentage: progressPercentage
})
@appMetaService.setAll(title, description)
initializeEventHandlers: ->
@scope.$on "related-tasks:update", =>
@ -73,16 +92,16 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
@scope.$on "attachment:create", =>
@analytics.trackEvent("attachment", "create", "create attachment on userstory", 1)
@rootscope.$broadcast("history:reload")
@rootscope.$broadcast("object:updated")
@scope.$on "attachment:edit", =>
@rootscope.$broadcast("history:reload")
@rootscope.$broadcast("object:updated")
@scope.$on "attachment:delete", =>
@rootscope.$broadcast("history:reload")
@rootscope.$broadcast("object:updated")
@scope.$on "custom-attributes-values:edit", =>
@rootscope.$broadcast("history:reload")
@rootscope.$broadcast("object:updated")
initializeOnDeleteGoToUrl: ->
ctx = {project: @scope.project.slug}
@ -299,6 +318,7 @@ UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $temp
save = $qqueue.bindAdd (status) =>
us = $model.$modelValue.clone()
us.status = status
$.fn.popover().closeAll()
@ -307,7 +327,7 @@ UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $temp
onSuccess = ->
$confirm.notify("success")
$rootScope.$broadcast("history:reload")
$rootScope.$broadcast("object:updated")
$loading.finish($el.find(".level-name"))
onError = ->
@ -389,7 +409,7 @@ UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qq
promise = $tgrepo.save($model.$modelValue)
promise.then =>
$loading.finish($el.find("label"))
$rootscope.$broadcast("history:reload")
$rootscope.$broadcast("object:updated")
promise.then null, ->
$loading.finish($el.find("label"))
@ -451,7 +471,7 @@ UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $
promise = $tgrepo.save($model.$modelValue)
promise.then =>
$loading.finish($el.find("label"))
$rootscope.$broadcast("history:reload")
$rootscope.$broadcast("object:updated")
promise.then null, ->
$loading.finish($el.find("label"))
$confirm.notify("error")

View File

@ -46,15 +46,14 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
"$tgLocation",
"$filter",
"$log",
"$appTitle",
"tgAppMetaService",
"$tgNavUrls",
"$tgAnalytics",
"tgLoader",
"$translate"
]
constructor: (@scope, @rootscope, @repo, @model, @confirm, @rs, @params, @q, @location,
@filter, @log, @appTitle, @navUrls, @analytics, tgLoader, @translate) ->
@filter, @log, @appMetaService, @navUrls, @analytics, @translate) ->
@scope.projectSlug = @params.pslug
@scope.wikiSlug = @params.slug
@scope.sectionName = "Wiki"
@ -62,12 +61,22 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
promise = @.loadInitialData()
# On Success
promise.then () =>
@appTitle.set("Wiki - " + @scope.project.name)
promise.then () => @._setMeta()
# On Error
promise.then null, @.onInitialDataError.bind(@)
promise.finally tgLoader.pageLoaded
_setMeta: ->
title = @translate.instant("WIKI.PAGE_TITLE", {
wikiPageName: @scope.wiki.slug
projectName: unslugify(@scope.wiki.slug)
})
description = @translate.instant("WIKI.PAGE_DESCRIPTION", {
wikiPageContent: angular.element(@scope.wiki.html or "").text()
totalEditions: @scope.wiki.editions or 0
lastModifiedDate: moment(@scope.wiki.modified_date).format(@translate.instant("WIKI.DATETIME"))
})
@appMetaService.setAll(title, description)
loadProject: ->
return @rs.projects.getBySlug(@params.pslug).then (project) =>
@ -109,7 +118,8 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
promise = @.loadProject()
return promise.then (project) =>
@.fillUsersAndRoles(project.users, project.roles)
@q.all([@.loadWikiLinks(), @.loadWiki()])
@q.all([@.loadWikiLinks(), @.loadWiki()]).then () =>
delete: ->
title = @translate.instant("WIKI.DELETE_LIGHTBOX_TITLE")

View File

@ -126,6 +126,19 @@ startswith = (str1, str2) ->
return _.str.startsWith(str1, str2)
truncate = (str, maxLength, suffix="...") ->
return str if (typeof str != "string") and not (str instanceof String)
out = str.slice(0)
if out.length > maxLength
out = out.substring(0, maxLength + 1)
out = out.substring(0, Math.min(out.length, out.lastIndexOf(" ")))
out = out + suffix
return out
sizeFormat = (input, precision=1) ->
if isNaN(parseFloat(input)) or not isFinite(input)
return "-"
@ -140,6 +153,37 @@ sizeFormat = (input, precision=1) ->
size = (input / Math.pow(1024, number)).toFixed(precision)
return "#{size} #{units[number]}"
stripTags = (str, exception) ->
if exception
pattern = new RegExp('<(?!' + exception + '\s*\/?)[^>]+>', 'gi')
return String(str).replace(pattern, '')
else
return String(str).replace(/<\/?[^>]+>/g, '')
replaceTags = (str, tags, replace) ->
# open tag
pattern = new RegExp('<(' + tags + ')>', 'gi')
str = str.replace(pattern, '<' + replace + '>')
# close tag
pattern = new RegExp('<\/(' + tags + ')>', 'gi')
str = str.replace(pattern, '</' + replace + '>')
return str
defineImmutableProperty = (obj, name, fn) =>
Object.defineProperty obj, name, {
get: () =>
if !_.isFunction(fn)
throw "defineImmutableProperty third param must be a function"
fn_result = fn()
if fn_result && _.isObject(fn_result)
if fn_result.size == undefined
throw "defineImmutableProperty must return immutable data"
return fn_result
}
taiga = @.taiga
taiga.nl2br = nl2br
@ -156,7 +200,11 @@ taiga.cancelTimeout = cancelTimeout
taiga.scopeDefer = scopeDefer
taiga.toString = toString
taiga.joinStr = joinStr
taiga.truncate = truncate
taiga.debounce = debounce
taiga.debounceLeading = debounceLeading
taiga.startswith = startswith
taiga.sizeFormat = sizeFormat
taiga.stripTags = stripTags
taiga.replaceTags = replaceTags
taiga.defineImmutableProperty = defineImmutableProperty

Binary file not shown.

View File

@ -0,0 +1,581 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg>
<metadata>
Created by FontForge 20110222 at Thu May 12 12:49:24 2011
By www-data
Digitized data copyright (c) 2010-2011, Google Corporation.
</metadata>
<defs>
<font id="OpenSans-Light" horiz-adv-x="1169" >
<font-face
font-family="Open Sans Light"
font-weight="300"
font-stretch="normal"
units-per-em="2048"
panose-1="2 11 3 6 3 5 4 2 2 4"
ascent="1638"
descent="-410"
x-height="1087"
cap-height="1462"
bbox="-907 -512 2218 1907"
underline-thickness="102"
underline-position="-103"
unicode-range="U+0020-2122"
/>
<missing-glyph />
<glyph glyph-name="space" unicode=" " horiz-adv-x="532"
/>
<glyph glyph-name="exclam" unicode="!" horiz-adv-x="492"
d="M276 377h-61l-29 1085h119zM164 78q0 98 80 98q82 0 82 -98t-82 -98q-80 0 -80 98z" />
<glyph glyph-name="quotedbl" unicode="&#x22;" horiz-adv-x="723"
d="M260 1462l-33 -528h-61l-33 528h127zM590 1462l-33 -528h-61l-33 528h127z" />
<glyph glyph-name="numbersign" unicode="#" horiz-adv-x="1323"
d="M967 928l-76 -398h303v-79h-320l-86 -451h-90l88 451h-360l-86 -451h-88l86 451h-283v79h299l76 398h-297v80h311l86 454h91l-89 -454h365l88 454h86l-88 -454h285v-80h-301zM440 530h363l78 398h-363z" />
<glyph glyph-name="dollar" unicode="$"
d="M991 440q0 -133 -99 -217t-274 -106v-236h-81v232q-92 2 -200.5 22.5t-172.5 50.5v103q75 -36 179.5 -61t193.5 -25v508q-145 44 -215 88t-102 104t-32 146q0 124 94.5 208.5t254.5 104.5v192h81v-190q197 -9 351 -72l-33 -90q-141 62 -318 72v-486q213 -66 293 -144
t80 -204zM881 444q0 85 -63 140.5t-200 95.5v-471q122 13 192.5 75t70.5 160zM297 1049q0 -86 57 -141t183 -93v453q-119 -16 -179.5 -76t-60.5 -143z" />
<glyph glyph-name="percent" unicode="%" horiz-adv-x="1653"
d="M211 1026q0 -186 45 -279.5t141 -93.5q193 0 193 373q0 184 -49.5 276.5t-143.5 92.5q-96 0 -141 -92.5t-45 -276.5zM688 1026q0 -226 -75 -343.5t-216 -117.5q-133 0 -208.5 120.5t-75.5 340.5q0 223 72 340t212 117q139 0 215 -120.5t76 -336.5zM1063 438
q0 -185 45 -277.5t141 -92.5q193 0 193 370q0 369 -193 369q-96 0 -141 -91.5t-45 -277.5zM1540 438q0 -226 -74 -343.5t-215 -117.5q-136 0 -211 121.5t-75 339.5q0 225 73.5 341t212.5 116q137 0 213 -120t76 -337zM1280 1462l-811 -1462h-96l811 1462h96z" />
<glyph glyph-name="ampersand" unicode="&#x26;" horiz-adv-x="1460"
d="M123 371q0 138 73.5 235t274.5 205l-75 82q-66 71 -98 139t-32 142q0 143 95.5 227t256.5 84q155 0 245.5 -81t90.5 -224q0 -105 -70 -192.5t-253 -194.5l452 -457q61 72 104 157t75 201h96q-63 -246 -209 -426l266 -268h-135l-193 197q-92 -90 -164 -131.5t-157.5 -63.5
t-194.5 -22q-209 0 -328.5 103t-119.5 288zM578 70q128 0 234.5 43.5t209.5 146.5l-483 485q-136 -72 -196.5 -122.5t-88 -109.5t-27.5 -138q0 -143 93 -224t258 -81zM373 1176q0 -79 40 -146t152 -174q159 85 221 159t62 169q0 94 -62 152.5t-168 58.5q-114 0 -179.5 -58
t-65.5 -161z" />
<glyph glyph-name="quotesingle" unicode="'" horiz-adv-x="393"
d="M260 1462l-33 -528h-61l-33 528h127z" />
<glyph glyph-name="parenleft" unicode="(" horiz-adv-x="557"
d="M82 561q0 265 77.5 496t223.5 405h113q-148 -182 -227 -412.5t-79 -486.5q0 -483 304 -887h-111q-147 170 -224 397t-77 488z" />
<glyph glyph-name="parenright" unicode=")" horiz-adv-x="557"
d="M475 561q0 -263 -77.5 -490t-223.5 -395h-111q304 404 304 887q0 257 -79 487.5t-227 411.5h113q147 -175 224 -406.5t77 -494.5z" />
<glyph glyph-name="asterisk" unicode="*" horiz-adv-x="1128"
d="M631 1556l-37 -405l405 104l21 -131l-395 -39l247 -340l-124 -71l-191 379l-180 -379l-125 71l242 340l-390 39l19 131l401 -104l-39 405h146z" />
<glyph glyph-name="plus" unicode="+"
d="M625 764h434v-82h-434v-432h-82v432h-432v82h432v434h82v-434z" />
<glyph glyph-name="comma" unicode="," horiz-adv-x="440"
d="M295 238l12 -21q-75 -265 -174 -481h-65q77 275 110 502h117z" />
<glyph glyph-name="hyphen" unicode="-" horiz-adv-x="659"
d="M92 512v82h475v-82h-475z" />
<glyph glyph-name="period" unicode="." horiz-adv-x="487"
d="M162 78q0 98 80 98q82 0 82 -98t-82 -98q-80 0 -80 98z" />
<glyph glyph-name="slash" unicode="/" horiz-adv-x="698"
d="M674 1462l-545 -1462h-104l544 1462h105z" />
<glyph glyph-name="zero" unicode="0"
d="M1055 735q0 -385 -117.5 -570t-355.5 -185q-229 0 -348 190.5t-119 564.5q0 382 115.5 566t351.5 184q231 0 352 -190.5t121 -559.5zM223 735q0 -340 89 -502.5t270 -162.5q189 0 275.5 168t86.5 497q0 324 -86.5 492t-275.5 168t-274 -168t-85 -492z" />
<glyph glyph-name="one" unicode="1"
d="M682 0h-98v1065q0 145 12 301q-15 -15 -31 -29t-309 -243l-57 71l397 297h86v-1462z" />
<glyph glyph-name="two" unicode="2"
d="M1028 0h-915v88l389 406q164 170 230 260t97 172t31 172q0 131 -86 213t-223 82q-183 0 -350 -133l-54 69q183 154 406 154q191 0 300.5 -102t109.5 -281q0 -145 -73.5 -280.5t-268.5 -334.5l-375 -385v-4h782v-96z" />
<glyph glyph-name="three" unicode="3"
d="M979 1118q0 -136 -85.5 -229t-229.5 -119v-6q176 -22 268 -112t92 -242q0 -205 -139.5 -317.5t-401.5 -112.5q-223 0 -389 83v99q84 -44 188.5 -69t196.5 -25q221 0 332 89.5t111 252.5q0 145 -113.5 223t-333.5 78h-158v96h160q182 0 288.5 86.5t106.5 234.5
q0 122 -86.5 195.5t-226.5 73.5q-109 0 -199 -30.5t-202 -104.5l-49 67q85 71 205 112.5t243 41.5q202 0 312 -95.5t110 -269.5z" />
<glyph glyph-name="four" unicode="4"
d="M1141 373h-252v-373h-94v373h-752v67l725 1030h121v-1011h252v-86zM795 459v418q0 302 14 507h-8q-20 -37 -123 -188l-516 -737h633z" />
<glyph glyph-name="five" unicode="5"
d="M537 879q234 0 368.5 -113t134.5 -311q0 -225 -140 -350t-386 -125q-109 0 -207 21.5t-164 61.5v103q108 -55 192 -76.5t179 -21.5q192 0 308 101.5t116 274.5q0 163 -113 256t-307 93q-130 0 -272 -39l-60 39l58 669h704v-96h-610l-45 -516q156 29 244 29z" />
<glyph glyph-name="six" unicode="6"
d="M131 623q0 285 77.5 479.5t220 288.5t343.5 94q94 0 172 -23v-88q-73 27 -176 27q-247 0 -384.5 -178t-154.5 -518h13q76 98 174 148t207 50q205 0 320.5 -117t115.5 -323q0 -224 -121.5 -353.5t-327.5 -129.5q-222 0 -350.5 169.5t-128.5 473.5zM610 68q164 0 255 103
t91 294q0 168 -90 262t-245 94q-102 0 -189.5 -45t-139.5 -119.5t-52 -152.5q0 -111 49.5 -213.5t134 -162.5t186.5 -60z" />
<glyph glyph-name="seven" unicode="7"
d="M334 0l602 1366h-827v96h946v-73l-604 -1389h-117z" />
<glyph glyph-name="eight" unicode="8"
d="M582 1487q186 0 299.5 -95t113.5 -257q0 -112 -70.5 -198t-228.5 -159q192 -79 270 -173t78 -228q0 -181 -126.5 -289t-339.5 -108q-221 0 -339 101t-118 294q0 131 83 230t257 169q-161 76 -227 160.5t-66 202.5q0 105 53 184.5t148.5 122.5t212.5 43zM223 360
q0 -138 93.5 -214t261.5 -76q164 0 264 80.5t100 218.5q0 124 -78.5 201.5t-302.5 162.5q-184 -71 -261 -157t-77 -216zM580 1397q-141 0 -226.5 -69.5t-85.5 -190.5q0 -70 31.5 -123.5t91 -97t199.5 -101.5q163 63 234 139t71 183q0 120 -84.5 190t-230.5 70z" />
<glyph glyph-name="nine" unicode="9"
d="M1036 842q0 -288 -75.5 -482t-220 -287t-349.5 -93q-104 0 -192 26v86q43 -14 103.5 -21.5t92.5 -7.5q247 0 387 178.5t156 520.5h-12q-73 -96 -174 -147.5t-211 -51.5q-203 0 -316.5 112t-113.5 318q0 220 124.5 356t323.5 136q144 0 252 -75.5t166.5 -221.5t58.5 -346z
M559 1397q-158 0 -252 -106.5t-94 -291.5q0 -174 87 -264t249 -90q101 0 188.5 45t139 119.5t51.5 151.5q0 117 -46.5 219t-130 159.5t-192.5 57.5z" />
<glyph glyph-name="colon" unicode=":" horiz-adv-x="487"
d="M162 78q0 98 80 98q82 0 82 -98t-82 -98q-80 0 -80 98zM162 971q0 98 80 98q82 0 82 -98q0 -53 -23.5 -76t-58.5 -23q-34 0 -57 23t-23 76z" />
<glyph glyph-name="semicolon" unicode=";" horiz-adv-x="487"
d="M303 238l12 -21q-75 -265 -174 -481h-65q29 97 62 245.5t48 256.5h117zM162 971q0 98 80 98q82 0 82 -98q0 -53 -23.5 -76t-58.5 -23q-34 0 -57 23t-23 76z" />
<glyph glyph-name="less" unicode="&#x3c;"
d="M1059 266l-948 416v61l948 474v-95l-823 -405l823 -355v-96z" />
<glyph glyph-name="equal" unicode="="
d="M111 885v82h948v-82h-948zM111 477v82h948v-82h-948z" />
<glyph glyph-name="greater" unicode="&#x3e;"
d="M111 362l823 355l-823 405v95l948 -474v-61l-948 -416v96z" />
<glyph glyph-name="question" unicode="?" horiz-adv-x="862"
d="M293 377v37q0 123 37.5 201t138.5 167l91 79q72 61 103 121t31 138q0 127 -83.5 202t-219.5 75q-79 0 -148 -17.5t-149 -56.5l-37 80q110 48 184.5 64t153.5 16q183 0 288 -98.5t105 -270.5q0 -68 -18 -119t-50.5 -94.5t-78.5 -84t-102 -87.5q-64 -54 -98.5 -98.5
t-50 -93.5t-15.5 -146v-14h-82zM260 78q0 98 80 98q82 0 82 -98t-82 -98q-80 0 -80 98z" />
<glyph glyph-name="at" unicode="@" horiz-adv-x="1815"
d="M1702 725q0 -228 -90.5 -366t-245.5 -138q-89 0 -144.5 54t-64.5 147h-4q-43 -100 -124 -150.5t-189 -50.5q-148 0 -229 96.5t-81 270.5q0 202 120.5 330.5t314.5 128.5q138 0 286 -41l-22 -464v-30q0 -104 35 -156.5t116 -52.5q103 0 168.5 116.5t65.5 303.5
q0 194 -79 340t-225.5 224.5t-334.5 78.5q-230 0 -405.5 -99.5t-270 -281.5t-94.5 -418q0 -322 167 -497.5t474 -175.5q93 0 188.5 18t231.5 70v-99q-203 -80 -414 -80q-349 0 -544 200.5t-195 557.5q0 256 108.5 460.5t307 317.5t448.5 113q215 0 380.5 -89t255 -254.5
t89.5 -383.5zM633 590q0 -143 55 -215t174 -72q255 0 273 346l16 291q-79 27 -193 27q-149 0 -237 -102.5t-88 -274.5z" />
<glyph glyph-name="A" unicode="A" horiz-adv-x="1229"
d="M911 516h-594l-204 -516h-113l588 1468h65l576 -1468h-115zM354 608h523l-199 527q-25 62 -60 172q-27 -96 -59 -174z" />
<glyph glyph-name="B" unicode="B" horiz-adv-x="1284"
d="M207 1462h401q271 0 398 -92t127 -278q0 -127 -77.5 -211.5t-226.5 -108.5v-6q175 -26 257.5 -110.5t82.5 -235.5q0 -202 -134 -311t-380 -109h-448v1462zM309 811h322q206 0 299.5 68.5t93.5 214.5t-105.5 212t-314.5 66h-295v-561zM309 721v-631h344q406 0 406 330
q0 301 -428 301h-322z" />
<glyph glyph-name="C" unicode="C" horiz-adv-x="1272"
d="M831 1391q-275 0 -433 -176t-158 -482q0 -313 149 -486t426 -173q184 0 338 47v-90q-145 -51 -362 -51q-308 0 -485 199t-177 556q0 223 84.5 393t243 262.5t368.5 92.5q214 0 383 -80l-41 -92q-160 80 -336 80z" />
<glyph glyph-name="D" unicode="D" horiz-adv-x="1446"
d="M1317 745q0 -368 -193 -556.5t-567 -188.5h-350v1462h395q350 0 532.5 -183t182.5 -534zM1206 741q0 314 -159.5 472.5t-468.5 158.5h-269v-1282h242q655 0 655 651z" />
<glyph glyph-name="E" unicode="E" horiz-adv-x="1130"
d="M1006 0h-799v1462h799v-94h-697v-553h658v-94h-658v-627h697v-94z" />
<glyph glyph-name="F" unicode="F" horiz-adv-x="1028"
d="M309 0h-102v1462h801v-94h-699v-620h660v-95h-660v-653z" />
<glyph glyph-name="G" unicode="G" horiz-adv-x="1481"
d="M782 737h539v-667q-212 -90 -477 -90q-346 0 -530.5 195.5t-184.5 553.5q0 223 91.5 395.5t262 266.5t391.5 94q239 0 429 -88l-41 -92q-190 88 -394 88q-289 0 -458.5 -178.5t-169.5 -481.5q0 -330 161 -496.5t473 -166.5q202 0 343 57v514h-435v96z" />
<glyph glyph-name="H" unicode="H" horiz-adv-x="1473"
d="M1266 0h-103v719h-854v-719h-102v1462h102v-649h854v649h103v-1462z" />
<glyph glyph-name="J" unicode="J" horiz-adv-x="506"
d="M-33 -369q-92 0 -151 27v88q78 -20 149 -20q242 0 242 264v1472h102v-1462q0 -369 -342 -369z" />
<glyph glyph-name="K" unicode="K" horiz-adv-x="1190"
d="M1190 0h-125l-561 772l-195 -172v-600h-102v1462h102v-760l162 162l573 598h130l-599 -618z" />
<glyph glyph-name="L" unicode="L" horiz-adv-x="1051"
d="M207 0v1462h102v-1366h697v-96h-799z" />
<glyph glyph-name="M" unicode="M" horiz-adv-x="1767"
d="M850 0l-545 1350h-8q8 -124 8 -254v-1096h-98v1462h158l518 -1286h6l518 1286h154v-1462h-103v1108q0 116 12 240h-8l-547 -1348h-65z" />
<glyph glyph-name="N" unicode="N" horiz-adv-x="1477"
d="M1270 0h-103l-866 1298h-8q12 -232 12 -350v-948h-98v1462h102l865 -1296h6q-9 180 -9 342v954h99v-1462z" />
<glyph glyph-name="O" unicode="O" horiz-adv-x="1565"
d="M1436 733q0 -348 -174 -550.5t-480 -202.5q-305 0 -479 202.5t-174 552.5q0 349 175.5 549.5t479.5 200.5q306 0 479 -201.5t173 -550.5zM240 733q0 -314 140 -485.5t402 -171.5q264 0 403.5 170t139.5 487q0 316 -139.5 484.5t-401.5 168.5q-261 0 -402.5 -170
t-141.5 -483z" />
<glyph glyph-name="P" unicode="P" horiz-adv-x="1198"
d="M1087 1042q0 -212 -144 -325t-408 -113h-226v-604h-102v1462h358q522 0 522 -420zM309 692h201q247 0 357 81.5t110 264.5q0 169 -104 250.5t-322 81.5h-242v-678z" />
<glyph glyph-name="Q" unicode="Q" horiz-adv-x="1565"
d="M1436 733q0 -294 -126 -486.5t-349 -246.5l333 -348h-166l-282 330l-33 -2h-31q-305 0 -479 202.5t-174 552.5q0 349 175.5 549.5t479.5 200.5q306 0 479 -201.5t173 -550.5zM240 733q0 -314 140 -485.5t402 -171.5q264 0 403.5 170t139.5 487q0 316 -139.5 484.5
t-401.5 168.5q-261 0 -402.5 -170t-141.5 -483z" />
<glyph glyph-name="R" unicode="R" horiz-adv-x="1217"
d="M309 637v-637h-102v1462h348q272 0 402 -100.5t130 -302.5q0 -147 -77.5 -248t-235.5 -145l397 -666h-122l-377 637h-363zM309 725h279q185 0 287 82.5t102 243.5q0 167 -100 243t-326 76h-242v-645z" />
<glyph glyph-name="S" unicode="S" horiz-adv-x="1116"
d="M1014 377q0 -183 -134.5 -290t-357.5 -107q-268 0 -411 59v102q158 -67 403 -67q180 0 285.5 82.5t105.5 216.5q0 83 -35 137.5t-114 99.5t-232 97q-224 77 -309.5 166.5t-85.5 238.5q0 164 128.5 267.5t330.5 103.5q206 0 387 -78l-37 -88q-182 76 -348 76
q-162 0 -258 -75t-96 -204q0 -81 29.5 -133t96.5 -93.5t230 -99.5q171 -59 257 -114.5t125.5 -126t39.5 -170.5z" />
<glyph glyph-name="T" unicode="T" horiz-adv-x="1073"
d="M588 0h-103v1366h-475v96h1053v-96h-475v-1366z" />
<glyph glyph-name="U" unicode="U" horiz-adv-x="1473"
d="M1282 1462v-946q0 -252 -146 -394t-407 -142q-254 0 -396.5 142.5t-142.5 397.5v942h103v-946q0 -211 117 -328.5t331 -117.5q209 0 324 115.5t115 320.5v956h102z" />
<glyph glyph-name="V" unicode="V" horiz-adv-x="1182"
d="M1071 1462h111l-547 -1462h-90l-545 1462h109l368 -995q84 -225 113 -338q20 75 79 233z" />
<glyph glyph-name="W" unicode="W" horiz-adv-x="1827"
d="M1372 0h-84l-321 1128q-40 139 -60 228q-16 -87 -45.5 -200t-322.5 -1156h-86l-402 1462h107l256 -942q15 -57 28 -105.5t23.5 -91t19 -82t15.5 -79.5q24 136 102 413l250 887h113l293 -1018q51 -176 73 -284q13 72 33.5 153t308.5 1149h103z" />
<glyph glyph-name="X" unicode="X" horiz-adv-x="1102"
d="M1102 0h-117l-432 682l-440 -682h-113l492 762l-447 700h115l395 -626l401 626h109l-453 -698z" />
<glyph glyph-name="Y" unicode="Y" horiz-adv-x="1081"
d="M543 662l428 800h110l-487 -897v-565h-105v557l-489 905h117z" />
<glyph glyph-name="Z" unicode="Z" horiz-adv-x="1180"
d="M1098 0h-1016v76l856 1290h-817v96h954v-76l-858 -1290h881v-96z" />
<glyph glyph-name="bracketleft" unicode="[" horiz-adv-x="653"
d="M602 -324h-428v1786h428v-94h-330v-1597h330v-95z" />
<glyph glyph-name="backslash" unicode="\" horiz-adv-x="698"
d="M127 1462l547 -1462h-103l-546 1462h102z" />
<glyph glyph-name="bracketright" unicode="]" horiz-adv-x="653"
d="M51 -229h330v1597h-330v94h428v-1786h-428v95z" />
<glyph glyph-name="asciicircum" unicode="^"
d="M88 561l465 912h68l460 -912h-100l-395 791l-398 -791h-100z" />
<glyph glyph-name="underscore" unicode="_" horiz-adv-x="842"
d="M846 -266h-850v82h850v-82z" />
<glyph glyph-name="grave" unicode="`" horiz-adv-x="1182"
d="M776 1241h-69q-96 79 -188.5 171.5t-125.5 139.5v17h142q26 -48 98.5 -142t142.5 -170v-16z" />
<glyph glyph-name="a" unicode="a" horiz-adv-x="1085"
d="M842 0l-25 172h-8q-82 -105 -168.5 -148.5t-204.5 -43.5q-160 0 -249 82t-89 227q0 159 132.5 247t383.5 93l207 6v72q0 155 -63 234t-203 79q-151 0 -313 -84l-37 86q179 84 354 84q179 0 267.5 -93t88.5 -290v-723h-73zM442 70q174 0 274.5 99.5t100.5 276.5v107
l-190 -8q-229 -11 -326.5 -71.5t-97.5 -188.5q0 -102 62.5 -158.5t176.5 -56.5z" />
<glyph glyph-name="b" unicode="b" horiz-adv-x="1219"
d="M641 1108q228 0 343.5 -143.5t115.5 -419.5q0 -271 -121.5 -418t-341.5 -147q-116 0 -209 48t-147 136h-9l-28 -164h-62v1556h99v-391q0 -88 -4 -162l-3 -85h7q62 98 149.5 144t210.5 46zM639 1018q-192 0 -275 -110t-83 -363v-17q0 -246 86.5 -353t269.5 -107
q178 0 268 124.5t90 354.5q0 471 -356 471z" />
<glyph glyph-name="c" unicode="c" horiz-adv-x="973"
d="M616 -20q-233 0 -365 147t-132 410q0 270 137 420.5t375 150.5q141 0 270 -49l-27 -88q-141 47 -245 47q-200 0 -303 -123.5t-103 -355.5q0 -220 103 -344.5t288 -124.5q148 0 275 53v-92q-104 -51 -273 -51z" />
<glyph glyph-name="d" unicode="d" horiz-adv-x="1219"
d="M580 1108q118 0 204 -43t154 -147h6q-6 126 -6 247v391h98v-1556h-65l-25 166h-8q-124 -186 -356 -186q-225 0 -344 140t-119 408q0 282 118 431t343 149zM580 1018q-178 0 -267.5 -125t-89.5 -363q0 -462 359 -462q184 0 270 107t86 353v17q0 252 -84.5 362.5
t-273.5 110.5z" />
<glyph glyph-name="e" unicode="e" horiz-adv-x="1124"
d="M621 -20q-237 0 -369.5 146t-132.5 409q0 260 128 416.5t345 156.5q192 0 303 -134t111 -364v-80h-783q2 -224 104.5 -342t293.5 -118q93 0 163.5 13t178.5 56v-90q-92 -40 -170 -54.5t-172 -14.5zM592 1020q-157 0 -252 -103.5t-111 -298.5h672q0 189 -82 295.5
t-227 106.5z" />
<glyph glyph-name="f" unicode="f" horiz-adv-x="614"
d="M586 1001h-256v-1001h-99v1001h-202v58l202 37v84q0 200 73.5 293.5t240.5 93.5q90 0 180 -27l-23 -86q-80 25 -159 25q-116 0 -164.5 -68.5t-48.5 -222.5v-101h256v-86z" />
<glyph glyph-name="g" unicode="g" horiz-adv-x="1071"
d="M1030 1087v-69l-225 -14q90 -112 90 -246q0 -157 -104.5 -254.5t-280.5 -97.5q-74 0 -104 6q-59 -31 -90 -73t-31 -89q0 -52 39.5 -76t132.5 -24h190q177 0 271 -71.5t94 -211.5q0 -172 -139.5 -265.5t-397.5 -93.5q-205 0 -317.5 79t-112.5 220q0 112 69.5 186
t188.5 101q-49 21 -78.5 59.5t-29.5 88.5q0 109 139 192q-95 39 -148 122.5t-53 191.5q0 163 103.5 261.5t279.5 98.5q107 0 166 -21h348zM150 -184q0 -224 333 -224q428 0 428 273q0 98 -67 142t-217 44h-178q-299 0 -299 -235zM233 748q0 -126 76.5 -195.5t204.5 -69.5
q136 0 208.5 69t72.5 200q0 139 -74.5 208.5t-208.5 69.5q-130 0 -204.5 -74.5t-74.5 -207.5z" />
<glyph glyph-name="h" unicode="h" horiz-adv-x="1208"
d="M940 0v705q0 164 -69 238.5t-214 74.5q-195 0 -285.5 -98.5t-90.5 -319.5v-600h-99v1556h99v-495l-5 -139h7q61 98 154 142t231 44q370 0 370 -397v-711h-98z" />
<glyph glyph-name="i" unicode="i" horiz-adv-x="463"
d="M281 0h-99v1087h99v-1087zM168 1389q0 96 63 96q31 0 48.5 -25t17.5 -71q0 -45 -17.5 -71t-48.5 -26q-63 0 -63 97z" />
<glyph glyph-name="j" unicode="j" horiz-adv-x="463"
d="M37 -492q-80 0 -135 25v86q69 -20 129 -20q151 0 151 176v1312h99v-1298q0 -135 -63.5 -208t-180.5 -73zM168 1389q0 96 63 96q31 0 48.5 -25t17.5 -71q0 -45 -17.5 -71t-48.5 -26q-63 0 -63 97z" />
<glyph glyph-name="k" unicode="k" horiz-adv-x="991"
d="M279 477l555 610h120l-428 -464l465 -623h-119l-413 549l-178 -162v-387h-99v1556h99v-780l-7 -299h5z" />
<glyph glyph-name="l" unicode="l" horiz-adv-x="463"
d="M281 0h-99v1556h99v-1556z" />
<glyph glyph-name="m" unicode="m" horiz-adv-x="1808"
d="M1540 0v713q0 159 -62 232t-190 73q-167 0 -247 -92t-80 -289v-637h-101v743q0 275 -252 275q-171 0 -249 -99.5t-78 -318.5v-600h-99v1087h82l21 -149h6q45 81 128 125.5t183 44.5q257 0 330 -193h4q53 93 142.5 143t203.5 50q178 0 267 -95t89 -302v-711h-98z" />
<glyph glyph-name="n" unicode="n" horiz-adv-x="1208"
d="M940 0v705q0 164 -69 238.5t-214 74.5q-195 0 -285.5 -98.5t-90.5 -319.5v-600h-99v1087h84l19 -149h6q106 170 377 170q370 0 370 -397v-711h-98z" />
<glyph glyph-name="o" unicode="o" horiz-adv-x="1200"
d="M1081 545q0 -266 -129 -415.5t-356 -149.5q-143 0 -252 69t-167 198t-58 298q0 266 129 414.5t354 148.5q224 0 351.5 -150.5t127.5 -412.5zM223 545q0 -224 98.5 -349.5t278.5 -125.5t278.5 125.5t98.5 349.5q0 225 -99.5 349t-279.5 124t-277.5 -123.5t-97.5 -349.5z
" />
<glyph glyph-name="p" unicode="p" horiz-adv-x="1219"
d="M647 -20q-251 0 -366 188h-7l3 -84q4 -74 4 -162v-414h-99v1579h84l19 -155h6q112 176 358 176q220 0 335.5 -144.5t115.5 -420.5q0 -268 -121.5 -415.5t-331.5 -147.5zM645 68q167 0 258.5 124t91.5 347q0 479 -346 479q-190 0 -279 -104.5t-89 -340.5v-32
q0 -255 85.5 -364t278.5 -109z" />
<glyph glyph-name="q" unicode="q" horiz-adv-x="1219"
d="M569 -20q-214 0 -332 142t-118 410q0 275 118 425.5t338 150.5q236 0 353 -174h6l18 153h84v-1579h-98v414q0 122 6 248h-6q-118 -190 -369 -190zM571 68q198 0 282.5 109t84.5 366v12q0 245 -85 354t-271 109q-176 0 -267.5 -124t-91.5 -364q0 -229 89.5 -345.5
t258.5 -116.5z" />
<glyph glyph-name="r" unicode="r" horiz-adv-x="797"
d="M610 1108q69 0 148 -14l-19 -95q-68 17 -141 17q-139 0 -228 -118t-89 -298v-600h-99v1087h84l10 -196h7q67 120 143 168.5t184 48.5z" />
<glyph glyph-name="s" unicode="s" horiz-adv-x="954"
d="M856 283q0 -146 -111 -224.5t-315 -78.5q-218 0 -346 67v107q164 -82 346 -82q161 0 244.5 53.5t83.5 142.5q0 82 -66.5 138t-218.5 110q-163 59 -229 101.5t-99.5 96t-33.5 130.5q0 122 102.5 193t286.5 71q176 0 334 -66l-37 -90q-160 66 -297 66q-133 0 -211 -44
t-78 -122q0 -85 60.5 -136t236.5 -114q147 -53 214 -95.5t100.5 -96.5t33.5 -127z" />
<glyph glyph-name="t" unicode="t" horiz-adv-x="686"
d="M469 68q94 0 164 16v-80q-72 -24 -166 -24q-144 0 -212.5 77t-68.5 242v702h-161v58l161 45l50 246h51v-263h319v-86h-319v-688q0 -125 44 -185t138 -60z" />
<glyph glyph-name="u" unicode="u" horiz-adv-x="1208"
d="M268 1087v-704q0 -164 69 -238.5t214 -74.5q194 0 285.5 98t91.5 319v600h98v-1087h-84l-18 150h-6q-106 -170 -377 -170q-371 0 -371 397v710h98z" />
<glyph glyph-name="v" unicode="v" horiz-adv-x="940"
d="M420 0l-420 1087h102l281 -739q56 -142 84 -248h6q41 136 84 250l281 737h102l-420 -1087h-100z" />
<glyph glyph-name="w" unicode="w" horiz-adv-x="1481"
d="M1051 0l-238 727q-23 74 -59 217h-6l-21 -74l-45 -145l-242 -725h-98l-311 1087h106l174 -630q61 -234 80 -344h6q59 234 86 311l224 663h90l213 -661q72 -235 88 -311h6q8 65 80 348l166 624h100l-295 -1087h-104z" />
<glyph glyph-name="x" unicode="x" horiz-adv-x="1020"
d="M449 559l-379 528h114l324 -458l321 458h109l-373 -528l400 -559h-115l-342 485l-344 -485h-109z" />
<glyph glyph-name="y" unicode="y" horiz-adv-x="940"
d="M0 1087h102l230 -610q105 -281 133 -379h6q42 129 137 385l230 604h102l-487 -1263q-59 -154 -99 -208t-93.5 -81t-129.5 -27q-57 0 -127 21v86q58 -16 125 -16q51 0 90 24t70.5 74.5t73 160t53.5 142.5z" />
<glyph glyph-name="z" unicode="z" horiz-adv-x="944"
d="M858 0h-776v63l645 936h-598v88h727v-63l-649 -936h651v-88z" />
<glyph glyph-name="braceleft" unicode="{" horiz-adv-x="723"
d="M389 -27q0 -102 59.5 -152.5t202.5 -53.5v-91q-195 0 -277.5 75t-82.5 231v337q0 205 -230 209v80q122 2 176 51t54 148v350q0 299 360 305v-90q-138 -5 -200 -58t-62 -157v-305q0 -130 -44 -194t-142 -85v-8q97 -20 141.5 -83.5t44.5 -186.5v-322z" />
<glyph glyph-name="bar" unicode="|" horiz-adv-x="1108"
d="M508 1561h92v-2067h-92v2067z" />
<glyph glyph-name="braceright" unicode="}" horiz-adv-x="723"
d="M334 295q0 123 44.5 186.5t141.5 83.5v8q-97 20 -141.5 84t-44.5 195v305q0 103 -61.5 156.5t-200.5 58.5v90q174 0 267 -77.5t93 -227.5v-350q0 -100 54.5 -148.5t175.5 -50.5v-80q-230 -4 -230 -209v-337q0 -155 -82.5 -230.5t-277.5 -75.5v91q141 2 201.5 52.5
t60.5 153.5v322z" />
<glyph glyph-name="asciitilde" unicode="~"
d="M334 745q-49 0 -108 -30.5t-115 -89.5v94q108 110 233 110q61 0 115 -13.5t155 -57.5q126 -58 220 -58q56 0 109.5 30.5t115.5 94.5v-96q-48 -49 -104.5 -81t-129.5 -32q-116 0 -270 72q-124 57 -221 57z" />
<glyph glyph-name="nonbreakingspace" unicode="&#xa0;" horiz-adv-x="532"
/>
<glyph glyph-name="exclamdown" unicode="&#xa1;" horiz-adv-x="492"
d="M215 711h61l29 -1086h-119zM166 1010q0 98 80 98q82 0 82 -98q0 -53 -23.5 -76t-58.5 -23q-34 0 -57 23t-23 76z" />
<glyph glyph-name="cent" unicode="&#xa2;"
d="M602 190q-186 30 -288.5 175t-102.5 380q0 232 102.5 381.5t288.5 182.5v174h82v-166h14q131 0 275 -55l-31 -84q-134 51 -237 51q-187 0 -288.5 -122.5t-101.5 -358.5q0 -225 100.5 -349.5t280.5 -124.5q131 0 267 58v-92q-110 -56 -267 -56h-12v-204h-82v210z" />
<glyph glyph-name="sterling" unicode="&#xa3;"
d="M412 676v-256q0 -116 -35 -196t-113 -128h809v-96h-995v84q110 21 171.5 110t61.5 224v258h-211v82h211v297q0 204 98 315t281 111q175 0 330 -68l-35 -86q-157 66 -295 66q-141 0 -209.5 -81t-68.5 -253v-301h411v-82h-411z" />
<glyph glyph-name="currency" unicode="&#xa4;"
d="M991 723q0 -151 -90 -256l139 -141l-59 -60l-137 142q-110 -93 -260 -93q-153 0 -260 93l-138 -142l-59 60l139 141q-90 106 -90 256q0 147 90 258l-139 141l59 60l138 -142q103 93 260 93q155 0 260 -93l137 142l59 -60l-139 -141q90 -111 90 -258zM584 395
q134 0 228.5 95.5t94.5 232.5q0 136 -95 233t-228 97q-134 0 -229 -97t-95 -233t94.5 -232t229.5 -96z" />
<glyph glyph-name="yen" unicode="&#xa5;"
d="M586 666l428 796h110l-432 -788h283v-82h-338v-205h338v-82h-338v-305h-105v305h-337v82h337v205h-337v82h278l-430 788h117z" />
<glyph glyph-name="brokenbar" unicode="&#xa6;" horiz-adv-x="1108"
d="M508 1561h92v-764h-92v764zM508 258h92v-764h-92v764z" />
<glyph glyph-name="section" unicode="&#xa7;" horiz-adv-x="1057"
d="M145 813q0 83 50.5 152.5t138.5 107.5q-86 47 -125 102t-39 136q0 117 101.5 183.5t275.5 66.5q175 0 336 -64l-35 -80q-91 34 -158.5 47t-144.5 13q-134 0 -205.5 -44.5t-71.5 -119.5q0 -54 25.5 -88.5t85.5 -65.5t188 -74q192 -64 264 -132.5t72 -170.5
q0 -173 -186 -274q86 -42 129 -96t43 -136q0 -135 -113 -207.5t-311 -72.5q-92 0 -171 15t-165 52v95q182 -78 332 -78q162 0 247 49.5t85 140.5q0 55 -25 87.5t-88.5 65.5t-190.5 79q-200 73 -272 141.5t-72 169.5zM246 825q0 -65 31.5 -104t105.5 -75t250 -99
q82 41 126 98t44 121q0 62 -32 102t-108.5 77t-236.5 87q-81 -23 -130.5 -79t-49.5 -128z" />
<glyph glyph-name="dieresis" unicode="&#xa8;" horiz-adv-x="1182"
d="M336 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM717 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
<glyph glyph-name="copyright" unicode="&#xa9;" horiz-adv-x="1704"
d="M897 1092q-142 0 -222.5 -94.5t-80.5 -264.5q0 -186 74.5 -275t220.5 -89q84 0 198 43v-88q-102 -45 -208 -45q-187 0 -288.5 115t-101.5 331q0 208 111 332.5t297 124.5q119 0 227 -52l-37 -83q-98 45 -190 45zM100 731q0 200 100 375t275 276t377 101q200 0 375 -100
t276 -275t101 -377q0 -197 -97 -370t-272 -277t-383 -104q-207 0 -382 103.5t-272.5 276.5t-97.5 371zM193 731q0 -178 88.5 -329.5t240.5 -240.5t330 -89t329.5 88.5t240.5 240.5t89 330q0 174 -85.5 325t-239 243t-334.5 92q-176 0 -328.5 -88.5t-241.5 -242.5t-89 -329z
" />
<glyph glyph-name="ordfeminine" unicode="&#xaa;" horiz-adv-x="686"
d="M512 813l-25 72q-84 -84 -202 -84q-95 0 -151 49t-56 139q0 100 80 151.5t241 59.5l95 4v43q0 77 -38 114.5t-106 37.5q-87 0 -196 -49l-33 73q117 56 231 56q228 0 228 -215v-451h-68zM168 993q0 -54 35 -85t96 -31q90 0 142.5 50t52.5 142v64l-88 -5
q-116 -6 -177 -36.5t-61 -98.5z" />
<glyph glyph-name="guillemotleft" unicode="&#xab;" horiz-adv-x="885"
d="M82 543l309 393l62 -43l-254 -363l254 -362l-62 -43l-309 391v27zM442 543l310 393l61 -43l-254 -363l254 -362l-61 -43l-310 391v27z" />
<glyph glyph-name="logicalnot" unicode="&#xac;"
d="M1038 764v-494h-82v412h-845v82h927z" />
<glyph glyph-name="uni00AD" unicode="&#xad;" horiz-adv-x="659"
d="M92 512v82h475v-82h-475z" />
<glyph glyph-name="registered" unicode="&#xae;" horiz-adv-x="1704"
d="M709 731h112q91 0 143 46.5t52 135.5q0 172 -197 172h-110v-354zM1120 918q0 -79 -38.5 -139.5t-110.5 -94.5l237 -393h-121l-210 360h-168v-360h-101v880h211q143 0 222 -62t79 -191zM100 731q0 200 100 375t275 276t377 101q200 0 375 -100t276 -275t101 -377
q0 -197 -97 -370t-272 -277t-383 -104q-207 0 -382 103.5t-272.5 276.5t-97.5 371zM193 731q0 -178 88.5 -329.5t240.5 -240.5t330 -89t329.5 88.5t240.5 240.5t89 330q0 174 -85.5 325t-239 243t-334.5 92q-176 0 -328.5 -88.5t-241.5 -242.5t-89 -329z" />
<glyph glyph-name="overscore" unicode="&#xaf;" horiz-adv-x="1024"
d="M1030 1556h-1036v82h1036v-82z" />
<glyph glyph-name="degree" unicode="&#xb0;" horiz-adv-x="877"
d="M139 1184q0 132 86.5 215.5t212.5 83.5t212.5 -83.5t86.5 -215.5t-86.5 -215.5t-212.5 -83.5q-130 0 -214.5 83t-84.5 216zM229 1184q0 -91 61 -154t148 -63q86 0 147.5 62t61.5 155q0 92 -60 154.5t-149 62.5q-90 0 -149.5 -64t-59.5 -153z" />
<glyph glyph-name="plusminus" unicode="&#xb1;"
d="M111 1v82h948v-82h-948zM625 764h434v-82h-434v-432h-82v432h-432v82h432v434h82v-434z" />
<glyph glyph-name="twosuperior" unicode="&#xb2;" horiz-adv-x="688"
d="M629 586h-576v78l242 237q125 121 172 193t47 149q0 71 -46.5 112.5t-123.5 41.5q-108 0 -217 -82l-49 65q119 103 270 103q124 0 194 -63.5t70 -174.5q0 -47 -13 -89t-40 -85.5t-68.5 -90t-308.5 -306.5h447v-88z" />
<glyph glyph-name="threesuperior" unicode="&#xb3;" horiz-adv-x="688"
d="M616 1260q0 -78 -44 -131.5t-117 -75.5q186 -45 186 -211q0 -130 -88.5 -201.5t-247.5 -71.5q-144 0 -264 60v88q136 -62 266 -62q115 0 174.5 49t59.5 136q0 83 -59.5 122t-178.5 39h-131v84h135q105 0 158 43.5t53 120.5q0 67 -47 107.5t-127 40.5q-128 0 -246 -78
l-47 70q130 94 293 94q127 0 199.5 -60t72.5 -163z" />
<glyph glyph-name="acute" unicode="&#xb4;" horiz-adv-x="1182"
d="M393 1257q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70v16z" />
<glyph glyph-name="mu" unicode="&#xb5;" horiz-adv-x="1221"
d="M281 1087v-704q0 -164 69 -238.5t213 -74.5q194 0 285.5 98t91.5 319v600h98v-1087h-84l-18 150h-6q-50 -77 -150 -123.5t-217 -46.5q-99 0 -167.5 27.5t-119.5 84.5q5 -92 5 -170v-414h-99v1579h99z" />
<glyph glyph-name="paragraph" unicode="&#xb6;" horiz-adv-x="1341"
d="M1106 -260h-100v1722h-228v-1722h-100v819q-64 -18 -146 -18q-216 0 -317.5 125t-101.5 376q0 260 109 387t341 127h543v-1816z" />
<glyph glyph-name="periodcentered" unicode="&#xb7;" horiz-adv-x="487"
d="M162 721q0 98 80 98q82 0 82 -98t-82 -98q-80 0 -80 98z" />
<glyph glyph-name="cedilla" unicode="&#xb8;" horiz-adv-x="420"
d="M393 -291q0 -100 -67.5 -150.5t-188.5 -50.5q-68 0 -94 11v88q30 -10 92 -10q78 0 119 28t41 80q0 94 -193 121l93 174h96l-66 -117q168 -37 168 -174z" />
<glyph glyph-name="onesuperior" unicode="&#xb9;" horiz-adv-x="688"
d="M350 1462h92v-876h-98v547q0 99 12 233q-26 -23 -233 -145l-47 77z" />
<glyph glyph-name="ordmasculine" unicode="&#xba;" horiz-adv-x="739"
d="M670 1141q0 -161 -80 -250.5t-223 -89.5t-220 86t-77 254q0 162 78 250t223 88q142 0 220.5 -87t78.5 -251zM160 1141q0 -264 209 -264t209 264q0 131 -50 194.5t-159 63.5t-159 -63.5t-50 -194.5z" />
<glyph glyph-name="guillemotright" unicode="&#xbb;" horiz-adv-x="885"
d="M803 518l-309 -393l-62 43l254 362l-254 363l62 43l309 -391v-27zM442 518l-309 -393l-61 43l254 362l-254 363l61 43l309 -391v-27z" />
<glyph glyph-name="onequarter" unicode="&#xbc;" horiz-adv-x="1516"
d="M1392 242h-129v-241h-90v241h-413v60l407 581h96v-563h129v-78zM1173 320v221q0 132 8 232q-6 -12 -21.5 -35.5t-295.5 -417.5h309zM1148 1462l-811 -1462h-94l811 1462h94zM333 1462h92v-876h-98v547q0 99 12 233q-26 -23 -233 -145l-47 77z" />
<glyph glyph-name="onehalf" unicode="&#xbd;" horiz-adv-x="1516"
d="M1073 1462l-811 -1462h-94l811 1462h94zM285 1462h92v-876h-98v547q0 99 12 233q-26 -23 -233 -145l-47 77zM1403 1h-576v78l242 237q125 121 172 193t47 149q0 71 -46.5 112.5t-123.5 41.5q-108 0 -217 -82l-49 65q119 103 270 103q124 0 194 -63.5t70 -174.5
q0 -47 -13 -89t-40 -85.5t-68.5 -90t-308.5 -306.5h447v-88z" />
<glyph glyph-name="threequarters" unicode="&#xbe;" horiz-adv-x="1516"
d="M1495 242h-129v-241h-90v241h-413v60l407 581h96v-563h129v-78zM1276 320v221q0 132 8 232q-6 -12 -21.5 -35.5t-295.5 -417.5h309zM1300 1462l-811 -1462h-94l811 1462h94zM616 1260q0 -78 -44 -131.5t-117 -75.5q186 -45 186 -211q0 -130 -88.5 -201.5t-247.5 -71.5
q-144 0 -264 60v88q136 -62 266 -62q115 0 174.5 49t59.5 136q0 83 -59.5 122t-178.5 39h-131v84h135q105 0 158 43.5t53 120.5q0 67 -47 107.5t-127 40.5q-128 0 -246 -78l-47 70q130 94 293 94q127 0 199.5 -60t72.5 -163z" />
<glyph glyph-name="questiondown" unicode="&#xbf;" horiz-adv-x="862"
d="M569 711v-37q0 -125 -39.5 -204.5t-136.5 -164.5l-90 -79q-73 -61 -104 -120.5t-31 -138.5q0 -124 82 -200t221 -76q125 0 233 46l64 27l37 -79q-111 -48 -185.5 -64t-152.5 -16q-184 0 -288.5 99t-104.5 269q0 70 20 124t58.5 102t171.5 159q64 53 98.5 98.5t49.5 94
t15 145.5v15h82zM440 1010q0 98 80 98q82 0 82 -98q0 -53 -23.5 -76t-58.5 -23q-34 0 -57 23t-23 76z" />
<glyph glyph-name="Agrave" unicode="&#xc0;" horiz-adv-x="1229"
d="M911 516h-594l-204 -516h-113l588 1468h65l576 -1468h-115zM354 608h523l-199 527q-25 62 -60 172q-27 -96 -59 -174zM720 1579h-69q-96 79 -188.5 171.5t-125.5 139.5v17h142q26 -48 98.5 -142t142.5 -170v-16z" />
<glyph glyph-name="Aacute" unicode="&#xc1;" horiz-adv-x="1229"
d="M911 516h-594l-204 -516h-113l588 1468h65l576 -1468h-115zM354 608h523l-199 527q-25 62 -60 172q-27 -96 -59 -174zM504 1595q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70v16z" />
<glyph glyph-name="Acircumflex" unicode="&#xc2;" horiz-adv-x="1229"
d="M911 516h-594l-204 -516h-113l588 1468h65l576 -1468h-115zM354 608h523l-199 527q-25 62 -60 172q-27 -96 -59 -174zM328 1595q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70v16z" />
<glyph glyph-name="Atilde" unicode="&#xc3;" horiz-adv-x="1229"
d="M911 516h-594l-204 -516h-113l588 1468h65l576 -1468h-115zM354 608h523l-199 527q-25 62 -60 172q-27 -96 -59 -174zM784 1581q-36 0 -75 18.5t-101 71.5q-32 26 -62.5 46t-62.5 20q-45 0 -75 -34.5t-48 -121.5h-73q10 111 63 174.5t137 63.5q48 0 88 -25t82 -59
q34 -28 66 -50t61 -22q46 0 77 36.5t48 119.5h76q-16 -116 -69 -177t-132 -61z" />
<glyph glyph-name="Adieresis" unicode="&#xc4;" horiz-adv-x="1229"
d="M911 516h-594l-204 -516h-113l588 1468h65l576 -1468h-115zM354 608h523l-199 527q-25 62 -60 172q-27 -96 -59 -174zM367 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM748 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
<glyph glyph-name="Aring" unicode="&#xc5;" horiz-adv-x="1229"
d="M836 1610q0 -97 -60 -155t-157 -58t-157 58t-60 155q0 94 60 152.5t157 58.5t157 -59t60 -152zM482 1610q0 -66 37.5 -103.5t99.5 -37.5t99.5 37.5t37.5 103.5q0 64 -39 101.5t-98 37.5q-62 0 -99.5 -38t-37.5 -101zM911 516h-594l-204 -516h-113l588 1468h65l576 -1468
h-115zM354 608h523l-199 527q-25 62 -60 172q-27 -96 -59 -174z" />
<glyph glyph-name="AE" unicode="&#xc6;" horiz-adv-x="1653"
d="M1528 0h-717v516h-475l-227 -516h-111l653 1462h877v-94h-615v-553h576v-94h-576v-627h615v-94zM377 608h434v760h-100z" />
<glyph glyph-name="Ccedilla" unicode="&#xc7;" horiz-adv-x="1272"
d="M831 1391q-275 0 -433 -176t-158 -482q0 -313 149 -486t426 -173q184 0 338 47v-90q-145 -51 -362 -51q-308 0 -485 199t-177 556q0 223 84.5 393t243 262.5t368.5 92.5q214 0 383 -80l-41 -92q-160 80 -336 80zM911 -291q0 -100 -67.5 -150.5t-188.5 -50.5q-68 0 -94 11
v88q30 -10 92 -10q78 0 119 28t41 80q0 94 -193 121l93 174h96l-66 -117q168 -37 168 -174z" />
<glyph glyph-name="Egrave" unicode="&#xc8;" horiz-adv-x="1130"
d="M1006 0h-799v1462h799v-94h-697v-553h658v-94h-658v-627h697v-94zM697 1579h-69q-96 79 -188.5 171.5t-125.5 139.5v17h142q26 -48 98.5 -142t142.5 -170v-16z" />
<glyph glyph-name="Eacute" unicode="&#xc9;" horiz-adv-x="1130"
d="M1006 0h-799v1462h799v-94h-697v-553h658v-94h-658v-627h697v-94zM463 1595q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70v16z" />
<glyph glyph-name="Ecircumflex" unicode="&#xca;" horiz-adv-x="1130"
d="M1006 0h-799v1462h799v-94h-697v-553h658v-94h-658v-627h697v-94zM315 1595q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70v16z" />
<glyph glyph-name="Edieresis" unicode="&#xcb;" horiz-adv-x="1130"
d="M1006 0h-799v1462h799v-94h-697v-553h658v-94h-658v-627h697v-94zM354 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM735 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
<glyph glyph-name="Eth" unicode="&#xd0;" horiz-adv-x="1466"
d="M1317 745q0 -368 -193 -556.5t-567 -188.5h-350v678h-160v94h160v690h395q350 0 532.5 -183t182.5 -534zM1206 741q0 314 -159.5 472.5t-468.5 158.5h-269v-600h406v-94h-406v-588h242q655 0 655 651z" />
<glyph glyph-name="Ntilde" unicode="&#xd1;" horiz-adv-x="1477"
d="M1270 0h-103l-866 1298h-8q12 -232 12 -350v-948h-98v1462h102l865 -1296h6q-9 180 -9 342v954h99v-1462zM897 1581q-36 0 -75 18.5t-101 71.5q-32 26 -62.5 46t-62.5 20q-45 0 -75 -34.5t-48 -121.5h-73q10 111 63 174.5t137 63.5q48 0 88 -25t82 -59q34 -28 66 -50
t61 -22q46 0 77 36.5t48 119.5h76q-16 -116 -69 -177t-132 -61z" />
<glyph glyph-name="Ograve" unicode="&#xd2;" horiz-adv-x="1565"
d="M1436 733q0 -348 -174 -550.5t-480 -202.5q-305 0 -479 202.5t-174 552.5q0 349 175.5 549.5t479.5 200.5q306 0 479 -201.5t173 -550.5zM240 733q0 -314 140 -485.5t402 -171.5q264 0 403.5 170t139.5 487q0 316 -139.5 484.5t-401.5 168.5q-261 0 -402.5 -170
t-141.5 -483zM885 1579h-69q-96 79 -188.5 171.5t-125.5 139.5v17h142q26 -48 98.5 -142t142.5 -170v-16z" />
<glyph glyph-name="Oacute" unicode="&#xd3;" horiz-adv-x="1565"
d="M1436 733q0 -348 -174 -550.5t-480 -202.5q-305 0 -479 202.5t-174 552.5q0 349 175.5 549.5t479.5 200.5q306 0 479 -201.5t173 -550.5zM240 733q0 -314 140 -485.5t402 -171.5q264 0 403.5 170t139.5 487q0 316 -139.5 484.5t-401.5 168.5q-261 0 -402.5 -170
t-141.5 -483zM686 1595q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70v16z" />
<glyph glyph-name="Ocircumflex" unicode="&#xd4;" horiz-adv-x="1565"
d="M1436 733q0 -348 -174 -550.5t-480 -202.5q-305 0 -479 202.5t-174 552.5q0 349 175.5 549.5t479.5 200.5q306 0 479 -201.5t173 -550.5zM240 733q0 -314 140 -485.5t402 -171.5q264 0 403.5 170t139.5 487q0 316 -139.5 484.5t-401.5 168.5q-261 0 -402.5 -170
t-141.5 -483zM492 1595q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70v16z" />
<glyph glyph-name="Otilde" unicode="&#xd5;" horiz-adv-x="1565"
d="M1436 733q0 -348 -174 -550.5t-480 -202.5q-305 0 -479 202.5t-174 552.5q0 349 175.5 549.5t479.5 200.5q306 0 479 -201.5t173 -550.5zM240 733q0 -314 140 -485.5t402 -171.5q264 0 403.5 170t139.5 487q0 316 -139.5 484.5t-401.5 168.5q-261 0 -402.5 -170
t-141.5 -483zM940 1581q-36 0 -75 18.5t-101 71.5q-32 26 -62.5 46t-62.5 20q-45 0 -75 -34.5t-48 -121.5h-73q10 111 63 174.5t137 63.5q48 0 88 -25t82 -59q34 -28 66 -50t61 -22q46 0 77 36.5t48 119.5h76q-16 -116 -69 -177t-132 -61z" />
<glyph glyph-name="Odieresis" unicode="&#xd6;" horiz-adv-x="1565"
d="M1436 733q0 -348 -174 -550.5t-480 -202.5q-305 0 -479 202.5t-174 552.5q0 349 175.5 549.5t479.5 200.5q306 0 479 -201.5t173 -550.5zM240 733q0 -314 140 -485.5t402 -171.5q264 0 403.5 170t139.5 487q0 316 -139.5 484.5t-401.5 168.5q-261 0 -402.5 -170
t-141.5 -483zM529 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM910 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
<glyph glyph-name="multiply" unicode="&#xd7;"
d="M584 780l409 408l58 -58l-408 -407l406 -408l-58 -57l-407 408l-406 -408l-57 57l405 408l-407 407l57 58z" />
<glyph glyph-name="Oslash" unicode="&#xd8;" horiz-adv-x="1565"
d="M1436 733q0 -348 -174 -550.5t-480 -202.5q-236 0 -395 120l-86 -120l-74 59l90 127q-188 200 -188 569q0 349 175.5 549.5t479.5 200.5q232 0 392 -121l108 152l72 -60l-111 -153q191 -207 191 -570zM1325 733q0 315 -139 486l-742 -1037q133 -106 338 -106
q264 0 403.5 170t139.5 487zM240 733q0 -312 139 -483l739 1034q-133 102 -334 102q-261 0 -402.5 -170t-141.5 -483z" />
<glyph glyph-name="Ugrave" unicode="&#xd9;" horiz-adv-x="1473"
d="M1282 1462v-946q0 -252 -146 -394t-407 -142q-254 0 -396.5 142.5t-142.5 397.5v942h103v-946q0 -211 117 -328.5t331 -117.5q209 0 324 115.5t115 320.5v956h102zM833 1579h-69q-96 79 -188.5 171.5t-125.5 139.5v17h142q26 -48 98.5 -142t142.5 -170v-16z" />
<glyph glyph-name="Uacute" unicode="&#xda;" horiz-adv-x="1473"
d="M1282 1462v-946q0 -252 -146 -394t-407 -142q-254 0 -396.5 142.5t-142.5 397.5v942h103v-946q0 -211 117 -328.5t331 -117.5q209 0 324 115.5t115 320.5v956h102zM633 1595q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70v16z" />
<glyph glyph-name="Ucircumflex" unicode="&#xdb;" horiz-adv-x="1473"
d="M1282 1462v-946q0 -252 -146 -394t-407 -142q-254 0 -396.5 142.5t-142.5 397.5v942h103v-946q0 -211 117 -328.5t331 -117.5q209 0 324 115.5t115 320.5v956h102zM444 1595q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207
q-108 -114 -221 -207h-70v16z" />
<glyph glyph-name="Udieresis" unicode="&#xdc;" horiz-adv-x="1473"
d="M1282 1462v-946q0 -252 -146 -394t-407 -142q-254 0 -396.5 142.5t-142.5 397.5v942h103v-946q0 -211 117 -328.5t331 -117.5q209 0 324 115.5t115 320.5v956h102zM481 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM862 1727q0 46 15.5 66t47.5 20
q64 0 64 -86t-64 -86q-63 0 -63 86z" />
<glyph glyph-name="Yacute" unicode="&#xdd;" horiz-adv-x="1081"
d="M543 662l428 800h110l-487 -897v-565h-105v557l-489 905h117zM434 1595q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70v16z" />
<glyph glyph-name="Thorn" unicode="&#xde;" horiz-adv-x="1198"
d="M1087 778q0 -212 -144 -325t-408 -113h-226v-340h-102v1462h102v-264h256q522 0 522 -420zM309 428h201q247 0 357 81.5t110 264.5q0 169 -104 250.5t-322 81.5h-242v-678z" />
<glyph glyph-name="germandbls" unicode="&#xdf;" horiz-adv-x="1194"
d="M961 1284q0 -139 -139 -250q-81 -64 -110.5 -100.5t-29.5 -75.5q0 -44 14.5 -68t51.5 -57t102 -78q106 -75 151.5 -124.5t68 -103t22.5 -120.5q0 -156 -88 -241.5t-246 -85.5q-95 0 -174.5 18.5t-126.5 48.5v107q65 -38 148.5 -62t152.5 -24q114 0 174.5 54.5t60.5 160.5
q0 83 -39 144t-149 136q-127 87 -175 147t-48 146q0 60 32.5 110t106.5 108q74 57 106.5 105.5t32.5 106.5q0 93 -70 143t-202 50q-145 0 -226 -69t-81 -196v-1214h-99v1206q0 173 103.5 267t292.5 94q188 0 285.5 -72.5t97.5 -210.5z" />
<glyph glyph-name="agrave" unicode="&#xe0;" horiz-adv-x="1085"
d="M842 0l-25 172h-8q-82 -105 -168.5 -148.5t-204.5 -43.5q-160 0 -249 82t-89 227q0 159 132.5 247t383.5 93l207 6v72q0 155 -63 234t-203 79q-151 0 -313 -84l-37 86q179 84 354 84q179 0 267.5 -93t88.5 -290v-723h-73zM442 70q174 0 274.5 99.5t100.5 276.5v107
l-190 -8q-229 -11 -326.5 -71.5t-97.5 -188.5q0 -102 62.5 -158.5t176.5 -56.5zM638 1241h-69q-96 79 -188.5 171.5t-125.5 139.5v17h142q26 -48 98.5 -142t142.5 -170v-16z" />
<glyph glyph-name="aacute" unicode="&#xe1;" horiz-adv-x="1085"
d="M842 0l-25 172h-8q-82 -105 -168.5 -148.5t-204.5 -43.5q-160 0 -249 82t-89 227q0 159 132.5 247t383.5 93l207 6v72q0 155 -63 234t-203 79q-151 0 -313 -84l-37 86q179 84 354 84q179 0 267.5 -93t88.5 -290v-723h-73zM442 70q174 0 274.5 99.5t100.5 276.5v107
l-190 -8q-229 -11 -326.5 -71.5t-97.5 -188.5q0 -102 62.5 -158.5t176.5 -56.5zM422 1257q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70v16z" />
<glyph glyph-name="acircumflex" unicode="&#xe2;" horiz-adv-x="1085"
d="M842 0l-25 172h-8q-82 -105 -168.5 -148.5t-204.5 -43.5q-160 0 -249 82t-89 227q0 159 132.5 247t383.5 93l207 6v72q0 155 -63 234t-203 79q-151 0 -313 -84l-37 86q179 84 354 84q179 0 267.5 -93t88.5 -290v-723h-73zM442 70q174 0 274.5 99.5t100.5 276.5v107
l-190 -8q-229 -11 -326.5 -71.5t-97.5 -188.5q0 -102 62.5 -158.5t176.5 -56.5zM251 1257q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70v16z" />
<glyph glyph-name="atilde" unicode="&#xe3;" horiz-adv-x="1085"
d="M842 0l-25 172h-8q-82 -105 -168.5 -148.5t-204.5 -43.5q-160 0 -249 82t-89 227q0 159 132.5 247t383.5 93l207 6v72q0 155 -63 234t-203 79q-151 0 -313 -84l-37 86q179 84 354 84q179 0 267.5 -93t88.5 -290v-723h-73zM442 70q174 0 274.5 99.5t100.5 276.5v107
l-190 -8q-229 -11 -326.5 -71.5t-97.5 -188.5q0 -102 62.5 -158.5t176.5 -56.5zM697 1243q-36 0 -75 18.5t-101 71.5q-32 26 -62.5 46t-62.5 20q-45 0 -75 -34.5t-48 -121.5h-73q10 111 63 174.5t137 63.5q48 0 88 -25t82 -59q34 -28 66 -50t61 -22q46 0 77 36.5t48 119.5
h76q-16 -116 -69 -177t-132 -61z" />
<glyph glyph-name="adieresis" unicode="&#xe4;" horiz-adv-x="1085"
d="M842 0l-25 172h-8q-82 -105 -168.5 -148.5t-204.5 -43.5q-160 0 -249 82t-89 227q0 159 132.5 247t383.5 93l207 6v72q0 155 -63 234t-203 79q-151 0 -313 -84l-37 86q179 84 354 84q179 0 267.5 -93t88.5 -290v-723h-73zM442 70q174 0 274.5 99.5t100.5 276.5v107
l-190 -8q-229 -11 -326.5 -71.5t-97.5 -188.5q0 -102 62.5 -158.5t176.5 -56.5zM282 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM663 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
<glyph glyph-name="aring" unicode="&#xe5;" horiz-adv-x="1085"
d="M759 1456q0 -97 -60 -155t-157 -58t-157 58t-60 155q0 94 60 152.5t157 58.5t157 -59t60 -152zM405 1456q0 -66 37.5 -103.5t99.5 -37.5t99.5 37.5t37.5 103.5q0 64 -39 101.5t-98 37.5q-62 0 -99.5 -38t-37.5 -101zM842 0l-25 172h-8q-82 -105 -168.5 -148.5
t-204.5 -43.5q-160 0 -249 82t-89 227q0 159 132.5 247t383.5 93l207 6v72q0 155 -63 234t-203 79q-151 0 -313 -84l-37 86q179 84 354 84q179 0 267.5 -93t88.5 -290v-723h-73zM442 70q174 0 274.5 99.5t100.5 276.5v107l-190 -8q-229 -11 -326.5 -71.5t-97.5 -188.5
q0 -102 62.5 -158.5t176.5 -56.5z" />
<glyph glyph-name="ae" unicode="&#xe6;" horiz-adv-x="1731"
d="M1243 -20q-295 0 -397 256q-68 -133 -168 -194.5t-252 -61.5q-156 0 -242 82.5t-86 226.5q0 154 125 243t377 97l201 6v72q0 155 -61.5 234t-198.5 79q-148 0 -305 -84l-37 86q173 84 346 84q261 0 325 -211q111 213 347 213q184 0 289.5 -134.5t105.5 -363.5v-80h-715
q0 -460 348 -460q85 0 150 12t174 57v-90q-92 -41 -165 -55t-161 -14zM434 70q169 0 266 99.5t97 276.5v107l-187 -8q-219 -11 -313 -71.5t-94 -188.5q0 -102 61 -158.5t170 -56.5zM1217 1020q-284 0 -314 -402h604q0 188 -77.5 295t-212.5 107z" />
<glyph glyph-name="ccedilla" unicode="&#xe7;" horiz-adv-x="973"
d="M616 -20q-233 0 -365 147t-132 410q0 270 137 420.5t375 150.5q141 0 270 -49l-27 -88q-141 47 -245 47q-200 0 -303 -123.5t-103 -355.5q0 -220 103 -344.5t288 -124.5q148 0 275 53v-92q-104 -51 -273 -51zM723 -291q0 -100 -67.5 -150.5t-188.5 -50.5q-68 0 -94 11v88
q30 -10 92 -10q78 0 119 28t41 80q0 94 -193 121l93 174h96l-66 -117q168 -37 168 -174z" />
<glyph glyph-name="egrave" unicode="&#xe8;" horiz-adv-x="1124"
d="M621 -20q-237 0 -369.5 146t-132.5 409q0 260 128 416.5t345 156.5q192 0 303 -134t111 -364v-80h-783q2 -224 104.5 -342t293.5 -118q93 0 163.5 13t178.5 56v-90q-92 -40 -170 -54.5t-172 -14.5zM592 1020q-157 0 -252 -103.5t-111 -298.5h672q0 189 -82 295.5
t-227 106.5zM685 1241h-69q-96 79 -188.5 171.5t-125.5 139.5v17h142q26 -48 98.5 -142t142.5 -170v-16z" />
<glyph glyph-name="eacute" unicode="&#xe9;" horiz-adv-x="1124"
d="M621 -20q-237 0 -369.5 146t-132.5 409q0 260 128 416.5t345 156.5q192 0 303 -134t111 -364v-80h-783q2 -224 104.5 -342t293.5 -118q93 0 163.5 13t178.5 56v-90q-92 -40 -170 -54.5t-172 -14.5zM592 1020q-157 0 -252 -103.5t-111 -298.5h672q0 189 -82 295.5
t-227 106.5zM452 1257q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70v16z" />
<glyph glyph-name="ecircumflex" unicode="&#xea;" horiz-adv-x="1124"
d="M621 -20q-237 0 -369.5 146t-132.5 409q0 260 128 416.5t345 156.5q192 0 303 -134t111 -364v-80h-783q2 -224 104.5 -342t293.5 -118q93 0 163.5 13t178.5 56v-90q-92 -40 -170 -54.5t-172 -14.5zM592 1020q-157 0 -252 -103.5t-111 -298.5h672q0 189 -82 295.5
t-227 106.5zM290 1257q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70v16z" />
<glyph glyph-name="edieresis" unicode="&#xeb;" horiz-adv-x="1124"
d="M621 -20q-237 0 -369.5 146t-132.5 409q0 260 128 416.5t345 156.5q192 0 303 -134t111 -364v-80h-783q2 -224 104.5 -342t293.5 -118q93 0 163.5 13t178.5 56v-90q-92 -40 -170 -54.5t-172 -14.5zM592 1020q-157 0 -252 -103.5t-111 -298.5h672q0 189 -82 295.5
t-227 106.5zM331 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM712 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
<glyph glyph-name="igrave" unicode="&#xec;" horiz-adv-x="463"
d="M281 0h-99v1087h99v-1087zM349 1241h-69q-96 79 -188.5 171.5t-125.5 139.5v17h142q26 -48 98.5 -142t142.5 -170v-16z" />
<glyph glyph-name="iacute" unicode="&#xed;" horiz-adv-x="463"
d="M281 0h-99v1087h99v-1087zM107 1257q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70v16z" />
<glyph glyph-name="icircumflex" unicode="&#xee;" horiz-adv-x="463"
d="M281 0h-99v1087h99v-1087zM-58 1257q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70v16z" />
<glyph glyph-name="idieresis" unicode="&#xef;" horiz-adv-x="463"
d="M281 0h-99v1087h99v-1087zM-21 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM360 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
<glyph glyph-name="eth" unicode="&#xf0;" horiz-adv-x="1174"
d="M1055 559q0 -276 -124 -427.5t-349 -151.5q-214 0 -339.5 130t-125.5 361q0 228 126.5 357.5t342.5 129.5q108 0 187.5 -33t148.5 -96l4 2q-64 270 -269 459l-270 -157l-49 77l244 146q-86 62 -199 119l45 81q147 -69 248 -145l225 137l49 -84l-202 -121
q154 -151 230.5 -353t76.5 -431zM950 557q0 146 -97 228.5t-267 82.5q-185 0 -275 -100.5t-90 -304.5q0 -186 94.5 -289.5t268.5 -103.5q179 0 272.5 123t93.5 364z" />
<glyph glyph-name="ntilde" unicode="&#xf1;" horiz-adv-x="1208"
d="M940 0v705q0 164 -69 238.5t-214 74.5q-195 0 -285.5 -98.5t-90.5 -319.5v-600h-99v1087h84l19 -149h6q106 170 377 170q370 0 370 -397v-711h-98zM779 1243q-36 0 -75 18.5t-101 71.5q-32 26 -62.5 46t-62.5 20q-45 0 -75 -34.5t-48 -121.5h-73q10 111 63 174.5
t137 63.5q48 0 88 -25t82 -59q34 -28 66 -50t61 -22q46 0 77 36.5t48 119.5h76q-16 -116 -69 -177t-132 -61z" />
<glyph glyph-name="ograve" unicode="&#xf2;" horiz-adv-x="1200"
d="M1081 545q0 -266 -129 -415.5t-356 -149.5q-143 0 -252 69t-167 198t-58 298q0 266 129 414.5t354 148.5q224 0 351.5 -150.5t127.5 -412.5zM223 545q0 -224 98.5 -349.5t278.5 -125.5t278.5 125.5t98.5 349.5q0 225 -99.5 349t-279.5 124t-277.5 -123.5t-97.5 -349.5z
M718 1241h-69q-96 79 -188.5 171.5t-125.5 139.5v17h142q26 -48 98.5 -142t142.5 -170v-16z" />
<glyph glyph-name="oacute" unicode="&#xf3;" horiz-adv-x="1200"
d="M1081 545q0 -266 -129 -415.5t-356 -149.5q-143 0 -252 69t-167 198t-58 298q0 266 129 414.5t354 148.5q224 0 351.5 -150.5t127.5 -412.5zM223 545q0 -224 98.5 -349.5t278.5 -125.5t278.5 125.5t98.5 349.5q0 225 -99.5 349t-279.5 124t-277.5 -123.5t-97.5 -349.5z
M499 1257q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70v16z" />
<glyph glyph-name="ocircumflex" unicode="&#xf4;" horiz-adv-x="1200"
d="M1081 545q0 -266 -129 -415.5t-356 -149.5q-143 0 -252 69t-167 198t-58 298q0 266 129 414.5t354 148.5q224 0 351.5 -150.5t127.5 -412.5zM223 545q0 -224 98.5 -349.5t278.5 -125.5t278.5 125.5t98.5 349.5q0 225 -99.5 349t-279.5 124t-277.5 -123.5t-97.5 -349.5z
M309 1257q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70v16z" />
<glyph glyph-name="otilde" unicode="&#xf5;" horiz-adv-x="1200"
d="M1081 545q0 -266 -129 -415.5t-356 -149.5q-143 0 -252 69t-167 198t-58 298q0 266 129 414.5t354 148.5q224 0 351.5 -150.5t127.5 -412.5zM223 545q0 -224 98.5 -349.5t278.5 -125.5t278.5 125.5t98.5 349.5q0 225 -99.5 349t-279.5 124t-277.5 -123.5t-97.5 -349.5z
M761 1243q-36 0 -75 18.5t-101 71.5q-32 26 -62.5 46t-62.5 20q-45 0 -75 -34.5t-48 -121.5h-73q10 111 63 174.5t137 63.5q48 0 88 -25t82 -59q34 -28 66 -50t61 -22q46 0 77 36.5t48 119.5h76q-16 -116 -69 -177t-132 -61z" />
<glyph glyph-name="odieresis" unicode="&#xf6;" horiz-adv-x="1200"
d="M1081 545q0 -266 -129 -415.5t-356 -149.5q-143 0 -252 69t-167 198t-58 298q0 266 129 414.5t354 148.5q224 0 351.5 -150.5t127.5 -412.5zM223 545q0 -224 98.5 -349.5t278.5 -125.5t278.5 125.5t98.5 349.5q0 225 -99.5 349t-279.5 124t-277.5 -123.5t-97.5 -349.5z
M346 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM727 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
<glyph glyph-name="divide" unicode="&#xf7;"
d="M111 682v82h948v-82h-948zM504 1075q0 99 80 99q82 0 82 -99q0 -52 -23.5 -75t-58.5 -23q-34 0 -57 23t-23 75zM504 371q0 98 80 98q82 0 82 -98q0 -53 -23.5 -76t-58.5 -23q-34 0 -57 23t-23 76z" />
<glyph glyph-name="oslash" unicode="&#xf8;" horiz-adv-x="1200"
d="M1081 545q0 -266 -129 -415.5t-356 -149.5q-173 0 -291 98l-86 -113l-72 58l93 120q-121 153 -121 402q0 266 129 414.5t354 148.5q179 0 301 -104l96 124l74 -55l-104 -137q112 -147 112 -391zM223 545q0 -200 78 -322l543 705q-98 90 -246 90q-180 0 -277.5 -123.5
t-97.5 -349.5zM977 545q0 190 -72 309l-543 -702q94 -82 238 -82q180 0 278.5 125.5t98.5 349.5z" />
<glyph glyph-name="ugrave" unicode="&#xf9;" horiz-adv-x="1208"
d="M268 1087v-704q0 -164 69 -238.5t214 -74.5q194 0 285.5 98t91.5 319v600h98v-1087h-84l-18 150h-6q-106 -170 -377 -170q-371 0 -371 397v710h98zM687 1241h-69q-96 79 -188.5 171.5t-125.5 139.5v17h142q26 -48 98.5 -142t142.5 -170v-16z" />
<glyph glyph-name="uacute" unicode="&#xfa;" horiz-adv-x="1208"
d="M268 1087v-704q0 -164 69 -238.5t214 -74.5q194 0 285.5 98t91.5 319v600h98v-1087h-84l-18 150h-6q-106 -170 -377 -170q-371 0 -371 397v710h98zM495 1257q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70v16z" />
<glyph glyph-name="ucircumflex" unicode="&#xfb;" horiz-adv-x="1208"
d="M268 1087v-704q0 -164 69 -238.5t214 -74.5q194 0 285.5 98t91.5 319v600h98v-1087h-84l-18 150h-6q-106 -170 -377 -170q-371 0 -371 397v710h98zM313 1257q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70v16z" />
<glyph glyph-name="udieresis" unicode="&#xfc;" horiz-adv-x="1208"
d="M268 1087v-704q0 -164 69 -238.5t214 -74.5q194 0 285.5 98t91.5 319v600h98v-1087h-84l-18 150h-6q-106 -170 -377 -170q-371 0 -371 397v710h98zM350 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM731 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86
q-63 0 -63 86z" />
<glyph glyph-name="yacute" unicode="&#xfd;" horiz-adv-x="940"
d="M0 1087h102l230 -610q105 -281 133 -379h6q42 129 137 385l230 604h102l-487 -1263q-59 -154 -99 -208t-93.5 -81t-129.5 -27q-57 0 -127 21v86q58 -16 125 -16q51 0 90 24t70.5 74.5t73 160t53.5 142.5zM361 1257q73 79 144.5 171.5t97.5 140.5h141v-17
q-36 -52 -122.5 -138t-190.5 -173h-70v16z" />
<glyph glyph-name="thorn" unicode="&#xfe;" horiz-adv-x="1219"
d="M281 918q114 190 368 190q220 0 335.5 -144.5t115.5 -420.5q0 -268 -121.5 -415.5t-331.5 -147.5q-251 0 -366 188h-7l3 -84q4 -74 4 -162v-414h-99v2048h99v-391l-7 -247h7zM645 68q167 0 258.5 124t91.5 347q0 479 -348 479q-193 0 -279.5 -105t-86.5 -354v-18
q0 -255 85.5 -364t278.5 -109z" />
<glyph glyph-name="ydieresis" unicode="&#xff;" horiz-adv-x="940"
d="M0 1087h102l230 -610q105 -281 133 -379h6q42 129 137 385l230 604h102l-487 -1263q-59 -154 -99 -208t-93.5 -81t-129.5 -27q-57 0 -127 21v86q58 -16 125 -16q51 0 90 24t70.5 74.5t73 160t53.5 142.5zM214 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86
q-63 0 -63 86zM595 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
<glyph glyph-name="itilde" unicode="&#x129;" horiz-adv-x="463"
d="M281 0h-99v1087h99v-1087zM390 1243q-36 0 -75 18.5t-101 71.5q-32 26 -62.5 46t-62.5 20q-45 0 -75 -34.5t-48 -121.5h-73q10 111 63 174.5t137 63.5q48 0 88 -25t82 -59q34 -28 66 -50t61 -22q46 0 77 36.5t48 119.5h76q-16 -116 -69 -177t-132 -61z" />
<glyph glyph-name="Eng" unicode="&#x14a;" horiz-adv-x="1477"
d="M1270 0q0 -369 -342 -369q-93 0 -152 27v88q78 -20 150 -20q241 0 241 264v10l-866 1298h-8q12 -232 12 -350v-948h-98v1462h102l865 -1296h6q-9 180 -9 342v954h99v-1462z" />
<glyph glyph-name="eng" unicode="&#x14b;" horiz-adv-x="1208"
d="M795 -492q-79 0 -136 25v86q69 -20 129 -20q152 0 152 176v930q0 164 -72.5 238.5t-224.5 74.5q-189 0 -275.5 -99.5t-86.5 -318.5v-600h-99v1087h84l19 -149h6q54 87 147 128.5t205 41.5q201 0 298 -99t97 -298v-922q0 -134 -62.5 -207.5t-180.5 -73.5z" />
<glyph glyph-name="OE" unicode="&#x152;" horiz-adv-x="1839"
d="M1714 0h-756q-76 -16 -176 -16q-305 0 -479 200t-174 551q0 347 174.5 545.5t480.5 198.5q78 0 183 -17h747v-94h-655v-553h616v-94h-616v-627h655v-94zM782 80q109 0 174 18v1266q-62 16 -172 16q-262 0 -403 -167.5t-141 -479.5q0 -315 140.5 -484t401.5 -169z" />
<glyph glyph-name="oe" unicode="&#x153;" horiz-adv-x="1942"
d="M1438 -20q-156 0 -266.5 67.5t-165.5 198.5q-59 -128 -158 -197t-252 -69q-143 0 -252 69t-167 198t-58 298q0 266 129 414.5t354 148.5q151 0 251 -70t157 -209q110 279 399 279q192 0 303 -134t111 -364v-80h-762q2 -230 100.5 -345t276.5 -115q93 0 163.5 13t178.5 56
v-90q-92 -40 -170 -54.5t-172 -14.5zM223 545q0 -224 98.5 -349.5t278.5 -125.5q174 0 265 122.5t91 352.5q0 224 -93 348.5t-265 124.5q-180 0 -277.5 -123.5t-97.5 -349.5zM1409 1020q-155 0 -242 -104t-102 -298h653q0 189 -82 295.5t-227 106.5z" />
<glyph glyph-name="Scaron" unicode="&#x160;" horiz-adv-x="1116"
d="M859 1890q-170 -188 -242 -311h-98q-76 128 -242 311v17h70q114 -94 221 -207q108 114 221 207h70v-17zM1014 377q0 -183 -134.5 -290t-357.5 -107q-268 0 -411 59v102q158 -67 403 -67q180 0 285.5 82.5t105.5 216.5q0 83 -35 137.5t-114 99.5t-232 97
q-224 77 -309.5 166.5t-85.5 238.5q0 164 128.5 267.5t330.5 103.5q206 0 387 -78l-37 -88q-182 76 -348 76q-162 0 -258 -75t-96 -204q0 -81 29.5 -133t96.5 -93.5t230 -99.5q171 -59 257 -114.5t125.5 -126t39.5 -170.5z" />
<glyph glyph-name="scaron" unicode="&#x161;" horiz-adv-x="954"
d="M759 1552q-170 -188 -242 -311h-98q-76 128 -242 311v17h70q114 -94 221 -207q108 114 221 207h70v-17zM856 283q0 -146 -111 -224.5t-315 -78.5q-218 0 -346 67v107q164 -82 346 -82q161 0 244.5 53.5t83.5 142.5q0 82 -66.5 138t-218.5 110q-163 59 -229 101.5
t-99.5 96t-33.5 130.5q0 122 102.5 193t286.5 71q176 0 334 -66l-37 -90q-160 66 -297 66q-133 0 -211 -44t-78 -122q0 -85 60.5 -136t236.5 -114q147 -53 214 -95.5t100.5 -96.5t33.5 -127z" />
<glyph glyph-name="Wcircumflex" unicode="&#x174;" horiz-adv-x="1827"
d="M1372 0h-84l-321 1128q-40 139 -60 228q-16 -87 -45.5 -200t-322.5 -1156h-86l-402 1462h107l256 -942q15 -57 28 -105.5t23.5 -91t19 -82t15.5 -79.5q24 136 102 413l250 887h113l293 -1018q51 -176 73 -284q13 72 33.5 153t308.5 1149h103zM618 1595q62 67 131.5 156
t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70v16z" />
<glyph glyph-name="Ydieresis" unicode="&#x178;" horiz-adv-x="1081"
d="M543 662l428 800h110l-487 -897v-565h-105v557l-489 905h117zM288 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM669 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
<glyph glyph-name="Zcaron" unicode="&#x17d;" horiz-adv-x="1180"
d="M891 1890q-170 -188 -242 -311h-98q-76 128 -242 311v17h70q114 -94 221 -207q108 114 221 207h70v-17zM1098 0h-1016v76l856 1290h-817v96h954v-76l-858 -1290h881v-96z" />
<glyph glyph-name="zcaron" unicode="&#x17e;" horiz-adv-x="944"
d="M780 1552q-170 -188 -242 -311h-98q-76 128 -242 311v17h70q114 -94 221 -207q108 114 221 207h70v-17zM858 0h-776v63l645 936h-598v88h727v-63l-649 -936h651v-88z" />
<glyph glyph-name="florin" unicode="&#x192;"
d="M303 854v59l207 37v146q0 201 76.5 294t240.5 93q82 0 183 -31l-25 -86q-89 29 -160 29q-115 0 -166 -67.5t-51 -223.5v-162h281v-88h-281v-1028q0 -155 -71 -236.5t-207 -81.5q-85 0 -140 19v90q78 -18 144 -18q176 0 176 217v1038h-207z" />
<glyph glyph-name="circumflex" unicode="&#x2c6;" horiz-adv-x="1182"
d="M299 1257q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70v16z" />
<glyph glyph-name="tilde" unicode="&#x2dc;" horiz-adv-x="1182"
d="M780 1243q-36 0 -75 18.5t-101 71.5q-32 26 -62.5 46t-62.5 20q-45 0 -75 -34.5t-48 -121.5h-73q10 111 63 174.5t137 63.5q48 0 88 -25t82 -59q34 -28 66 -50t61 -22q46 0 77 36.5t48 119.5h76q-16 -116 -69 -177t-132 -61z" />
<glyph glyph-name="Alphatonos" unicode="&#x386;" horiz-adv-x="1229"
d="M76 1152q29 75 57 184t42 210h119v-17q-18 -77 -63 -191t-97 -202h-58v16zM911 516h-594l-204 -516h-113l588 1468h65l576 -1468h-115zM354 608h523l-199 527q-25 62 -60 172q-27 -96 -59 -174z" />
<glyph glyph-name="endash" unicode="&#x2013;" horiz-adv-x="1024"
d="M82 512v82h860v-82h-860z" />
<glyph glyph-name="emdash" unicode="&#x2014;" horiz-adv-x="2048"
d="M82 512v82h1884v-82h-1884z" />
<glyph glyph-name="quoteleft" unicode="&#x2018;" horiz-adv-x="297"
d="M41 961l-12 20q32 112 81.5 251t92.5 230h65q-30 -101 -64.5 -257t-45.5 -244h-117z" />
<glyph glyph-name="quoteright" unicode="&#x2019;" horiz-adv-x="297"
d="M256 1462l12 -20q-75 -265 -174 -481h-65q29 96 61 241.5t49 259.5h117z" />
<glyph glyph-name="quotesinglbase" unicode="&#x201a;" horiz-adv-x="451"
d="M295 238l12 -20q-75 -265 -174 -481h-65q29 96 61 241.5t49 259.5h117z" />
<glyph glyph-name="quotedblleft" unicode="&#x201c;" horiz-adv-x="614"
d="M358 961l-12 20q34 120 83 255t91 226h66q-30 -98 -63 -248.5t-48 -252.5h-117zM41 961l-12 20q32 112 81.5 251t92.5 230h65q-30 -101 -64.5 -257t-45.5 -244h-117z" />
<glyph glyph-name="quotedblright" unicode="&#x201d;" horiz-adv-x="614"
d="M256 1462l12 -20q-75 -265 -174 -481h-65q29 96 61 241.5t49 259.5h117zM573 1462l13 -20q-36 -128 -85 -261t-89 -220h-66q30 98 63 248.5t48 252.5h116z" />
<glyph glyph-name="quotedblbase" unicode="&#x201e;" horiz-adv-x="768"
d="M295 238l12 -20q-75 -265 -174 -481h-65q29 96 61 241.5t49 259.5h117zM612 238l13 -20q-36 -128 -85 -261t-89 -220h-66q30 98 63 248.5t48 252.5h116z" />
<glyph glyph-name="dagger" unicode="&#x2020;" horiz-adv-x="1006"
d="M883 1055l-359 20l27 -1075h-117l27 1075l-338 -20v112l338 -28l-27 417h117l-27 -417l359 28v-112z" />
<glyph glyph-name="daggerdbl" unicode="&#x2021;" horiz-adv-x="1006"
d="M524 461l359 24v-112l-359 24l27 -397h-117l27 397l-338 -24v112l338 -24l-17 325l17 293l-338 -24v112l338 -24l-27 413h117l-27 -413l359 24v-112l-359 24l17 -293z" />
<glyph glyph-name="bullet" unicode="&#x2022;" horiz-adv-x="770"
d="M231 748q0 89 40.5 134.5t113.5 45.5t113.5 -47t40.5 -133q0 -85 -41 -133t-113 -48t-113 47t-41 134z" />
<glyph glyph-name="ellipsis" unicode="&#x2026;" horiz-adv-x="1466"
d="M162 78q0 98 80 98q82 0 82 -98t-82 -98q-80 0 -80 98zM651 78q0 98 80 98q82 0 82 -98t-82 -98q-80 0 -80 98zM1141 78q0 98 80 98q82 0 82 -98t-82 -98q-80 0 -80 98z" />
<glyph glyph-name="perthousand" unicode="&#x2030;" horiz-adv-x="2331"
d="M211 1026q0 -186 45 -279.5t141 -93.5q193 0 193 373q0 184 -49.5 276.5t-143.5 92.5q-96 0 -141 -92.5t-45 -276.5zM688 1026q0 -226 -75 -343.5t-216 -117.5q-133 0 -208.5 120.5t-75.5 340.5q0 223 72 340t212 117q139 0 215 -120.5t76 -336.5zM1063 438
q0 -185 45 -277.5t141 -92.5q193 0 193 370q0 369 -193 369q-96 0 -141 -91.5t-45 -277.5zM1540 438q0 -226 -74 -343.5t-215 -117.5q-136 0 -211 121.5t-75 339.5q0 225 73.5 341t212.5 116q137 0 213 -120t76 -337zM1280 1462l-811 -1462h-96l811 1462h96zM1741 438
q0 -185 45 -277.5t141 -92.5q193 0 193 370q0 369 -193 369q-96 0 -141 -91.5t-45 -277.5zM2218 438q0 -226 -74 -343.5t-215 -117.5q-135 0 -211 120.5t-76 340.5q0 225 73.5 341t213.5 116q137 0 213 -120t76 -337z" />
<glyph glyph-name="guilsinglleft" unicode="&#x2039;" horiz-adv-x="524"
d="M82 543l309 393l62 -43l-254 -363l254 -362l-62 -43l-309 391v27z" />
<glyph glyph-name="guilsinglright" unicode="&#x203a;" horiz-adv-x="524"
d="M442 518l-309 -393l-61 43l254 362l-254 363l61 43l309 -391v-27z" />
<glyph glyph-name="Euro" unicode="&#x20ac;"
d="M803 1397q-174 0 -288 -125.5t-155 -364.5h502v-82h-510l-4 -104v-24q0 -65 4 -87h449v-82h-443q30 -217 147.5 -338.5t301.5 -121.5q148 0 287 65v-94q-81 -34 -150.5 -46.5t-140.5 -12.5q-228 0 -367.5 140t-181.5 408h-180v82h172q-4 38 -4 113l4 102h-172v82h184
q39 272 183 425t362 153q88 0 161 -17t148 -57l-39 -86q-132 72 -270 72z" />
<glyph glyph-name="trademark" unicode="&#x2122;" horiz-adv-x="1485"
d="M313 741h-86v643h-217v78h522v-78h-219v-643zM913 741l-221 609h-6l4 -201v-408h-82v721h125l221 -606l224 606h125v-721h-86v398l4 207h-7l-227 -605h-74z" />
<glyph glyph-name="uni0492" unicode="&#x492;" horiz-adv-x="1028"
d="M309 772h439v-94h-439v-678h-102v678h-160v94h160v690h801v-94h-699v-596z" />
<glyph glyph-name="uni0493" unicode="&#x493;" horiz-adv-x="862"
d="M821 1001h-540v-409h366v-86h-366v-506h-99v506h-164v86h164v495h639v-86z" />
<glyph glyph-name="uni04A4" unicode="&#x4a4;" horiz-adv-x="1602"
d="M1591 1366h-325v-1366h-103v719h-854v-719h-102v1462h102v-649h854v649h428v-96z" />
<glyph glyph-name="uni04A5" unicode="&#x4a5;" horiz-adv-x="1430"
d="M281 1087v-477h690v477h418v-86h-320v-1001h-98v524h-690v-524h-99v1087h99z" />
<glyph glyph-name="uni04A6" unicode="&#x4a6;" horiz-adv-x="2146"
d="M1184 0h-103v1366h-772v-1366h-102v1462h977v-682q113 23 223 23q194 0 336 -79.5t216.5 -226.5t74.5 -343q0 -321 -136 -493.5t-382 -172.5q-145 0 -259 49v101q134 -56 259 -56q192 0 299.5 150.5t107.5 423.5q0 261 -139 408t-389 147q-116 0 -211 -23v-688z" />
<glyph glyph-name="uni04A7" unicode="&#x4a7;" horiz-adv-x="1737"
d="M989 610q93 27 174 27q250 0 378 -146t128 -434q0 -267 -96 -413t-275 -146q-138 0 -225 58v100q114 -68 221 -68q132 0 201.5 123.5t69.5 347.5q0 488 -412 488q-91 0 -164 -27v-520h-98v993h-610v-993h-99v1087h807v-477z" />
<glyph glyph-name="uni04A8" unicode="&#x4a8;" horiz-adv-x="1565"
d="M1436 678q0 -185 -78.5 -348.5t-212.5 -251.5q89 -68 196 -68q74 0 129 21v-92q-42 -23 -139 -23t-169.5 30.5t-131.5 73.5q-100 -40 -252 -40q-196 0 -344.5 92.5t-226.5 262t-78 390.5q0 361 163 560.5t460 199.5q130 0 209 -27l-27 -96q-75 24 -186 24
q-508 0 -508 -663q0 -311 143.5 -479t404.5 -168q38 0 85.5 5.5t74.5 14.5q-105 98 -163 250t-58 332q0 245 94.5 380.5t257.5 135.5q173 0 265 -132.5t92 -383.5zM1329 680q0 416 -250 416q-115 0 -181.5 -109.5t-66.5 -308.5q0 -350 224 -543q132 72 203 215t71 330z" />
<glyph glyph-name="uni04A9" unicode="&#x4a9;" horiz-adv-x="1239"
d="M686 496q0 -122 39.5 -208t114.5 -151q78 51 126 145t48 220q0 139 -36.5 210.5t-121.5 71.5q-170 0 -170 -288zM1067 -53q-126 0 -240 76q-86 -43 -227 -43q-145 0 -254 70.5t-168 197.5t-59 287q0 266 117 419.5t329 153.5q82 0 146 -18l-21 -91q-69 19 -129 19
q-162 0 -250 -124.5t-88 -358.5q0 -139 47.5 -245t138 -163t218.5 -57q78 0 127 22q-172 154 -172 406q0 178 72 277t202 99q128 0 195 -96t67 -276q0 -132 -53 -241.5t-150 -178.5q24 -18 68 -32.5t84 -14.5q62 0 107 14v-88q-44 -14 -107 -14z" />
<glyph glyph-name="brevetildecomb" horiz-adv-x="0"
d="M-612 1241q-253 0 -275 246h82q14 -87 60.5 -124.5t134.5 -37.5t134.5 37.5t61.5 124.5h80q-30 -246 -278 -246zM-471 1587q-39 0 -74.5 20t-67.5 43.5t-61 43.5t-55 20q-80 0 -105 -129h-73q14 97 60 154t124 57q39 0 73.5 -19.5t66 -43t60.5 -43t58 -19.5q45 0 66 35
t32 92h76q-11 -101 -58.5 -156t-121.5 -55z" />
<glyph glyph-name="gcommaaccent.alt" horiz-adv-x="1219"
d="M938 -2q0 118 6 172h-6q-118 -190 -369 -190q-214 0 -332 142t-118 410q0 275 118 425.5t338 150.5q236 0 353 -174h6l18 153h84v-1107q0 -214 -122 -343t-326 -129q-221 0 -377 70v107q166 -88 383 -88q160 0 252 100t92 280v21zM571 68q198 0 282.5 109t84.5 366v12
q0 245 -85 354t-271 109q-176 0 -267.5 -124t-91.5 -364q0 -229 89.5 -345.5t258.5 -116.5zM731 1552q-27 -58 -53.5 -159t-32.5 -152h-112v14q20 77 61.5 165t83.5 149h53v-17z" />
<glyph glyph-name="I" unicode="I" horiz-adv-x="516"
d="M207 0v1462h102v-1462h-102z" />
<glyph glyph-name="Igrave" unicode="&#xcc;" horiz-adv-x="516"
d="M207 0v1462h102v-1462h-102zM320 1579h-69q-96 79 -188.5 171.5t-125.5 139.5v17h142q26 -48 98.5 -142t142.5 -170v-16z" />
<glyph glyph-name="Iacute" unicode="&#xcd;" horiz-adv-x="516"
d="M207 0v1462h102v-1462h-102zM191 1595q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70v16z" />
<glyph glyph-name="Icircumflex" unicode="&#xce;" horiz-adv-x="516"
d="M207 0v1462h102v-1462h-102zM-32 1595q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70v16z" />
<glyph glyph-name="Idieresis" unicode="&#xcf;" horiz-adv-x="516"
d="M207 0v1462h102v-1462h-102zM5 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM386 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Binary file not shown.

View File

@ -30,16 +30,13 @@
<glyph unicode="&#117;" d="M366 174l0-19c0-2-1-4-3-6-2-2-4-3-6-3l-202 0c-2 0-4 1-6 3-2 2-3 4-3 6l0 19c0 2 1 5 3 6 2 2 4 3 6 3l202 0c2 0 4-1 6-3 2-1 3-4 3-6z m0 73l0-18c0-3-1-5-3-7-2-2-4-3-6-3l-202 0c-2 0-4 1-6 3-2 2-3 4-3 7l0 18c0 3 1 5 3 6 2 2 4 3 6 3l202 0c2 0 4-1 6-3 2-1 3-3 3-6z m-256-174l292 0 0 220-119 0c-7 0-14 2-19 8-5 5-8 11-8 19l0 119-146 0z m183 256l107 0c-2 6-4 10-6 12l-90 89c-2 3-6 5-11 7z m146-9l0-256c0-8-3-14-8-19-5-6-12-8-20-8l-310 0c-8 0-15 2-20 8-5 5-8 11-8 19l0 384c0 8 3 14 8 19 5 6 12 8 20 8l182 0c8 0 16-1 26-5 9-4 16-9 21-14l89-89c6-5 10-13 14-22 4-9 6-17 6-25z"/>
<glyph unicode="&#118;" d="M256 475c40 0 77-9 110-29 34-20 60-46 80-80 20-33 29-70 29-110 0-40-9-77-29-110-20-34-46-60-80-80-33-20-70-29-110-29-40 0-77 9-110 29-34 20-60 46-80 80-20 33-29 70-29 110 0 40 9 77 29 110 20 34 46 60 80 80 33 20 70 29 110 29z m37-356l0 54c0 3-1 5-3 7-2 2-4 3-6 3l-55 0c-3 0-5-1-7-3-2-2-3-4-3-7l0-54c0-2 1-5 3-6 2-2 4-3 7-3l55 0c2 0 4 1 6 2 2 2 3 4 3 7z m-1 98l5 178c0 2-1 4-3 5-2 2-4 2-7 2l-62 0c-3 0-5 0-7-2-2-1-3-3-3-5l5-178c0-1 1-3 3-5 1-1 4-2 6-2l53 0c3 0 5 1 7 2 2 2 3 4 3 5z"/>
<glyph unicode="&#119;" d="M512 174l0-55c0-3-1-5-3-7-2-1-4-2-6-2l-393 0 0-55c0-3-1-5-3-7-2-1-4-2-6-2-3 0-5 1-7 3l-91 91c-2 2-3 4-3 6 0 3 1 5 3 7l91 91c2 2 4 3 7 3 2 0 4-1 6-3 2-2 3-4 3-6l0-55 393 0c2 0 4-1 6-3 2-2 3-4 3-6z m0 155c0-3-1-5-3-6l-91-92c-2-2-4-2-7-2-2 0-4 0-6 2-2 2-3 4-3 7l0 55-393 0c-2 0-4 0-6 2-2 2-3 4-3 7l0 55c0 2 1 4 3 6 2 2 4 3 6 3l393 0 0 55c0 2 1 4 3 6 2 2 4 3 6 3 3 0 5-1 7-3l91-91c2-2 3-4 3-7z"/>
<glyph unicode="&#120;" d="M165 302l0-55c0-8-3-14-8-20-6-5-12-8-20-8l-55 0c-7 0-14 3-19 8-5 6-8 12-8 20l0 55c0 7 3 14 8 19 5 5 12 8 19 8l55 0c8 0 14-3 20-8 5-5 8-12 8-19z m146 0l0-55c0-8-3-14-8-20-5-5-12-8-20-8l-54 0c-8 0-15 3-20 8-5 6-8 12-8 20l0 55c0 7 3 14 8 19 5 5 12 8 20 8l54 0c8 0 15-3 20-8 5-5 8-12 8-19z m146 0l0-55c0-8-3-14-8-20-5-5-12-8-19-8l-55 0c-8 0-14 3-20 8-5 6-8 12-8 20l0 55c0 7 3 14 8 19 6 5 12 8 20 8l55 0c7 0 14-3 19-8 5-5 8-12 8-19z"/>
<glyph unicode="&#121;" d="M311 155l0-54c0-8-3-15-8-20-5-5-12-8-20-8l-54 0c-8 0-15 3-20 8-5 5-8 12-8 20l0 54c0 8 3 15 8 20 5 5 12 8 20 8l54 0c8 0 15-3 20-8 5-5 8-12 8-20z m0 147l0-55c0-8-3-14-8-20-5-5-12-8-20-8l-54 0c-8 0-15 3-20 8-5 6-8 12-8 20l0 55c0 7 3 14 8 19 5 5 12 8 20 8l54 0c8 0 15-3 20-8 5-5 8-12 8-19z m0 146l0-55c0-7-3-14-8-19-5-6-12-8-20-8l-54 0c-8 0-15 2-20 8-5 5-8 12-8 19l0 55c0 8 3 14 8 19 5 6 12 8 20 8l54 0c8 0 15-2 20-8 5-5 8-11 8-19z"/>
<glyph unicode="&#122;" d="M456 428c3-8 2-15-4-20l-141-141 0-212c0-8-4-14-11-17-3-1-5-1-7-1-6 0-10 1-13 5l-73 73c-4 4-6 8-6 13l0 139-141 141c-6 5-7 12-4 20 4 7 9 11 17 11l366 0c8 0 13-4 17-11z"/>
<glyph unicode="&#65;" d="M201 165c0-8-1-16-3-24-3-8-7-15-13-22-6-6-12-9-20-9-8 0-15 3-21 9-6 7-10 14-12 22-3 8-4 16-4 24 0 7 1 15 4 23 2 8 6 15 12 22 6 6 13 9 21 9 8 0 14-3 20-9 6-7 10-14 13-22 2-8 3-16 3-23z m183 0c0-8-1-16-4-24-2-8-6-15-12-22-6-6-13-9-21-9-8 0-14 3-20 9-6 7-10 14-13 22-2 8-3 16-3 24 0 7 1 15 3 23 3 8 7 15 13 22 6 6 12 9 20 9 8 0 15-3 21-9 6-7 10-14 12-22 3-8 4-16 4-23z m46 0c0 22-7 42-20 58-13 16-31 24-53 24-8 0-27-2-56-6-14-2-29-3-45-3-16 0-31 1-45 3-29 4-47 6-56 6-22 0-40-8-53-24-13-16-20-36-20-58 0-17 3-32 9-44 7-13 14-23 24-30 9-7 21-13 34-17 14-4 28-7 40-8 13-2 27-2 43-2l48 0c16 0 30 0 43 2 12 1 26 4 40 8 13 4 25 10 34 17 10 7 17 17 24 30 6 12 9 27 9 44z m64 50c0-40-6-71-18-95-7-14-17-27-30-38-13-10-26-19-40-24-14-6-30-11-49-14-18-3-34-5-49-6-14-1-30-1-47-1-15 0-29 0-41 0-12 1-26 2-42 4-16 2-31 5-44 9-13 3-26 8-39 14-13 6-24 14-34 23-11 10-19 21-25 33-12 24-18 55-18 95 0 45 13 83 39 113-5 16-8 32-8 49 0 22 5 42 15 62 21 0 39-4 54-11 16-8 34-20 54-36 28 7 58 10 89 10 28 0 54-3 80-9 20 16 37 27 53 35 16 7 34 11 54 11 10-20 15-40 15-62 0-17-3-33-8-48 26-31 39-69 39-114z"/>
<glyph unicode="&#66;" d="M293 119l0 55c0 2-1 5-3 6-2 2-4 3-7 3l-54 0c-3 0-5-1-7-3-2-1-3-4-3-6l0-55c0-3 1-5 3-7 2-1 4-2 7-2l54 0c3 0 5 1 7 2 2 2 3 4 3 7z m73 192c0 17-6 32-16 46-11 15-24 26-40 34-16 7-32 11-48 11-47 0-82-20-106-61-3-4-2-8 2-12l38-28c1-1 3-2 5-2 3 0 6 1 7 4 10 13 19 21 25 26 6 4 15 7 24 7 10 0 18-3 25-8 7-5 11-10 11-17 0-7-2-13-6-17-4-4-10-9-20-13-12-5-23-13-33-25-10-11-15-23-15-35l0-11c0-2 1-5 3-6 2-2 4-3 7-3l54 0c3 0 5 1 7 3 2 1 3 4 3 6 0 4 2 9 6 14 4 6 9 11 15 15 6 3 11 6 14 8 4 2 8 5 13 10 6 4 10 9 13 13 3 5 6 11 8 18 3 7 4 14 4 23z m109-55c0-40-9-77-29-110-20-34-46-60-80-80-33-20-70-29-110-29-40 0-77 9-110 29-34 20-60 46-80 80-20 33-29 70-29 110 0 40 9 77 29 110 20 34 46 60 80 80 33 20 70 29 110 29 40 0 77-9 110-29 34-20 60-46 80-80 20-33 29-70 29-110z"/>
<glyph unicode="&#67;" d="M468 210c0-1 0-1 0-2-12-51-38-92-77-124-38-32-84-47-136-47-28 0-55 5-81 15-26 11-49 26-69 45l-37-37c-4-3-8-5-13-5-5 0-9 2-13 5-4 4-5 8-5 13l0 128c0 5 1 9 5 13 4 4 8 5 13 5l128 0c5 0 9-1 13-5 3-4 5-8 5-13 0-5-2-9-5-13l-39-39c13-12 28-22 46-29 17-7 35-10 53-10 26 0 49 6 71 18 23 13 40 30 54 51 2 4 7 15 15 34 1 4 4 6 8 6l55 0c3 0 5 0 7-2 1-2 2-4 2-7z m7 229l0-128c0-5-1-9-5-13-4-4-8-5-13-5l-128 0c-5 0-9 1-13 5-3 4-5 8-5 13 0 5 2 9 5 13l40 39c-28 26-62 39-100 39-26 0-49-6-71-18-23-13-40-30-54-51-2-4-7-15-15-34-1-4-4-6-8-6l-57 0c-3 0-5 0-7 2-1 2-2 4-2 7l0 2c12 51 38 92 77 124 39 32 85 47 137 47 28 0 55-5 81-15 26-11 50-26 70-45l37 37c4 3 8 5 13 5 5 0 9-2 13-5 4-4 5-8 5-13z"/>
<glyph unicode="&#68;" d="M107 6c-2-7-7-8-14-4-6 3-8 9-8 17 2 35 10 73 26 116-34 53-43 107-27 162 4-11 9-24 17-40 7-16 15-29 22-41 8-12 13-17 17-15 2 1 2 15 0 42-3 27-5 55-6 85-1 30 3 57 13 81 7 15 21 31 41 48 19 17 37 29 53 36-8-16-14-32-17-49-3-16-4-29-2-40 2-10 5-15 11-16 4 0 18 21 43 62 24 40 42 61 54 62 16 1 35-4 58-15 24-11 38-22 42-33 4-8 4-22 0-41-4-18-11-33-20-42-15-15-40-26-75-32-35-6-54-10-58-12-6-4-4-9 6-18 18-16 48-19 90-10-19-27-42-47-70-58-27-12-49-18-67-20-18-1-27-3-28-5-1-8 7-17 25-27 18-11 36-13 52-8-10-19-21-33-32-43-12-9-21-15-28-17-7-3-20-5-39-6-19-1-33-3-43-4 0 0-36-115-36-115"/>
<glyph unicode="&#69;" 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"/>
<glyph unicode="&#70;" d="M388 461c0 0 73-80 73-80 0 0 0-279 0-279 0-13-5-25-15-35-10-11-22-16-36-16 0 0-308 0-308 0-13 0-25 5-35 16-11 10-16 22-16 35 0 0 0 308 0 308 0 14 5 26 16 36 10 10 22 15 35 15 0 0 286 0 286 0m-30-154c0 0 0 128 0 128 0 0-204 0-204 0 0 0 0-128 0-128 0-7 2-13 7-18 5-5 11-7 18-7 0 0 154 0 154 0 7 0 13 2 18 7 5 5 7 11 7 18m-25 103c0 0 0-103 0-103 0 0-51 0-51 0 0 0 0 103 0 103 0 0 51 0 51 0"/>
<glyph unicode="&#71;" 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="&#72;" d="M256 484c-126 0-228-102-228-228 0-126 102-228 228-228 126 0 228 102 228 228 0 126-102 228-228 228z m0-399c-94 0-171 77-171 171 0 94 77 171 171 171 94 0 171-77 171-171 0-94-77-171-171-171z m-2 312c-23-2-40-23-37-46l12-118c2-13 12-24 26-26 16-1 30 10 31 26l13 118c0 3 0 6 0 8-2 23-22 40-45 38z m-21-220c-7-7-11-16-11-25 0-10 4-19 11-26 7-6 16-10 25-10 9 0 19 4 25 10 7 7 11 16 11 26 0 9-4 18-11 25-13 13-37 13-50 0z"/>
<glyph unicode="&#73;" d="M427 491l-363 0c-35 0-64-29-64-64l0-256c0-36 43-43 64-43l0-128 192 128 171 0c35 0 64 29 64 64l0 235c0 35-29 64-64 64z m21-299c0-12-10-21-21-21l-192 0-128-107 0 107-43 0c-12 0-21 9-21 21l0 235c0 11 9 21 21 21l363 0c11 0 21-10 21-21z"/>
<glyph unicode="&#75;" 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"/>
@ -49,7 +46,6 @@
<glyph unicode="&#81;" d="M224 512c-123 0-223-104-223-232 0-128 100-231 223-231 52 0 100 18 137 49l99-98 51 52-101 101c23 37 37 80 37 127 0 128-100 232-223 232z m0-32c107 0 193-89 193-200 0-111-86-200-193-200-107 0-193 89-193 200 0 111 86 200 193 200z"/>
<glyph unicode="&#76;" d="M0 510l0-508 512 0 0 508z m52-51l409 0 0-408-409 0z m32-86l344 0 0-35-344 0z m0-68l344 0 0-35-344 0z m0-67l344 0 0-36-344 0z m0-68l344 0 0-35-344 0z"/>
<glyph unicode="&#84;" d="M0 455l0-398c0-32 25-57 57-57l398 0c32 0 57 25 57 57l0 398c0 32-25 57-57 57l-398 0c-32 0-57-25-57-57z m341-114c0-47-38-85-85-85-47 0-85 38-85 85 0 48 38 86 85 86 47 0 85-38 85-86z m-256-228c0 58 114 89 171 89 57 0 171-31 171-89l0-28-342 0z"/>
<glyph unicode="&#74;" 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="&#78;" d="M112 49l44-45 100 100 100-100 44 45-144 144z m288 414l-44 45-100-100-100 100-44-45 144-144z"/>
<glyph unicode="&#85;" d="M328 511l-284 1c0 0-2-336-2-492l46 0 59 38 43-58 60 97 56-75 57 56c5-5 12-11 19-18 8-8 17-17 25-24 7-7 12-13 17-16l46 0 0 349z m50-386l-16 15-51-51-65 86-59-97-29 39-63-46 0 393 223 1 0-102c0-2 0-3 1-4 1-1 3-1 4-1l95 0 0-277c-21 19-40 44-40 44z"/>
<glyph unicode="&#82;" d="M0 512l0-512 512 0 0 512z m52-51l179 0 0-102-179 0z m230 0l179 0 0-410-179 0z m-230-154l179 0 0-106-179 0z m0-157l179 0 0-99-179 0z"/>
@ -59,4 +55,9 @@
<glyph unicode="&#88;" d="M456 343c0 1 0 1 0 1 0 5-2 10-5 13l0 1-40 69c-1 6-6 10-13 10 0 0 0 0-1 0l0 0-283 0 0 0c0 0 0 0 0 0-5 0-9-3-12-7l0 0-42-72 0 0c-3-4-4-9-4-14 0 0 0 0 0-1l0-247c0 0 0 0 0 0 0-12 9-21 20-21 1 0 1 0 1 0l358 0c0 0 0 0 0 0 12 0 21 9 21 21 0 0 0 0 0 0l0 247z m-131-125l-64-90c-1-1-3-2-5-2 0 0 0 0 0 0-2 0-4 1-5 2l-64 90c-1 2-1 4 0 6 1 2 3 3 5 3l30 0 0 81c0 3 3 6 6 6l56 0c3 0 6-3 6-6l0-81 30 0c2 0 4-1 5-3 1-2 1-4 0-6z m-231 147l27 47 270 0 27-47z"/>
<glyph unicode="&#89;" d="M256 352l130-130 41 40-171 171-171-171 41-40z m-171-273l342 0 0 57-342 0z"/>
<glyph unicode="&#90;" d="M171 128l170 0 0 171 114 0-199 199-199-199 114 0z m-114-57l398 0 0-57-398 0z"/>
<glyph unicode="&#65;" d="M201 165c0-8-1-16-3-24-3-8-7-15-13-22-6-6-12-9-20-9-8 0-15 3-21 9-6 7-10 14-12 22-3 8-4 16-4 24 0 7 1 15 4 23 2 8 6 15 12 22 6 6 13 9 21 9 8 0 14-3 20-9 6-7 10-14 13-22 2-8 3-16 3-23z m183 0c0-8-1-16-4-24-2-8-6-15-12-22-6-6-13-9-21-9-8 0-14 3-20 9-6 7-10 14-13 22-2 8-3 16-3 24 0 7 1 15 3 23 3 8 7 15 13 22 6 6 12 9 20 9 8 0 15-3 21-9 6-7 10-14 12-22 3-8 4-16 4-23z m46 0c0 22-7 42-20 58-13 16-31 24-53 24-8 0-27-2-56-6-14-2-29-3-45-3-16 0-31 1-45 3-29 4-47 6-56 6-22 0-40-8-53-24-13-16-20-36-20-58 0-17 3-32 9-44 7-13 14-23 24-30 9-7 21-13 34-17 14-4 28-7 40-8 13-2 27-2 43-2l48 0c16 0 30 0 43 2 12 1 26 4 40 8 13 4 25 10 34 17 10 7 17 17 24 30 6 12 9 27 9 44z m64 50c0-40-6-71-18-95-7-14-17-27-30-38-13-10-26-19-40-24-14-6-30-11-49-14-18-3-34-5-49-6-14-1-30-1-47-1-15 0-29 0-41 0-12 1-26 2-42 4-16 2-31 5-44 9-13 3-26 8-39 14-13 6-24 14-34 23-11 10-19 21-25 33-12 24-18 55-18 95 0 45 13 83 39 113-5 16-8 32-8 49 0 22 5 42 15 62 21 0 39-4 54-11 16-8 34-20 54-36 28 7 58 10 89 10 28 0 54-3 80-9 20 16 37 27 53 35 16 7 34 11 54 11 10-20 15-40 15-62 0-17-3-33-8-48 26-31 39-69 39-114z"/>
<glyph unicode="&#120;" d="M411 167l-182 0 0 0c0 0 0 0 0 0-6 0-11-6-11-12 0 0 0 0 0-1l0-53 0 0c0-6 5-11 11-11l0 0 182 0 0 0c6 0 11 5 11 11l0 53c0 1 0 1 0 1 0 6-5 12-11 12z m-256 0l-54 0c-6 0-11-6-11-12l0-54c0-6 5-11 11-11l54 0c6 0 12 5 12 11l0 54c0 6-6 12-12 12z m256 128l-182 0 0 0c0 0 0 0 0 0-6 0-11-6-11-12 0 0 0 0 0-1l0-53 0 0c0-6 5-11 11-11l0 0 182 0 0 0c6 0 11 5 11 11l0 53c0 1 0 1 0 1 0 6-5 12-11 12z m-256 0l-54 0c-6 0-11-6-11-12l0-54c0-6 5-11 11-11l54 0c6 0 12 5 12 11l0 54c0 6-6 12-12 12z m63 62l0 0c0-6 5-12 11-12l0 0 182 0 0 0c6 0 11 6 11 12l0 53c0 0 0 0 0 1 0 6-5 11-11 11l0 0-182 0 0 0c0 0 0 0 0 0-6 0-11-5-11-11 0-1 0-1 0-1l0-53z m-63 65l-54 0c-6 0-11-5-11-11l0-54c0-6 5-12 11-12l54 0c6 0 12 6 12 12l0 54c0 6-6 11-12 11z"/>
<glyph unicode="&#71;" d="M1 332l369 0 49-182 57 215-378 0 16 27 226 0 21 37 114 0c0 0 17 2 30-15 13-17 0-59 0-59l-72-272-368 0z"/>
<glyph unicode="&#74;" d="M494 327c0-4-3-9-8-14l-103-101 24-143c0-1 0-3 0-5 0-4-1-8-3-10-2-3-4-5-8-5-4 0-8 2-12 4l-128 67-128-67c-4-2-8-4-12-4-4 0-7 2-9 5-2 2-3 6-3 10 0 1 0 3 1 5l24 143-104 101c-4 6-7 10-7 14 0 7 6 12 16 13l144 21 64 130c4 8 8 12 14 12 6 0 10-4 14-12l64-130 144-21c10-1 16-6 16-13z"/>
<glyph unicode="&#48;" d="M343 225l88 85-121 18-54 109-54-109-121-18 88-85-21-120 108 57 108-57z m151 102c0-4-3-9-8-14l-103-101 24-143c0-1 0-3 0-5 0-10-3-15-11-15-4 0-8 2-12 4l-128 67-128-67c-4-2-8-4-12-4-4 0-7 2-9 5-2 2-3 6-3 10 0 1 0 3 1 5l24 143-104 101c-4 6-7 10-7 14 0 7 6 12 16 13l144 21 64 130c4 8 8 12 14 12 6 0 10-4 14-12l64-130 144-21c10-1 16-6 16-13z"/>
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

BIN
app/images/logo-color.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
app/images/menu-vert.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
app/images/quote.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

View File

@ -2,22 +2,23 @@ doctype html
html(lang="en")
head
meta(charset="utf-8")
title Taiga
meta(http-equiv="content-type", content="text/html; charset=utf-8")
meta(name="description", content="Taiga Landing page")
meta(name="keywords", content="Agile, Taiga, Management, Github")
meta(name="fragment", content="!")
// Main meta
title Taiga
meta(name="description", content="Taiga is a project management platform for startups and agile developers & designers who want a simple, beautiful tool that makes work truly enjoyable.")
meta(name="keywords", content="agile, scrum, taiga, management, project, developer, designer, user experience")
//-meta(name="viewport", content="width=device-width, user-scalable=no")
link(rel="stylesheet", href="/styles/main.css")
link(rel="icon", type="image/png", href="/images/favicon.png")
//- PRERENDER SERVICE: This is to know when the page is completely loaded.
script(type='text/javascript').
window.prerenderReady = false;
body(tg-main)
include partials/includes/modules/projects-nav
//- the content of nav.menu is in coffe.modules.base TaigaMain directive
nav.menu.hidden(tg-project-menu)
include partials/includes/components/notification-message
div(tg-navigation-bar)
div.master(ng-view)
@ -31,13 +32,11 @@ html(lang="en")
include partials/includes/modules/lightbox-generic-error
div.lightbox.lightbox-generic-loading
include partials/includes/modules/lightbox-generic-loading
div.lightbox.lightbox-search(tg-search-box)
include partials/includes/modules/lightbox-search
div.lightbox.lightbox-feedback.lightbox-generic-form(tg-lb-feedback)
include partials/includes/modules/lightbox-feedback
include partials/includes/modules/loader
include partials/includes/components/notification-message
script(src="/js/libs.js?v=#{v}")
script(src="/js/templates.js?v=#{v}")
script(src="/js/app-loader.js?v=#{v}")

File diff suppressed because it is too large Load Diff

326
app/js/tg-repeat.js Normal file
View File

@ -0,0 +1,326 @@
/*
--replace
minError -> angular.$$minErr
isString -> angular.isString
isArray -> angular.isArray
var expression = $attr.ngRepeat; -> var expression = $attr.tgRepeat;
forEach(nextBlockOrder, function(block) { -> nextBlockOrder.forEach(function(block) {
$scope.$watchCollection(rhs, function ngRepeatAction(collection) {
->
$scope.$watch(rhs, function ngRepeatAction(immutable_collection) {
var collection = []
if (immutable_collection.toJS) {
collection = immutable_collection.toJS();
}
$scope[aliasAs] = collection; -> $scope[aliasAs] = immutable_collection;
value = collection[key];
immutable_value = immutable_collection.get(key); #x2
trackById = trackByIdFn(key, value, index);
->
trackById = trackByIdFn(key, immutable_value, index);
updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
-> (x2)
updateScope(block.scope, index, valueIdentifier, immutable_value, keyIdentifier, key, collectionLength);
--copy from angular
copy angular hashKey
copy angular createMap
copy angular isArrayLike
copy angular isWindow
copy angular NODE_TYPE_ELEMENT
copy angular nextUid
copy angular getBlockNodes
--add
jqLite = $
var uid = 0;
*/
(function() {
var NODE_TYPE_ELEMENT = 1;
var uid = 0;
function nextUid() {
return ++uid;
}
function hashKey(obj, nextUidFn) {
var key = obj && obj.$$hashKey;
if (key) {
if (typeof key === 'function') {
key = obj.$$hashKey();
}
return key;
}
var objType = typeof obj;
if (objType == 'function' || (objType == 'object' && obj !== null)) {
key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)();
} else {
key = objType + ':' + obj;
}
return key;
}
function createMap() {
return Object.create(null);
}
function isArrayLike(obj) {
if (obj == null || isWindow(obj)) {
return false;
}
var length = obj.length;
if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
return true;
}
return angular.isString(obj) || angular.isArray(obj) || length === 0 ||
typeof length === 'number' && length > 0 && (length - 1) in obj;
}
function isWindow(obj) {
return obj && obj.window === obj;
}
function isString(value) {return typeof value === 'string';}
function getBlockNodes(nodes) {
// TODO(perf): just check if all items in `nodes` are siblings and if they are return the original
// collection, otherwise update the original collection.
var node = nodes[0];
var endNode = nodes[nodes.length - 1];
var blockNodes = [node];
do {
node = node.nextSibling;
if (!node) break;
blockNodes.push(node);
} while (node !== endNode);
return jqLite(blockNodes);
}
var isArray = Array.isArray;
var jqLite = $;
var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
var NG_REMOVED = '$$NG_REMOVED';
var ngRepeatMinErr = angular.$$minErr('ngRepeat');
var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) {
// TODO(perf): generate setters to shave off ~40ms or 1-1.5%
scope[valueIdentifier] = value;
if (keyIdentifier) scope[keyIdentifier] = key;
scope.$index = index;
scope.$first = (index === 0);
scope.$last = (index === (arrayLength - 1));
scope.$middle = !(scope.$first || scope.$last);
// jshint bitwise: false
scope.$odd = !(scope.$even = (index&1) === 0);
// jshint bitwise: true
};
var getBlockStart = function(block) {
return block.clone[0];
};
var getBlockEnd = function(block) {
return block.clone[block.clone.length - 1];
};
return {
restrict: 'A',
multiElement: true,
transclude: 'element',
priority: 1000,
terminal: true,
$$tlb: true,
compile: function ngRepeatCompile($element, $attr) {
var expression = $attr.tgRepeat;
var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' ');
var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
if (!match) {
throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",
expression);
}
var lhs = match[1];
var rhs = match[2];
var aliasAs = match[3];
var trackByExp = match[4];
match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);
if (!match) {
throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.",
lhs);
}
var valueIdentifier = match[3] || match[1];
var keyIdentifier = match[2];
if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.",
aliasAs);
}
var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn;
var hashFnLocals = {$id: hashKey};
if (trackByExp) {
trackByExpGetter = $parse(trackByExp);
} else {
trackByIdArrayFn = function(key, value) {
return hashKey(value);
};
trackByIdObjFn = function(key) {
return key;
};
}
return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) {
if (trackByExpGetter) {
trackByIdExpFn = function(key, value, index) {
// assign key, value, and $index to the locals so that they can be used in hash functions
if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
hashFnLocals[valueIdentifier] = value;
hashFnLocals.$index = index;
return trackByExpGetter($scope, hashFnLocals);
};
}
// Store a list of elements from previous run. This is a hash where key is the item from the
// iterator, and the value is objects with following properties.
// - scope: bound scope
// - element: previous element.
// - index: position
//
// We are using no-proto object so that we don't need to guard against inherited props via
// hasOwnProperty.
var lastBlockMap = createMap();
$scope.$watch(rhs, function ngRepeatAction(immutable_collection) {
var collection = []
if (immutable_collection && immutable_collection.toJS) {
collection = immutable_collection.toJS();
}
var index, length,
previousNode = $element[0], // node that cloned nodes should be inserted after
// initialized to the comment node anchor
nextNode,
// Same as lastBlockMap but it has the current state. It will become the
// lastBlockMap on the next iteration.
nextBlockMap = createMap(),
collectionLength,
key, value, // key/value of iteration
trackById,
trackByIdFn,
collectionKeys,
block, // last object information {scope, element, id}
nextBlockOrder,
elementsToRemove;
if (aliasAs) {
$scope[aliasAs] = immutable_collection;
}
if (isArrayLike(collection)) {
collectionKeys = collection;
trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
} else {
trackByIdFn = trackByIdExpFn || trackByIdObjFn;
// if object, extract keys, in enumeration order, unsorted
collectionKeys = [];
for (var itemKey in collection) {
if (collection.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
collectionKeys.push(itemKey);
}
}
}
collectionLength = collectionKeys.length;
nextBlockOrder = new Array(collectionLength);
// locate existing items
for (index = 0; index < collectionLength; index++) {
key = (collection === collectionKeys) ? index : collectionKeys[index];
value = collection[key];
immutable_value = immutable_collection.get(key);
trackById = trackByIdFn(key, immutable_value, index);
if (lastBlockMap[trackById]) {
// found previously seen block
block = lastBlockMap[trackById];
delete lastBlockMap[trackById];
nextBlockMap[trackById] = block;
nextBlockOrder[index] = block;
} else if (nextBlockMap[trackById]) {
// if collision detected. restore lastBlockMap and throw an error
nextBlockOrder.forEach(function(block) {
if (block && block.scope) lastBlockMap[block.id] = block;
});
throw ngRepeatMinErr('dupes',
"Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}",
expression, trackById, value);
} else {
// new never before seen block
nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined};
nextBlockMap[trackById] = true;
}
}
// remove leftover items
for (var blockKey in lastBlockMap) {
block = lastBlockMap[blockKey];
elementsToRemove = getBlockNodes(block.clone);
$animate.leave(elementsToRemove);
if (elementsToRemove[0].parentNode) {
// if the element was not removed yet because of pending animation, mark it as deleted
// so that we can ignore it later
for (index = 0, length = elementsToRemove.length; index < length; index++) {
elementsToRemove[index][NG_REMOVED] = true;
}
}
block.scope.$destroy();
}
// we are not using forEach for perf reasons (trying to avoid #call)
for (index = 0; index < collectionLength; index++) {
key = (collection === collectionKeys) ? index : collectionKeys[index];
value = collection[key];
immutable_value = immutable_collection.get(key);
block = nextBlockOrder[index];
if (block.scope) {
// if we have already seen this object, then we need to reuse the
// associated scope/element
nextNode = previousNode;
// skip nodes that are already pending removal via leave animation
do {
nextNode = nextNode.nextSibling;
} while (nextNode && nextNode[NG_REMOVED]);
if (getBlockStart(block) != nextNode) {
// existing item which got moved
$animate.move(getBlockNodes(block.clone), null, jqLite(previousNode));
}
previousNode = getBlockEnd(block);
updateScope(block.scope, index, valueIdentifier, immutable_value, keyIdentifier, key, collectionLength);
} else {
// new item which we don't know about
$transclude(function ngRepeatTransclude(clone, scope) {
block.scope = scope;
// http://jsperf.com/clone-vs-createcomment
var endNode = ngRepeatEndComment.cloneNode(false);
clone[clone.length++] = endNode;
// TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper?
$animate.enter(clone, null, jqLite(previousNode));
previousNode = endNode;
// Note: We only need the first/last node of the cloned nodes.
// However, we need to keep the reference to the jqlite wrapper as it might be changed later
// by a directive with templateUrl when its template arrives.
block.clone = clone;
nextBlockMap[block.id] = block;
updateScope(block.scope, index, valueIdentifier, immutable_value, keyIdentifier, key, collectionLength);
});
}
}
lastBlockMap = nextBlockMap;
});
};
}
};
}];
angular.module("tgRepeat", []).directive("tgRepeat", ngRepeatDirective);
})();

View File

@ -100,6 +100,10 @@
"SAT": "Ds"
}
},
"SEE_USER_PROFILE": "See {{username }} profile",
"USER_STORY": "Història d'usuari",
"TASK": "Task",
"ISSUE": "Issue",
"TAGS": {
"PLACEHOLDER": "Afegir tag",
"DELETE": "Elimina l'etiqueta",
@ -125,7 +129,8 @@
"ASSIGNED_TO": "Assignat a",
"POINTS": "Punts",
"BLOCKED_NOTE": "Nota de bloqueig",
"IS_BLOCKED": "està bloquejat"
"IS_BLOCKED": "està bloquejat",
"REF": "Ref"
},
"ROLES": {
"ALL": "Tot"
@ -231,14 +236,107 @@
"ADD_WIKI_LINKS": "Afegir link de wiki",
"DELETE_WIKI_LINKS": "Esborrar enllaços de wiki"
}
},
"META": {
"PAGE_TITLE": "Taiga",
"PAGE_DESCRIPTION": "Taiga is a project management platform for startups and agile developers & designers who want a simple, beautiful tool that makes work truly enjoyable."
}
},
"LOGIN": {
"PAGE_TITLE": "Login - Taiga",
"PAGE_DESCRIPTION": "Logging in to Taiga, a project management platform for startups and agile developers & designers who want a simple, beautiful tool that makes work truly enjoyable."
},
"AUTH": {
"INVITED_YOU": "T'ha convidat a participar en el projecte",
"NOT_REGISTERED_YET": "No t'has registrat encara?",
"REGISTER": "Registrat",
"CREATE_ACCOUNT": "Crea el teu conter gratis ací"
},
"LOGIN_COMMON": {
"HEADER": "Ja tinc un conter de Taiga",
"PLACEHOLDER_AUTH_NAME": "Nom d'usuari i correu electrònic (sensible a majúscules i minúscules)",
"LINK_FORGOT_PASSWORD": "L'has oblidat?",
"TITLE_LINK_FORGOT_PASSWORD": "Has oblidat la teua contrasenya?",
"ACTION_ENTER": "Entrar",
"ACTION_SIGN_IN": "Entrar",
"PLACEHOLDER_AUTH_PASSWORD": "Contrasenya (sensible a majúscules i minúscules)"
},
"LOGIN_FORM": {
"ERROR_AUTH_INCORRECT": "Segons els Oompa Loompas, el vostre nom d'usuari/correu electrònic o contrasenya són incorrectes.",
"SUCCESS": "Our Oompa Loompas están contents, benvinguts a Taiga."
},
"REGISTER": {
"PAGE_TITLE": "Register - Taiga",
"PAGE_DESCRIPTION": "Create your account in Taiga, a project management platform for startups and agile developers & designers who want a simple, beautiful tool that makes work truly enjoyable."
},
"REGISTER_FORM": {
"TITLE": "Register a new Taiga account (free)",
"PLACEHOLDER_NAME": "Trieu un nom d'usuari (sensible a majúscules i minúscules)",
"PLACEHOLDER_FULL_NAME": "Escriu el teu nom complet",
"PLACEHOLDER_EMAIL": "El teu correu",
"PLACEHOLDER_PASSWORD": "Tria una contrasenya(sensible a majúscules i minúscules)",
"ACTION_SIGN_UP": "Registrar-se",
"TITLE_LINK_LOGIN": "Entrar",
"LINK_LOGIN": "Ja estàs registrat? Entra"
},
"FORGOT_PASSWORD": {
"PAGE_TITLE": "Forgot password - Taiga",
"PAGE_DESCRIPTION": "Enter your username or email to get a new password and you can access to Taiga again."
},
"FORGOT_PASSWORD_FORM": {
"TITLE": "Oops, has oblidat la teua contrasenya?",
"SUBTITLE": "Escriviu el vostre nom d'usuari o correu electrònic per a conseguir-ne un de nou",
"PLACEHOLDER_FIELD": "Nom d'usuari o correu electrònic",
"ACTION_RESET_PASSWORD": "Resetejar contrasenya",
"LINK_CANCEL": "No, portam enrere, crec que ho recorde.",
"SUCCESS": "<strong>Mira el teu correu!</strong><br /> Hem enviat un correu amb les instrucciones per a setejar una nova contrasenya.",
"ERROR": "Segons els nostres Oompa Loompas, no estàs registrat encara."
},
"CHANGE_PASSWORD": {
"PAGE_TITLE": "Change you password - Taiga",
"PAGE_DESCRIPTION": "Set a new passoword for your Taiga account and hey!, you may want to eat some more iron-rich food, it's good for your brain :P",
"SECTION_NAME": "Canvi de contrasenya",
"FIELD_CURRENT_PASSWORD": "Contrasenya actual",
"PLACEHOLDER_CURRENT_PASSWORD": "La teua contrasenya actua (buit si no tens contrasenya encara)",
"FIELD_NEW_PASSWORD": "Nova contrasenya",
"PLACEHOLDER_NEW_PASSWORD": "Escriu una nova contrasenya",
"FIELD_RETYPE_PASSWORD": "Reescriu contrasenya",
"PLACEHOLDER_RETYPE_PASSWORD": "Reescriu contrasenya",
"ERROR_PASSWORD_MATCH": "Les contrasenyes no coincideixen"
},
"CHANGE_PASSWORD_RECOVERY_FORM": {
"TITLE": "Crea un nou password",
"SUBTITLE": "Menja un poc més de ferro, es bo pel cervell :P ",
"PLACEHOLDER_RECOVER_PASSWORD_TOKEN": "Recuperar token de contrasenya",
"LINK_NEED_TOKEN": "Neccesites un?",
"TITLE_LINK_NEED_TOKEN": "Necessites un token per a recuperar la teua contrasenya perque l'has oblidat?",
"PLACEHOLDER_NEW_PASSWORD": "nova contrasenya",
"PLACEHOLDER_RE_TYPE_NEW_PASSWORD": "Reescriu la nova contrasenya",
"ACTION_RESET_PASSWORD": "Resetejar contrasenya",
"SUCCESS": "Els Oompa Loompas han salvat la teua contrasenya <br /> Prova a <strong>entrar</strong> amb ella."
},
"INVITATION": {
"PAGE_TITLE": "Invitation acceptance - Taiga",
"PAGE_DESCRIPTION": "Accept the invitation to join a project in Taiga, a project management platform for startups and agile developers & designers who want a simple, beautiful tool that makes work truly enjoyable."
},
"INVITATION_LOGIN_FORM": {
"NOT_FOUND": "<strong>Ooops, ha hagut un problema</strong><br />Els nosters Oompa Loompas no troben la teua invitació.",
"SUCCESS": "T'has incorporat a este projecte. Vos donem la benvinguda a {{project_name}}",
"ERROR": "Segons els nostres OOmpa Loompas, no estàs registrat encara o has escrit una contrasenya invàlida."
},
"HOME": {
"PAGE_TITLE": "Home - Taiga",
"PAGE_DESCRIPTION": "The Taiga home page with your main projects and all your assigned and watched user stories, tasks and issues",
"EMPTY_WATCHING": "<strong>Follow</strong> the projects, User Stories, Tasks, Issues... that you want to know about :)",
"EMPTY_PROJECT_LIST": "You don't have any projects yet",
"WORKING_ON_SECTION": "Working on",
"WATCHING_SECTION": "Watching"
},
"PROJECTS": {
"PAGE_TITLE": "My projects - Taiga",
"PAGE_DESCRIPTION": "A list with all your projects, you can reorder or create a new one.",
"MY_PROJECTS": "My projects"
},
"ATTACHMENT": {
"SECTION_NAME": "Adjunts",
"TITLE": "{{ fileName }} pujat el {{ date }}",
@ -276,6 +374,7 @@
},
"MEMBERSHIPS": {
"TITLE": "Gestió de Membres",
"PAGE_TITLE": "Memberships - {{projectName}}",
"ADD_BUTTON": "+ Nou membre",
"ADD_BUTTON_TITLE": "Afegir nou membre"
},
@ -311,7 +410,7 @@
"SALT_CHAT_ROOM": "Pots afegir un code salt a la sala de xat"
},
"PROJECT_PROFILE": {
"PAGE_TITLE": "Perfil de projecte - {{sectionName}} - {{projectName}}",
"PAGE_TITLE": "{{sectionName}} - Project profile - {{projectName}}",
"PROJECT_DETAILS": "Detalls de projecte",
"PROJECT_NAME": "Nom del projecte",
"PROJECT_SLUG": "Slug de projecte",
@ -352,23 +451,26 @@
"ISSUE_ADD": "Afegix camps personalitzats en incidències"
},
"PROJECT_VALUES": {
"APP_TITLE": "Valors de projecte - {{sectionName}} - {{projectName}}",
"PAGE_TITLE": "{{sectionName}} - Project values - {{projectName}}",
"REPLACEMENT": "Tots els elements amb aquest valor seràn canviats a",
"ERROR_DELETE_ALL": "No pots esborrar tots els valors."
},
"PROJECT_VALUES_POINTS": {
"TITLE": "Punts d'US",
"TITLE": "Points",
"SUBTITLE": "Especifica els punts en els que poden ser estimades les històries d'usuari",
"US_TITLE": "US points",
"ACTION_ADD": "Afegir punts nous"
},
"PROJECT_VALUES_PRIORITIES": {
"TITLE": "Prioritats d'incidències",
"TITLE": "Priorities",
"SUBTITLE": "Especifica les prioritats que tindran les teues tasques",
"ISSUE_TITLE": "Issue priorities",
"ACTION_ADD": "Add new priority"
},
"PROJECT_VALUES_SEVERITIES": {
"TITLE": "Severitat d'incidències",
"TITLE": "Severities",
"SUBTITLE": "Especifica les severitats que tindran les teues incidències",
"ISSUE_TITLE": "Issue severities",
"ACTION_ADD": "Add new severity"
},
"PROJECT_VALUES_STATUS": {
@ -382,10 +484,10 @@
"TITLE": "Tipus",
"SUBTITLE": "Especifica quin tipus d'incidència podria ser.",
"ISSUE_TITLE": "Tipus d'incidències",
"ACTION_ADD": "Add new type"
"ACTION_ADD": "Afegir now {{objName}}"
},
"ROLES": {
"SECTION_NAME": "Rols - {{projectName}}",
"PAGE_TITLE": "Roles - {{projectName}}",
"WARNING_NO_ROLE": "Ves amb compte, cap rol en el teu projecte pot estimar punts per a les històries d'usuari",
"HELP_ROLE_ENABLED": "Si està activat, els membres assignats a aquest rol podràn estimar els punts d'històries d'usuaris",
"COUNT_MEMBERS": "{{ role.members_count }} membres amb aquest rol",
@ -402,20 +504,20 @@
},
"BITBUCKET": {
"SECTION_NAME": "Bitbucket",
"APP_TITLE": "Bitbucket - {{projectName}}",
"PAGE_TITLE": "Bitbucket - {{projectName}}",
"INFO_VERIFYING_IP": "Les peticions a Bitbuket no estan signades. El millor mode de verificar l'oritge es per IP. Si el camp està buit no hi haurà verificació per IP."
},
"GITLAB": {
"SECTION_NAME": "Gitlab",
"APP_TITLE": "Gitlab - {{projectName}}",
"PAGE_TITLE": "Gitlab - {{projectName}}",
"INFO_VERIFYING_IP": "Les peticions a Bitbuket no estan signades. El millor mode de verificar l'oritge es per IP. Si el camp està buit no hi haurà verificació per IP."
},
"GITHUB": {
"SECTION_NAME": "Github",
"APP_TITLE": "Github - {{projectName}}"
"PAGE_TITLE": "Github - {{projectName}}"
},
"WEBHOOKS": {
"APP_TITLE": "Webhooks - {{projectName}}",
"PAGE_TITLE": "Webhooks - {{projectName}}",
"SECTION_NAME": "Webhooks",
"SUBTITLE": "Els Webhooks notifiquen serveis extens de events en taiga com comentaris, històries d'usuari...",
"ADD_NEW": "Afegir un nou Webhook",
@ -441,6 +543,7 @@
"WEBHOOK_NAME": "Webhook '{{name}}'"
},
"CUSTOM_ATTRIBUTES": {
"PAGE_TITLE": "{{sectionName}} - Custom Attributes - {{projectName}}",
"ADD": "Afegix camp personalitzat",
"EDIT": "Edita el camp personalitzat",
"DELETE": "Esborrar camp personalitzat",
@ -518,9 +621,36 @@
"TITLE": "Serveis"
}
},
"USER": {
"PROFILE": {
"PAGE_TITLE": "{{userFullName}} (@{{userUsername}})",
"EDIT": "Edit profile",
"FOLLOW": "Follow",
"PROJECTS": "Projectes",
"CLOSED_US": "Closed US",
"CONTACTS": "Contacts",
"REPORT": "Report Abuse",
"ACTIVITY_TAB": "Activity Tab",
"PROJECTS_TAB": "Projects Tab",
"CONTACTS_TAB": "Contacts Tab",
"FAVORITES_TAB": "Favorites Tab",
"CONTACTS_EMPTY": "{{username}} doesn't have contacts yet",
"CURRENT_USER_CONTACTS_EMPTY": "You don't have contacts yet",
"CURRENT_USER_CONTACTS_EMPTY_EXPLAIN": "The people with whom you work at Taiga will be your contacts automatically",
"PROJECTS_EMPTY": "{{username}} doesn't' have projects yet"
},
"PROFILE_SIDEBAR": {
"TITLE": "Your profile",
"DESCRIPTION": "La gent pot vore tot el que fas i en qué estàs treballant. Afegix una bio interessant per a donar una millor versió de la teua informació.",
"ADD_INFO": "Edita la teua bio"
}
},
"PROJECT": {
"PAGE_TITLE": "{{projectName}}",
"WELCOME": "Benvinguts",
"SECTION_PROJECTS": "Projectes",
"HELP": "Reorder your projects to set in the top the most used ones.<br/> The top 10 projects will appear in the top navigation bar project list",
"PRIVATE": "Projecte privat",
"STATS": {
"PROJECT": "punts<br/> projecte",
"DEFINED": "punts<br/> definits",
@ -529,6 +659,7 @@
},
"SECTION": {
"SEARCH": "Cerca",
"TIMELINE": "Timeline",
"BACKLOG": "Backlog",
"KANBAN": "Kanban",
"ISSUES": "Incidències",
@ -541,9 +672,32 @@
"SECTION_TITLE": "Els teus projectes",
"PLACEHOLDER_SEARCH": "Cerca en...",
"ACTION_CREATE_PROJECT": "Crear projecte",
"TITLE_ACTION_IMPORT": "Importar projecte",
"ACTION_IMPORT_PROJECT": "Import project",
"SEE_MORE_PROJECTS": "See more projects",
"TITLE_CREATE_PROJECT": "Create project",
"TITLE_IMPORT_PROJECT": "Import project",
"TITLE_PRVIOUS_PROJECT": "Mostra projectes previs",
"TITLE_NEXT_PROJECT": "Mostrar próxims projectes"
"TITLE_NEXT_PROJECT": "Mostrar próxims projectes",
"HELP_TITLE": "Taiga Support Page",
"HELP": "Help",
"FEEDBACK_TITLE": "Enviar sugerències",
"FEEDBACK": "Sugerències",
"NOTIFICATIONS_TITLE": "Edit your notification settings",
"NOTIFICATIONS": "Notificacions",
"ORGANIZATIONS_TITLE": "Edit your organizations",
"ORGANIZATIONS": "Edit organizations",
"SETTINGS_TITLE": "Edit your settings",
"SETTINGS": "Settings",
"VIEW_PROFILE_TITLE": "View Profile",
"VIEW_PROFILE": "View Profile",
"EDIT_PROFILE_TITLE": "Edit your profile",
"EDIT_PROFILE": "Edit Profile",
"CHANGE_PASSWORD_TITLE": "Canvi de contrasenya",
"CHANGE_PASSWORD": "Canvi de contrasenya",
"DASHBOARD_TITLE": "Dashboard",
"DISCOVER_TITLE": "Discover trending projects",
"DISCOVER": "Discover",
"ACTION_REORDER": "Drag & drop to reorder"
},
"IMPORT": {
"TITLE": "Important Projecte",
@ -624,6 +778,8 @@
}
},
"US": {
"PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}",
"PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Completed {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). Points: {{userStoryPoints}}. Description: {{userStoryDescription}}",
"SECTION_NAME": "User story details",
"LINK_TASKBOARD": "Panell de tasques",
"TITLE_LINK_TASKBOARD": "Anar a panell de tasques",
@ -711,6 +867,8 @@
}
},
"BACKLOG": {
"PAGE_TITLE": "Backlog - {{projectName}}",
"PAGE_DESCRIPTION": "The backlog panel, with user stories and sprints of the project {{projectName}}: {{projectDescription}}",
"SECTION_NAME": "Backlog",
"MOVE_US_TO_CURRENT_SPRINT": "Envia al Sprint",
"SHOW_FILTERS": "Mostra filtres",
@ -790,6 +948,8 @@
"VERSION_ERROR": "Algú dins de Taiga ha canviat aȯ abans i els Oompa Loompas no pode aplicar els teus canvis. Per favor recarrega i aplica els teus canvis (es perdràn)"
},
"TASKBOARD": {
"PAGE_TITLE": "{{sprintName}} - Sprint taskboard - {{projectName}}",
"PAGE_DESCRIPTION": "Sprint {{sprintName}} (from {{startDate}} to {{endDate}}) of {{projectName}}. Completed {{completedPercentage}}% ({{completedPoints}} of {{totalPoints}} points). {{openTasks}} opened tasks of {{totalTasks}}.",
"SECTION_NAME": "Panell de tasques",
"TITLE_ACTION_ADD": "Afegir nova tasca",
"TITLE_ACTION_ADD_BULK": "Afegeix noves històries d'usuari en grup",
@ -813,6 +973,8 @@
}
},
"TASK": {
"PAGE_TITLE": "{{taskSubject}} - Task {{taskRef}} - {{projectName}}",
"PAGE_DESCRIPTION": "Status: {{taskStatus }}. Description: {{taskDescription}}",
"SECTION_NAME": "Detalls de la tasca",
"LINK_TASKBOARD": "Panell de tasques",
"TITLE_LINK_TASKBOARD": "Anar a panell de tasques",
@ -858,56 +1020,9 @@
"ACTION_CHANGE_EMAIL": "Canviar correu",
"SUCCESS": "Els Oompa Loompas han actualitzat el teu correu"
},
"CHANGE_PASSWORD_RECOVERY_FORM": {
"TITLE": "Crea un nou password",
"SUBTITLE": "Menja un poc més de ferro, es bo pel cervell :P ",
"PLACEHOLDER_RECOVER_PASSWORD_TOKEN": "Recuperar token de contrasenya",
"LINK_NEED_TOKEN": "Neccesites un?",
"TITLE_LINK_NEED_TOKEN": "Necessites un token per a recuperar la teua contrasenya perque l'has oblidat?",
"PLACEHOLDER_NEW_PASSWORD": "nova contrasenya",
"PLACEHOLDER_RE_TYPE_NEW_PASSWORD": "Reescriu la nova contrasenya",
"ACTION_RESET_PASSWORD": "Resetejar contrasenya",
"SUCCESS": "Els Oompa Loompas han salvat la teua contrasenya <br /> Prova a <strong>entrar</strong> amb ella."
},
"FORGOT_PASSWORD_FORM": {
"TITLE": "Oops, has oblidat la teua contrasenya?",
"SUBTITLE": "Escriviu el vostre nom d'usuari o correu electrònic per a conseguir-ne un de nou",
"PLACEHOLDER_FIELD": "Nom d'usuari o correu electrònic",
"ACTION_RESET_PASSWORD": "Resetejar contrasenya",
"LINK_CANCEL": "No, portam enrere, crec que ho recorde.",
"SUCCESS": "<strong>Mira el teu correu!</strong><br /> Hem enviat un correu amb les instrucciones per a setejar una nova contrasenya.",
"ERROR": "Segons els nostres Oompa Loompas, no estàs registrat encara."
},
"LOGIN_COMMON": {
"HEADER": "Ja tinc un conter de Taiga",
"PLACEHOLDER_AUTH_NAME": "Nom d'usuari i correu electrònic (sensible a majúscules i minúscules)",
"LINK_FORGOT_PASSWORD": "L'has oblidat?",
"TITLE_LINK_FORGOT_PASSWORD": "Has oblidat la teua contrasenya?",
"ACTION_ENTER": "Entrar",
"ACTION_SIGN_IN": "Entrar",
"PLACEHOLDER_AUTH_PASSWORD": "Contrasenya (sensible a majúscules i minúscules)"
},
"LOGIN_FORM": {
"ERROR_AUTH_INCORRECT": "Segons els Oompa Loompas, el vostre nom d'usuari/correu electrònic o contrasenya són incorrectes.",
"ERROR_GENERIC": "Segons els Oompa Loompas ha hagut un error.",
"SUCCESS": "Our Oompa Loompas están contents, benvinguts a Taiga."
},
"INVITATION_LOGIN_FORM": {
"NOT_FOUND": "<strong>Ooops, ha hagut un problema</strong><br />Els nosters Oompa Loompas no troben la teua invitació.",
"SUCCESS": "T'has incorporat a este projecte. Vos donem la benvinguda a {{project_name}}",
"ERROR": "Segons els nostres OOmpa Loompas, no estàs registrat encara o has escrit una contrasenya invàlida."
},
"REGISTER_FORM": {
"TITLE": "Register a new Taiga account (free)",
"PLACEHOLDER_NAME": "Trieu un nom d'usuari (sensible a majúscules i minúscules)",
"PLACEHOLDER_FULL_NAME": "Escriu el teu nom complet",
"PLACEHOLDER_EMAIL": "El teu correu",
"PLACEHOLDER_PASSWORD": "Tria una contrasenya(sensible a majúscules i minúscules)",
"ACTION_SIGN_UP": "Registrar-se",
"TITLE_LINK_LOGIN": "Entrar",
"LINK_LOGIN": "Ja estàs registrat? Entra"
},
"ISSUES": {
"PAGE_TITLE": "Issues - {{projectName}}",
"PAGE_DESCRIPTION": "The issues list panel of the project {{projectName}}: {{projectDescription}}",
"LIST_SECTION_NAME": "Incidències",
"SECTION_NAME": "Issue details",
"ACTION_NEW_ISSUE": "+ NOVA INCIDÈNCIA",
@ -921,6 +1036,11 @@
"TITLE_NEXT_ISSUE": "pròxima incidència",
"ACTION_DELETE": "Esborrar incidència",
"LIGHTBOX_TITLE_BLOKING_ISSUE": "Bloquejant incidència",
"FIELDS": {
"PRIORITY": "Prioritat",
"SEVERITY": "Severitat",
"TYPE": "Tipus"
},
"CONFIRM_PROMOTE": {
"TITLE": "Promociona aquesta incidència a història d'usuari",
"MESSAGE": "Segur que vols crear una nova US desde aquesta incidència"
@ -966,7 +1086,13 @@
}
}
},
"ISSUE": {
"PAGE_TITLE": "{{issueSubject}} - Issue {{issueRef}} - {{projectName}}",
"PAGE_DESCRIPTION": "Status: {{issueStatus }}. Type: {{issueType}}, Priority: {{issuePriority}}. Severity: {{issueSeverity}}. Description: {{issueDescription}}"
},
"KANBAN": {
"PAGE_TITLE": "Kanban - {{projectName}}",
"PAGE_DESCRIPTION": "The kanban panel, with user stories of the project {{projectName}}: {{projectDescription}}",
"SECTION_NAME": "Kanban",
"TITLE_ACTION_FOLD": "Plegar columna",
"TITLE_ACTION_UNFOLD": "Desplegar columna",
@ -981,6 +1107,8 @@
"UNDO_ARCHIVED": "Arrastra de nou per desfer"
},
"SEARCH": {
"PAGE_TITLE": "Search - {{projectName}}",
"PAGE_DESCRIPTION": "Search anything, user stories, issues, tasks or wiki pages, in the project {{projectName}}: {{projectDescription}}",
"FILTER_USER_STORIES": "Històries d'usuari",
"FILTER_ISSUES": "Incidències",
"FILTER_TASKS": "Tasca",
@ -991,6 +1119,8 @@
"EMPTY_DESCRIPTION": "Prova amb una de les pestanyes o busca de nou"
},
"TEAM": {
"PAGE_TITLE": "Team - {{projectName}}",
"PAGE_DESCRIPTION": "The team panel to show all the members of the project {{projectName}}: {{projectDescription}}",
"SECTION_NAME": "Equip",
"APP_TITLE": "EQUIP - {{projectName}}",
"PLACEHOLDER_INPUT_SEARCH": "Busca per nom complet...",
@ -1011,16 +1141,6 @@
"CONFIRM_LEAVE_PROJECT": "Segur que vols deixar el projecte?",
"ACTION_LEAVE_PROJECT": "Abandonar aquest projecte"
},
"CHANGE_PASSWORD": {
"SECTION_NAME": "Canvi de contrasenya",
"FIELD_CURRENT_PASSWORD": "Contrasenya actual",
"PLACEHOLDER_CURRENT_PASSWORD": "La teua contrasenya actua (buit si no tens contrasenya encara)",
"FIELD_NEW_PASSWORD": "Nova contrasenya",
"PLACEHOLDER_NEW_PASSWORD": "Escriu una nova contrasenya",
"FIELD_RETYPE_PASSWORD": "Reescriu contrasenya",
"PLACEHOLDER_RETYPE_PASSWORD": "Reescriu contrasenya",
"ERROR_PASSWORD_MATCH": "Les contrasenyes no coincideixen"
},
"USER_SETTINGS": {
"AVATAR_MAX_SIZE": "[Max. grandària: {{maxFileSize}}]",
"MENU": {
@ -1059,7 +1179,7 @@
"EMAIL": "Correu electrònic",
"FULL_NAME": "Nom complet",
"PLACEHOLDER_FULL_NAME": "Esciur el teu nom complet (ex. Íñigo Montoya)",
"BIO": "Bio",
"BIO": "Bio (max. 210 caràcters)",
"PLACEHOLDER_BIO": "Contans algo sobre tu mateix",
"LANGUAGE": "Idioma",
"LANGUAGE_DEFAULT": "-- utiliza l'idioma per defecte --"
@ -1074,6 +1194,8 @@
"PROGRESS_NAME_DESCRIPTION": "Nom i descripció"
},
"WIKI": {
"PAGE_TITLE": "{{wikiPageName}} - Wiki - {{projectName}}",
"PAGE_DESCRIPTION": "Last edition on {{lastModifiedDate}} ({{totalEditions}} editions in total) Content: {{ wikiPageContent }}",
"DATETIME": "DD MMM YYYY HH:mm",
"PLACEHOLDER_PAGE": "Esciu pàgina del Wiki",
"REMOVE": "Esborrar pàgina de Wiki",
@ -1087,5 +1209,43 @@
"LAST_EDIT": "última <br />edició",
"LAST_MODIFICATION": "última modificació"
}
},
"HINTS": {
"SECTION_NAME": "Hint",
"LINK": "If you want to know how to use it visit our support page",
"LINK_TITLE": "Visit our support page",
"HINT1_TITLE": "Did you know you can import and export projects?",
"HINT1_TEXT": "This allow you to extract all your data from one Taiga and move it to another one.",
"HINT2_TITLE": "Did you know you can create custom fields?",
"HINT2_TEXT": "Teams can now create custom fields as a flexible means to enter specific data useful for their particular workflow",
"HINT3_TITLE": "Reorder your projects to feature those most relevant to you",
"HINT3_TEXT": "The top 10 project will be in your top bar direct access",
"HINT4_TITLE": "Did you forgot what were you working on?",
"HINT4_TEXT": "Don't worry, on your dashboard you'll find your open tasks, issues, and user stories in the order you worked on them."
},
"TIMELINE": {
"UPLOAD_ATTACHMENT": "{{username}} has uploaded a new attachment in {{obj_name}}",
"US_CREATED": "{{username}} has created a new US {{obj_name}} in {{project_name}}",
"ISSUE_CREATED": "{{username}} has created a new issue {{obj_name}} in {{project_name}}",
"TASK_CREATED": "{{username}} has created a new task {{obj_name}} in {{project_name}}",
"TASK_CREATED_WITH_US": "{{username}} has created a new task {{obj_name}} in {{project_name}} which belongs to the US {{us_name}}",
"WIKI_CREATED": "{{username}} has created a new wiki page {{obj_name}} in {{project_name}}",
"MILESTONE_CREATED": "{{username}} has created a new sprint {{obj_name}} in {{project_name}}",
"NEW_PROJECT": "{{username}} created the project {{project_name}}",
"MILESTONE_UPDATED": "{{username}} has updated the sprint {{obj_name}}",
"US_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the US {{obj_name}}",
"ISSUE_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the issue {{obj_name}}",
"TASK_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}}",
"TASK_UPDATED_WITH_US": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}} which belongs to the US {{us_name}}",
"WIKI_UPDATED": "{{username}} has updated the wiki page {{obj_name}}",
"NEW_COMMENT_US": "{{username}} has commented in the US {{obj_name}}",
"NEW_COMMENT_ISSUE": "{{username}} has commented in the issue {{obj_name}}",
"NEW_COMMENT_TASK": "{{username}} has commented in the task {{obj_name}}",
"NEW_MEMBER": "{{project_name}} has a new member",
"US_ADDED_MILESTONE": "{{username}} has added the US {{obj_name}} to {{sprint_name}}",
"US_REMOVED_FROM_MILESTONE": "{{username}} has added the US {{obj_name}} to the backlog",
"BLOCKED": "{{username}} has blocked {{obj_name}}",
"UNBLOCKED": "{{username}} has unblocked {{obj_name}}",
"NEW_USER": "{{username}} has joined Taiga"
}
}

1251
app/locales/locale-de.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -100,6 +100,10 @@
"SAT": "Sat"
}
},
"SEE_USER_PROFILE": "See {{username }} profile",
"USER_STORY": "User story",
"TASK": "Task",
"ISSUE": "Issue",
"TAGS": {
"PLACEHOLDER": "I'm it! Tag me...",
"DELETE": "Delete tag",
@ -125,7 +129,8 @@
"ASSIGNED_TO": "Assigned to",
"POINTS": "Points",
"BLOCKED_NOTE": "blocked note",
"IS_BLOCKED": "is blocked"
"IS_BLOCKED": "is blocked",
"REF": "Ref"
},
"ROLES": {
"ALL": "All"
@ -231,14 +236,107 @@
"ADD_WIKI_LINKS": "Add wiki links",
"DELETE_WIKI_LINKS": "Delete wiki links"
}
},
"META": {
"PAGE_TITLE": "Taiga",
"PAGE_DESCRIPTION": "Taiga is a project management platform for startups and agile developers & designers who want a simple, beautiful tool that makes work truly enjoyable."
}
},
"LOGIN": {
"PAGE_TITLE": "Login - Taiga",
"PAGE_DESCRIPTION": "Logging in to Taiga, a project management platform for startups and agile developers & designers who want a simple, beautiful tool that makes work truly enjoyable."
},
"AUTH": {
"INVITED_YOU": "has invited you to join the project",
"NOT_REGISTERED_YET": "Not registered yet?",
"REGISTER": "Register",
"CREATE_ACCOUNT": "create your free account here"
},
"LOGIN_COMMON": {
"HEADER": "I already have a Taiga login",
"PLACEHOLDER_AUTH_NAME": "Username or email (case sensitive)",
"LINK_FORGOT_PASSWORD": "Forgot it?",
"TITLE_LINK_FORGOT_PASSWORD": "Did you forgot your password?",
"ACTION_ENTER": "Enter",
"ACTION_SIGN_IN": "Sign in",
"PLACEHOLDER_AUTH_PASSWORD": "Password (case sensitive)"
},
"LOGIN_FORM": {
"ERROR_AUTH_INCORRECT": "According to our Oompa Loompas, your username/email or password are incorrect.",
"SUCCESS": "Our Oompa Loompas are happy, welcome to Taiga."
},
"REGISTER": {
"PAGE_TITLE": "Register - Taiga",
"PAGE_DESCRIPTION": "Create your account in Taiga, a project management platform for startups and agile developers & designers who want a simple, beautiful tool that makes work truly enjoyable."
},
"REGISTER_FORM": {
"TITLE": "Register a new Taiga account (free)",
"PLACEHOLDER_NAME": "Pick a username (case sensitive)",
"PLACEHOLDER_FULL_NAME": "Pick your full name",
"PLACEHOLDER_EMAIL": "Your email",
"PLACEHOLDER_PASSWORD": "Set a password (case sensitive)",
"ACTION_SIGN_UP": "Sign up",
"TITLE_LINK_LOGIN": "Log in",
"LINK_LOGIN": "Are you already registered? Log in"
},
"FORGOT_PASSWORD": {
"PAGE_TITLE": "Forgot password - Taiga",
"PAGE_DESCRIPTION": "Enter your username or email to get a new password and you can access to Taiga again."
},
"FORGOT_PASSWORD_FORM": {
"TITLE": "Oops, did you forget your password?",
"SUBTITLE": "Enter your username or email to get a new one",
"PLACEHOLDER_FIELD": "Username or email",
"ACTION_RESET_PASSWORD": "Reset Password",
"LINK_CANCEL": "Nah, take me back. I think I remember it.",
"SUCCESS": "<strong>Check your inbox!</strong><br />We have sent you an email with the instructions to set a new password",
"ERROR": "According to our Oompa Loompas, your are not registered yet."
},
"CHANGE_PASSWORD": {
"PAGE_TITLE": "Change you password - Taiga",
"PAGE_DESCRIPTION": "Set a new passoword for your Taiga account and hey!, you may want to eat some more iron-rich food, it's good for your brain :P",
"SECTION_NAME": "Change password",
"FIELD_CURRENT_PASSWORD": "Current password",
"PLACEHOLDER_CURRENT_PASSWORD": "Your current password (or empty if you have no password yet)",
"FIELD_NEW_PASSWORD": "New password",
"PLACEHOLDER_NEW_PASSWORD": "Type a new password",
"FIELD_RETYPE_PASSWORD": "Retype new password",
"PLACEHOLDER_RETYPE_PASSWORD": "Retype the new password",
"ERROR_PASSWORD_MATCH": "The passwords doesn't match"
},
"CHANGE_PASSWORD_RECOVERY_FORM": {
"TITLE": "Create a new Taiga pass",
"SUBTITLE": "And hey, you may want to eat some more iron-rich food, it's good for your brain :P",
"PLACEHOLDER_RECOVER_PASSWORD_TOKEN": "Recover password token",
"LINK_NEED_TOKEN": "Need one?",
"TITLE_LINK_NEED_TOKEN": "Did you need a token to recover your password because you forgot it?",
"PLACEHOLDER_NEW_PASSWORD": "New password",
"PLACEHOLDER_RE_TYPE_NEW_PASSWORD": "Re-type new password",
"ACTION_RESET_PASSWORD": "Reset Password",
"SUCCESS": "Our Oompa Loompas saved your new password.<br /> Try to <strong>sign in</strong> with it."
},
"INVITATION": {
"PAGE_TITLE": "Invitation acceptance - Taiga",
"PAGE_DESCRIPTION": "Accept the invitation to join a project in Taiga, a project management platform for startups and agile developers & designers who want a simple, beautiful tool that makes work truly enjoyable."
},
"INVITATION_LOGIN_FORM": {
"NOT_FOUND": "<strong>Ooops, we have a problem</strong><br />Our Oompa Loompas can't find your invitation.",
"SUCCESS": "You've successfully joined this project, Welcome to {{project_name}}",
"ERROR": "According to our Oompa Loompas, your are not registered yet or typed an invalid password."
},
"HOME": {
"PAGE_TITLE": "Home - Taiga",
"PAGE_DESCRIPTION": "The Taiga home page with your main projects and all your assigned and watched user stories, tasks and issues",
"EMPTY_WATCHING": "<strong>Follow</strong> the projects, User Stories, Tasks, Issues... that you want to know about :)",
"EMPTY_PROJECT_LIST": "You don't have any projects yet",
"WORKING_ON_SECTION": "Working on",
"WATCHING_SECTION": "Watching"
},
"PROJECTS": {
"PAGE_TITLE": "My projects - Taiga",
"PAGE_DESCRIPTION": "A list with all your projects, you can reorder or create a new one.",
"MY_PROJECTS": "My projects"
},
"ATTACHMENT": {
"SECTION_NAME": "attachments",
"TITLE": "{{ fileName }} uploaded on {{ date }}",
@ -276,6 +374,7 @@
},
"MEMBERSHIPS": {
"TITLE": "Manage members",
"PAGE_TITLE": "Memberships - {{projectName}}",
"ADD_BUTTON": "+ New member",
"ADD_BUTTON_TITLE": "Add new member"
},
@ -311,7 +410,7 @@
"SALT_CHAT_ROOM": "If you want you can append a salt code to the name of the chat room"
},
"PROJECT_PROFILE": {
"PAGE_TITLE": "Project profile - {{sectionName}} - {{projectName}}",
"PAGE_TITLE": "{{sectionName}} - Project profile - {{projectName}}",
"PROJECT_DETAILS": "Project details",
"PROJECT_NAME": "Project name",
"PROJECT_SLUG": "Project slug",
@ -352,23 +451,26 @@
"ISSUE_ADD": "Add a custom field in issues"
},
"PROJECT_VALUES": {
"APP_TITLE": "Project values - {{sectionName}} - {{projectName}}",
"PAGE_TITLE": "{{sectionName}} - Project values - {{projectName}}",
"REPLACEMENT": "All items with this value will be changed to",
"ERROR_DELETE_ALL": "You can't delete all values."
},
"PROJECT_VALUES_POINTS": {
"TITLE": "Us points",
"TITLE": "Points",
"SUBTITLE": "Specify the points your user stories could be estimated to",
"US_TITLE": "US points",
"ACTION_ADD": "Add new point"
},
"PROJECT_VALUES_PRIORITIES": {
"TITLE": "Issue priorities",
"TITLE": "Priorities",
"SUBTITLE": "Specify the priorities your issues will have",
"ISSUE_TITLE": "Issue priorities",
"ACTION_ADD": "Add new priority"
},
"PROJECT_VALUES_SEVERITIES": {
"TITLE": "Issue severities",
"TITLE": "Severities",
"SUBTITLE": "Specify the severities your issues will have",
"ISSUE_TITLE": "Issue severities",
"ACTION_ADD": "Add new severity"
},
"PROJECT_VALUES_STATUS": {
@ -382,10 +484,10 @@
"TITLE": "Types",
"SUBTITLE": "Specify the types your issues could be",
"ISSUE_TITLE": "Issues types",
"ACTION_ADD": "Add new type"
"ACTION_ADD": "Add new {{objName}}"
},
"ROLES": {
"SECTION_NAME": "Roles - {{projectName}}",
"PAGE_TITLE": "Roles - {{projectName}}",
"WARNING_NO_ROLE": "Be careful, no role in your project will be able to estimate the point value for user stories",
"HELP_ROLE_ENABLED": "When enabled, members assigned to this role will be able to estimate the point value for user stories",
"COUNT_MEMBERS": "{{ role.members_count }} members with this role",
@ -402,20 +504,20 @@
},
"BITBUCKET": {
"SECTION_NAME": "Bitbucket",
"APP_TITLE": "Bitbucket - {{projectName}}",
"PAGE_TITLE": "Bitbucket - {{projectName}}",
"INFO_VERIFYING_IP": "Bitbucket requests are not signed so the best way of verifying the origin is by IP. If the field is empty there will be no IP validation."
},
"GITLAB": {
"SECTION_NAME": "Gitlab",
"APP_TITLE": "Gitlab - {{projectName}}",
"PAGE_TITLE": "Gitlab - {{projectName}}",
"INFO_VERIFYING_IP": "Gitlab requests are not signed so the best way of verifying the origin is by IP. If the field is empty there will be no IP validation."
},
"GITHUB": {
"SECTION_NAME": "Github",
"APP_TITLE": "Github - {{projectName}}"
"PAGE_TITLE": "Github - {{projectName}}"
},
"WEBHOOKS": {
"APP_TITLE": "Webhooks - {{projectName}}",
"PAGE_TITLE": "Webhooks - {{projectName}}",
"SECTION_NAME": "Webhooks",
"SUBTITLE": "Webhooks notify external services about events in Taiga, like comments, user stories....",
"ADD_NEW": "Add a New Webhook",
@ -441,6 +543,7 @@
"WEBHOOK_NAME": "Webhook '{{name}}'"
},
"CUSTOM_ATTRIBUTES": {
"PAGE_TITLE": "{{sectionName}} - Custom Attributes - {{projectName}}",
"ADD": "Add custom field",
"EDIT": "Edit Custom Field",
"DELETE": "Delete Custom Field",
@ -518,9 +621,36 @@
"TITLE": "Services"
}
},
"USER": {
"PROFILE": {
"PAGE_TITLE": "{{userFullName}} (@{{userUsername}})",
"EDIT": "Edit profile",
"FOLLOW": "Follow",
"PROJECTS": "Projects",
"CLOSED_US": "Closed US",
"CONTACTS": "Contacts",
"REPORT": "Report Abuse",
"ACTIVITY_TAB": "Activity Tab",
"PROJECTS_TAB": "Projects Tab",
"CONTACTS_TAB": "Contacts Tab",
"FAVORITES_TAB": "Favorites Tab",
"CONTACTS_EMPTY": "{{username}} doesn't have contacts yet",
"CURRENT_USER_CONTACTS_EMPTY": "You don't have contacts yet",
"CURRENT_USER_CONTACTS_EMPTY_EXPLAIN": "The people with whom you work at Taiga will be your contacts automatically",
"PROJECTS_EMPTY": "{{username}} doesn't' have projects yet"
},
"PROFILE_SIDEBAR": {
"TITLE": "Your profile",
"DESCRIPTION": "People can see everything you do and what are you working on. Add a nice bio to give an enhanced version of your information.",
"ADD_INFO": "Edit your bio"
}
},
"PROJECT": {
"PAGE_TITLE": "{{projectName}}",
"WELCOME": "Welcome",
"SECTION_PROJECTS": "Projects",
"HELP": "Reorder your projects to set in the top the most used ones.<br/> The top 10 projects will appear in the top navigation bar project list",
"PRIVATE": "Private project",
"STATS": {
"PROJECT": "project<br/> points",
"DEFINED": "defined<br/> points",
@ -529,6 +659,7 @@
},
"SECTION": {
"SEARCH": "Search",
"TIMELINE": "Timeline",
"BACKLOG": "Backlog",
"KANBAN": "Kanban",
"ISSUES": "Issues",
@ -541,9 +672,32 @@
"SECTION_TITLE": "Your projects",
"PLACEHOLDER_SEARCH": "Search in...",
"ACTION_CREATE_PROJECT": "Create project",
"TITLE_ACTION_IMPORT": "Import project",
"ACTION_IMPORT_PROJECT": "Import project",
"SEE_MORE_PROJECTS": "See more projects",
"TITLE_CREATE_PROJECT": "Create project",
"TITLE_IMPORT_PROJECT": "Import project",
"TITLE_PRVIOUS_PROJECT": "Show previous projects",
"TITLE_NEXT_PROJECT": "Show next projects"
"TITLE_NEXT_PROJECT": "Show next projects",
"HELP_TITLE": "Taiga Support Page",
"HELP": "Help",
"FEEDBACK_TITLE": "Send feedback",
"FEEDBACK": "Feedback",
"NOTIFICATIONS_TITLE": "Edit your notification settings",
"NOTIFICATIONS": "Notifications",
"ORGANIZATIONS_TITLE": "Edit your organizations",
"ORGANIZATIONS": "Edit organizations",
"SETTINGS_TITLE": "Edit your settings",
"SETTINGS": "Settings",
"VIEW_PROFILE_TITLE": "View Profile",
"VIEW_PROFILE": "View Profile",
"EDIT_PROFILE_TITLE": "Edit your profile",
"EDIT_PROFILE": "Edit Profile",
"CHANGE_PASSWORD_TITLE": "Change password",
"CHANGE_PASSWORD": "Change password",
"DASHBOARD_TITLE": "Dashboard",
"DISCOVER_TITLE": "Discover trending projects",
"DISCOVER": "Discover",
"ACTION_REORDER": "Drag & drop to reorder"
},
"IMPORT": {
"TITLE": "Importing Project",
@ -625,6 +779,9 @@
}
},
"US": {
"PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}",
"PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Completed {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). Points: {{userStoryPoints}}. Description: {{userStoryDescription}}",
"SECTION_NAME": "User story details",
"LINK_TASKBOARD": "Taskboard",
"TITLE_LINK_TASKBOARD": "Go to the taskboard",
@ -712,6 +869,8 @@
}
},
"BACKLOG": {
"PAGE_TITLE": "Backlog - {{projectName}}",
"PAGE_DESCRIPTION": "The backlog panel, with user stories and sprints of the project {{projectName}}: {{projectDescription}}",
"SECTION_NAME": "Backlog",
"MOVE_US_TO_CURRENT_SPRINT": "Move to Current Sprint",
"SHOW_FILTERS": "Show filters",
@ -792,6 +951,8 @@
"VERSION_ERROR": "Someone inside Taiga has changed this before and our Oompa Loompas cannot apply your changes. Please reload and apply your changes again (they will be lost)."
},
"TASKBOARD": {
"PAGE_TITLE": "{{sprintName}} - Sprint taskboard - {{projectName}}",
"PAGE_DESCRIPTION": "Sprint {{sprintName}} (from {{startDate}} to {{endDate}}) of {{projectName}}. Completed {{completedPercentage}}% ({{completedPoints}} of {{totalPoints}} points). {{openTasks}} opened tasks of {{totalTasks}}." ,
"SECTION_NAME": "Taskboard",
"TITLE_ACTION_ADD": "Add a new Task",
"TITLE_ACTION_ADD_BULK": "Add some new Tasks in bulk",
@ -815,6 +976,8 @@
}
},
"TASK": {
"PAGE_TITLE": "{{taskSubject}} - Task {{taskRef}} - {{projectName}}",
"PAGE_DESCRIPTION": "Status: {{taskStatus }}. Description: {{taskDescription}}",
"SECTION_NAME": "Task details",
"LINK_TASKBOARD": "Taskboard",
"TITLE_LINK_TASKBOARD": "Go to the taskboard",
@ -860,56 +1023,9 @@
"ACTION_CHANGE_EMAIL": "Change email",
"SUCCESS": "Our Oompa Loompas updated your email"
},
"CHANGE_PASSWORD_RECOVERY_FORM": {
"TITLE": "Create a new Taiga pass",
"SUBTITLE": "And hey, you may want to eat some more iron-rich food, it's good for your brain :P",
"PLACEHOLDER_RECOVER_PASSWORD_TOKEN": "Recover password token",
"LINK_NEED_TOKEN": "Need one?",
"TITLE_LINK_NEED_TOKEN": "Did you need a token to recover your password because you forgot it?",
"PLACEHOLDER_NEW_PASSWORD": "New password",
"PLACEHOLDER_RE_TYPE_NEW_PASSWORD": "Re-type new password",
"ACTION_RESET_PASSWORD": "Reset Password",
"SUCCESS": "Our Oompa Loompas saved your new password.<br /> Try to <strong>sign in</strong> with it."
},
"FORGOT_PASSWORD_FORM": {
"TITLE": "Oops, did you forget your password?",
"SUBTITLE": "Enter your username or email to get a new one",
"PLACEHOLDER_FIELD": "Username or email",
"ACTION_RESET_PASSWORD": "Reset Password",
"LINK_CANCEL": "Nah, take me back. I think I remember it.",
"SUCCESS": "<strong>Check your inbox!</strong><br />We have sent you an email with the instructions to set a new password",
"ERROR": "According to our Oompa Loompas, your are not registered yet."
},
"LOGIN_COMMON": {
"HEADER": "I already have a Taiga login",
"PLACEHOLDER_AUTH_NAME": "Username or email (case sensitive)",
"LINK_FORGOT_PASSWORD": "Forgot it?",
"TITLE_LINK_FORGOT_PASSWORD": "Did you forgot your password?",
"ACTION_ENTER": "Enter",
"ACTION_SIGN_IN": "Sign in",
"PLACEHOLDER_AUTH_PASSWORD": "Password (case sensitive)"
},
"LOGIN_FORM": {
"ERROR_AUTH_INCORRECT": "According to our Oompa Loompas, your username/email or password are incorrect.",
"ERROR_GENERIC": "According to our Oompa Loompas there was an error.",
"SUCCESS": "Our Oompa Loompas are happy, welcome to Taiga."
},
"INVITATION_LOGIN_FORM": {
"NOT_FOUND": "<strong>Ooops, we have a problem</strong><br />Our Oompa Loompas can't find your invitation.",
"SUCCESS": "You've successfully joined this project, Welcome to {{project_name}}",
"ERROR": "According to our Oompa Loompas, your are not registered yet or typed an invalid password."
},
"REGISTER_FORM": {
"TITLE": "Register a new Taiga account (free)",
"PLACEHOLDER_NAME": "Pick a username (case sensitive)",
"PLACEHOLDER_FULL_NAME": "Pick your full name",
"PLACEHOLDER_EMAIL": "Your email",
"PLACEHOLDER_PASSWORD": "Set a password (case sensitive)",
"ACTION_SIGN_UP": "Sign up",
"TITLE_LINK_LOGIN": "Log in",
"LINK_LOGIN": "Are you already registered? Log in"
},
"ISSUES": {
"PAGE_TITLE": "Issues - {{projectName}}",
"PAGE_DESCRIPTION": "The issues list panel of the project {{projectName}}: {{projectDescription}}",
"LIST_SECTION_NAME": "Issues",
"SECTION_NAME": "Issue details",
"ACTION_NEW_ISSUE": "+ NEW ISSUE",
@ -923,6 +1039,11 @@
"TITLE_NEXT_ISSUE": "next issue",
"ACTION_DELETE": "Delete issue",
"LIGHTBOX_TITLE_BLOKING_ISSUE": "Blocking issue",
"FIELDS": {
"PRIORITY": "Priority",
"SEVERITY": "Severity",
"TYPE": "Type"
},
"CONFIRM_PROMOTE": {
"TITLE": "Promote this issue to a new user story",
"MESSAGE": "Are you sure you want to create a new US from this Issue?"
@ -968,7 +1089,13 @@
}
}
},
"ISSUE": {
"PAGE_TITLE": "{{issueSubject}} - Issue {{issueRef}} - {{projectName}}",
"PAGE_DESCRIPTION": "Status: {{issueStatus }}. Type: {{issueType}}, Priority: {{issuePriority}}. Severity: {{issueSeverity}}. Description: {{issueDescription}}"
},
"KANBAN": {
"PAGE_TITLE": "Kanban - {{projectName}}",
"PAGE_DESCRIPTION": "The kanban panel, with user stories of the project {{projectName}}: {{projectDescription}}",
"SECTION_NAME": "Kanban",
"TITLE_ACTION_FOLD": "Fold column",
"TITLE_ACTION_UNFOLD": "Unfold column",
@ -983,6 +1110,8 @@
"UNDO_ARCHIVED": "Drag & drop again to undo"
},
"SEARCH": {
"PAGE_TITLE": "Search - {{projectName}}",
"PAGE_DESCRIPTION": "Search anything, user stories, issues, tasks or wiki pages, in the project {{projectName}}: {{projectDescription}}",
"FILTER_USER_STORIES": "User Stories",
"FILTER_ISSUES": "Issues",
"FILTER_TASKS": "Tasks",
@ -993,6 +1122,8 @@
"EMPTY_DESCRIPTION": "Maybe try one of the tabs above or search again"
},
"TEAM": {
"PAGE_TITLE": "Team - {{projectName}}",
"PAGE_DESCRIPTION": "The team panel to show all the members of the project {{projectName}}: {{projectDescription}}",
"SECTION_NAME": "Team",
"APP_TITLE": "TEAM - {{projectName}}",
"PLACEHOLDER_INPUT_SEARCH": "Search by full name...",
@ -1013,16 +1144,6 @@
"CONFIRM_LEAVE_PROJECT": "Are you sure you want to leave the project?",
"ACTION_LEAVE_PROJECT": "Leave this project"
},
"CHANGE_PASSWORD": {
"SECTION_NAME": "Change password",
"FIELD_CURRENT_PASSWORD": "Current password",
"PLACEHOLDER_CURRENT_PASSWORD": "Your current password (or empty if you have no password yet)",
"FIELD_NEW_PASSWORD": "New password",
"PLACEHOLDER_NEW_PASSWORD": "Type a new password",
"FIELD_RETYPE_PASSWORD": "Retype new password",
"PLACEHOLDER_RETYPE_PASSWORD": "Retype the new password",
"ERROR_PASSWORD_MATCH": "The passwords doesn't match"
},
"USER_SETTINGS": {
"AVATAR_MAX_SIZE": "[Max. size: {{maxFileSize}}]",
"MENU": {
@ -1061,7 +1182,7 @@
"EMAIL": "Email",
"FULL_NAME": "Full name",
"PLACEHOLDER_FULL_NAME": "Set your full name (ex. Íñigo Montoya)",
"BIO": "Bio",
"BIO": "Bio (max. 210 chars)",
"PLACEHOLDER_BIO": "Tell us something about you",
"LANGUAGE": "Language",
"LANGUAGE_DEFAULT": "-- use default language --"
@ -1076,6 +1197,8 @@
"PROGRESS_NAME_DESCRIPTION": "Name and description"
},
"WIKI": {
"PAGE_TITLE": "{{wikiPageName}} - Wiki - {{projectName}}",
"PAGE_DESCRIPTION": "Last edition on {{lastModifiedDate}} ({{totalEditions}} editions in total) Content: {{ wikiPageContent }}",
"DATETIME": "DD MMM YYYY HH:mm",
"PLACEHOLDER_PAGE": "Write your wiki page",
"REMOVE": "Remove this wiki page",
@ -1089,5 +1212,43 @@
"LAST_EDIT": "last <br />edit",
"LAST_MODIFICATION": "last modification"
}
},
"HINTS": {
"SECTION_NAME": "Hint",
"LINK": "If you want to know how to use it visit our support page",
"LINK_TITLE": "Visit our support page",
"HINT1_TITLE": "Did you know you can import and export projects?",
"HINT1_TEXT": "This allow you to extract all your data from one Taiga and move it to another one.",
"HINT2_TITLE": "Did you know you can create custom fields?",
"HINT2_TEXT": "Teams can now create custom fields as a flexible means to enter specific data useful for their particular workflow",
"HINT3_TITLE": "Reorder your projects to feature those most relevant to you",
"HINT3_TEXT": "The top 10 project will be in your top bar direct access",
"HINT4_TITLE": "Did you forgot what were you working on?",
"HINT4_TEXT": "Don't worry, on your dashboard you'll find your open tasks, issues, and user stories in the order you worked on them."
},
"TIMELINE": {
"UPLOAD_ATTACHMENT": "{{username}} has uploaded a new attachment in {{obj_name}}",
"US_CREATED": "{{username}} has created a new US {{obj_name}} in {{project_name}}",
"ISSUE_CREATED": "{{username}} has created a new issue {{obj_name}} in {{project_name}}",
"TASK_CREATED": "{{username}} has created a new task {{obj_name}} in {{project_name}}",
"TASK_CREATED_WITH_US": "{{username}} has created a new task {{obj_name}} in {{project_name}} which belongs to the US {{us_name}}",
"WIKI_CREATED": "{{username}} has created a new wiki page {{obj_name}} in {{project_name}}",
"MILESTONE_CREATED": "{{username}} has created a new sprint {{obj_name}} in {{project_name}}",
"NEW_PROJECT": "{{username}} created the project {{project_name}}",
"MILESTONE_UPDATED": "{{username}} has updated the sprint {{obj_name}}",
"US_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the US {{obj_name}}",
"ISSUE_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the issue {{obj_name}}",
"TASK_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}}",
"TASK_UPDATED_WITH_US": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}} which belongs to the US {{us_name}}",
"WIKI_UPDATED": "{{username}} has updated the wiki page {{obj_name}}",
"NEW_COMMENT_US": "{{username}} has commented in the US {{obj_name}}",
"NEW_COMMENT_ISSUE": "{{username}} has commented in the issue {{obj_name}}",
"NEW_COMMENT_TASK": "{{username}} has commented in the task {{obj_name}}",
"NEW_MEMBER": "{{project_name}} has a new member",
"US_ADDED_MILESTONE": "{{username}} has added the US {{obj_name}} to {{sprint_name}}",
"US_REMOVED_FROM_MILESTONE": "{{username}} has added the US {{obj_name}} to the backlog",
"BLOCKED": "{{username}} has blocked {{obj_name}}",
"UNBLOCKED": "{{username}} has unblocked {{obj_name}}",
"NEW_USER": "{{username}} has joined Taiga"
}
}

View File

@ -3,7 +3,7 @@
"YES": "Si",
"NO": "No",
"LOADING": "Cargando...",
"LOADING_PROJECT": "Cargando projecto...",
"LOADING_PROJECT": "Cargando proyecto...",
"DATE": "DD MMM YYYY",
"DATETIME": "DD MMM YYYY HH:mm",
"SAVE": "Guardar",
@ -100,6 +100,10 @@
"SAT": "Sáb"
}
},
"SEE_USER_PROFILE": "Ver el perfil de {{username }}",
"USER_STORY": "Historia de usuario",
"TASK": "Tarea",
"ISSUE": "Petición",
"TAGS": {
"PLACEHOLDER": "¿Qué soy? Etiquétame...",
"DELETE": "Borrar etiqueta",
@ -125,7 +129,8 @@
"ASSIGNED_TO": "Asignado a",
"POINTS": "Puntos",
"BLOCKED_NOTE": "Motivo del bloqueo",
"IS_BLOCKED": "está bloqueada"
"IS_BLOCKED": "está bloqueada",
"REF": "Ref"
},
"ROLES": {
"ALL": "Todos"
@ -231,14 +236,107 @@
"ADD_WIKI_LINKS": "Crear enlaces",
"DELETE_WIKI_LINKS": "Borrar enlaces"
}
},
"META": {
"PAGE_TITLE": "Taiga",
"PAGE_DESCRIPTION": "Taiga es una plataforma de gestión de proyectos orientada a startups y equipos ágiles que buscan una herramienta sencilla, elegante y que les haga disfrutar trabajando."
}
},
"LOGIN": {
"PAGE_TITLE": "Login - Taiga",
"PAGE_DESCRIPTION": "Inicia sesión en Taiga, una plataforma de gestión de proyectos orientada a startups y equipos ágiles que buscan una herramienta sencilla, elegante y que les haga disfrutar trabajando."
},
"AUTH": {
"INVITED_YOU": "te ha invitado a unirte al proyecto",
"NOT_REGISTERED_YET": "¿Aún no está registrado?",
"REGISTER": "Registro",
"CREATE_ACCOUNT": "crea tu cuenta gratis aquí"
},
"LOGIN_COMMON": {
"HEADER": "Ya tengo una cuenta en Taiga",
"PLACEHOLDER_AUTH_NAME": "Nombre de usuario o email (distingue mayúsculas y minúsculas)",
"LINK_FORGOT_PASSWORD": "¿La olvidaste?",
"TITLE_LINK_FORGOT_PASSWORD": "¿Has olvidado tu contraseña?",
"ACTION_ENTER": "Entrar",
"ACTION_SIGN_IN": "Ingresar",
"PLACEHOLDER_AUTH_PASSWORD": "Contraseña (distingue mayúsculas y minúsculas)"
},
"LOGIN_FORM": {
"ERROR_AUTH_INCORRECT": "Nuestros Oompa Loompas indican que tu nombre de usuario/correo o contraseña son incorrectos",
"SUCCESS": "Nuestros Oompa Loompas están felices, bienvenido a Taiga."
},
"REGISTER": {
"PAGE_TITLE": "Registro - Taiga",
"PAGE_DESCRIPTION": "Crea tu cuenta en Taiga, una plataforma de gestión de proyectos orientada a startups y equipos ágiles que buscan una herramienta sencilla, elegante y que les haga disfrutar trabajando."
},
"REGISTER_FORM": {
"TITLE": "Crea tu nueva cuenta en Taiga, ¡es gratis!",
"PLACEHOLDER_NAME": "Escribe un nombre de usuario (distingue mayúsculas y minúsculas)",
"PLACEHOLDER_FULL_NAME": "Escribe tu nombre completo",
"PLACEHOLDER_EMAIL": "Tu email",
"PLACEHOLDER_PASSWORD": "Establece una contraseña (distingue mayúsculas y minúsculas)",
"ACTION_SIGN_UP": "Registrarme",
"TITLE_LINK_LOGIN": "Iniciar sesión",
"LINK_LOGIN": "¿Ya te has registrado? Inicia sesión"
},
"FORGOT_PASSWORD": {
"PAGE_TITLE": "Has olvidado tu contraseña - Taiga",
"PAGE_DESCRIPTION": "A partir de tu nombre de usuario o email obtendrás una nueva contraseña para acceder a Taiga nuevamente."
},
"FORGOT_PASSWORD_FORM": {
"TITLE": "Vaya, ¿has olvidado tu contraseña?",
"SUBTITLE": "Escribe tu nombre de usuario o email para obtener una nueva",
"PLACEHOLDER_FIELD": "Nombre de usuario o email",
"ACTION_RESET_PASSWORD": "Restablecer Contraseña",
"LINK_CANCEL": "Nah, llévame de vuelta, creo que lo recordé.",
"SUCCESS": "<strong>¡Revisa tu bandeja de entrada!</strong><br />Te hemos enviado un mail con las instrucciones necesarias para restablecer tu contraseña\n",
"ERROR": "Según nuestros Oompa Loompas tú no estás registrado"
},
"CHANGE_PASSWORD": {
"PAGE_TITLE": "Cambia tu contraseña - Taiga",
"PAGE_DESCRIPTION": "Indica una nueva contraseña para tu cuenta en Taiga y bueno, es posible que necesites comer un poco más de alimentos ricos en hierro, son buenos para tu cerebro :P",
"SECTION_NAME": "Cambiar contraseña",
"FIELD_CURRENT_PASSWORD": "Contraseña actual",
"PLACEHOLDER_CURRENT_PASSWORD": "Tu contraseña actual (o déjalo vacío si todavía no tienes contraseña)",
"FIELD_NEW_PASSWORD": "Nueva contraseña",
"PLACEHOLDER_NEW_PASSWORD": "Escribe una contraseña nueva",
"FIELD_RETYPE_PASSWORD": "Reescribe la nueva contraseña",
"PLACEHOLDER_RETYPE_PASSWORD": "Reescribe la nueva contraseña",
"ERROR_PASSWORD_MATCH": "Las contraseñas no coinciden"
},
"CHANGE_PASSWORD_RECOVERY_FORM": {
"TITLE": "Crear una nueva contraseña de Taiga",
"SUBTITLE": "Y bueno, es posible que necesites comer más alimentos ricos en hierro, son buenos para tu cerebro :P",
"PLACEHOLDER_RECOVER_PASSWORD_TOKEN": "token de recuperación de contraseña",
"LINK_NEED_TOKEN": "¿Necesitas una?",
"TITLE_LINK_NEED_TOKEN": "¿Necesitas un token para recuperar tu contraseña porque la has olvidado?",
"PLACEHOLDER_NEW_PASSWORD": "Nueva contraseña",
"PLACEHOLDER_RE_TYPE_NEW_PASSWORD": "Reescriba la nueva contraseña",
"ACTION_RESET_PASSWORD": "Restablecer contraseña",
"SUCCESS": "Nuestro Oompa Loopas guardaron tu nueva contraseña.<br /> Intenta <strong>registrarte</strong> con ella"
},
"INVITATION": {
"PAGE_TITLE": "Acepta la invitación - Taiga",
"PAGE_DESCRIPTION": "Acepta la invitación para unirte a un proyecto en Taiga, una plataforma de gestión de proyectos orientada a startups y equipos ágiles que buscan una herramienta sencilla, elegante y que les haga disfrutar trabajando."
},
"INVITATION_LOGIN_FORM": {
"NOT_FOUND": "<strong>Ooops, tenemos un problema</strong><br /> Nuestros Oompa Loompas no pueden encontrar tu invitación.",
"SUCCESS": "¡Acabas de unirte al proyecto! Bienvenido a {{project_name}}",
"ERROR": "Según nuestros Oompa Loompas tú no estás registrado o has escrito mal tu contraseña."
},
"HOME": {
"PAGE_TITLE": "Inicio - Taiga",
"PAGE_DESCRIPTION": "Página de inicio de Taiga, con tus proyectos principales y tus historias de usuario, tareas y peticiones en progreso asignadas y las que observas.",
"EMPTY_WATCHING": "<strong>Sigue</strong> aquellos proyectos, historias de usuario, tareas, peticiones... sobre los que quieras estar al tanto :)",
"EMPTY_PROJECT_LIST": "Todavía no tienes ningún proyecto",
"WORKING_ON_SECTION": "Trabajando en",
"WATCHING_SECTION": "Observando"
},
"PROJECTS": {
"PAGE_TITLE": "Mis proyectos - Taiga",
"PAGE_DESCRIPTION": "Una lista con todos tus proyectos, puedes reordenarla o crear un proyecto nuevo.",
"MY_PROJECTS": "Mis proyectos"
},
"ATTACHMENT": {
"SECTION_NAME": "adjuntos",
"TITLE": "{{ fileName }} subido el {{ date }}",
@ -276,6 +374,7 @@
},
"MEMBERSHIPS": {
"TITLE": "Administrar miembros",
"PAGE_TITLE": "Miembros - {{projectName}}",
"ADD_BUTTON": "+ Nuevo miembro",
"ADD_BUTTON_TITLE": "Añadir un nuevo miembro"
},
@ -311,7 +410,7 @@
"SALT_CHAT_ROOM": "Puedes añadirle un código salt al nombre del chat room"
},
"PROJECT_PROFILE": {
"PAGE_TITLE": "Perfil del proyecto - {{sectionName}} - {{projectName}}",
"PAGE_TITLE": "{{sectionName}} - Perfil de Proyecto - {{projectName}}\n",
"PROJECT_DETAILS": "Info del proyecto",
"PROJECT_NAME": "Nombre del proyecto",
"PROJECT_SLUG": "Slug de proyecto",
@ -352,23 +451,26 @@
"ISSUE_ADD": "Añadir un atributo personalizado en las peticiones"
},
"PROJECT_VALUES": {
"APP_TITLE": "Valores del proyecto - {{sectionName}} - {{projectName}}",
"PAGE_TITLE": "{{sectionName}} - Valores del Proyectos - {{projectName}}",
"REPLACEMENT": "Todos los elementos con este valor cambiarán a",
"ERROR_DELETE_ALL": "No puedes eliminar todos los valores."
},
"PROJECT_VALUES_POINTS": {
"TITLE": "Puntos de historia",
"TITLE": "Puntos",
"SUBTITLE": "Especifica los puntos a los que se podrían estimar tus historias de usuario",
"US_TITLE": "Puntos de historia de usuario",
"ACTION_ADD": "Añadir nuevo punto"
},
"PROJECT_VALUES_PRIORITIES": {
"TITLE": "Prioridades de las peticiones",
"TITLE": "Prioridades",
"SUBTITLE": "Especifica las prioridades que podrán tener tus peticiones",
"ISSUE_TITLE": "Prioridades de peticion",
"ACTION_ADD": "Añadir nueva prioridad"
},
"PROJECT_VALUES_SEVERITIES": {
"TITLE": "Gravedad de las peticiones",
"TITLE": "Gravedades",
"SUBTITLE": "Especifica la gravedad que tendrán tus peticiones",
"ISSUE_TITLE": "Gravedades de petición",
"ACTION_ADD": "Añadir nueva gravedad"
},
"PROJECT_VALUES_STATUS": {
@ -382,10 +484,10 @@
"TITLE": "Tipos",
"SUBTITLE": "Especifica los deferentes tipos posibles de peticiones que pueden existir.",
"ISSUE_TITLE": "Tipos de la petición",
"ACTION_ADD": "Añadir nuevo tipo"
"ACTION_ADD": "Añadir nuevo {{objName}}"
},
"ROLES": {
"SECTION_NAME": "Roles - {{projectName}}",
"PAGE_TITLE": "Roles - {{projectName}}",
"WARNING_NO_ROLE": "Ojo, ningún rol en tu proyecto podrá estimar historias de usuario",
"HELP_ROLE_ENABLED": "Si lo activas, los miembros que posean este rol serán capaces de estimar las histórias de usuario",
"COUNT_MEMBERS": "{{ role.members_count }} miembros con este rol",
@ -402,20 +504,20 @@
},
"BITBUCKET": {
"SECTION_NAME": "Bitbucket",
"APP_TITLE": "Bitbucket - {{projectName}}",
"PAGE_TITLE": "Bitbucket - {{projectName}}",
"INFO_VERIFYING_IP": "Las peticiones de Bitbucket no van firmadas, con la IP de origen verificamos su procedencia. Déjalo vacío y no se verificarán."
},
"GITLAB": {
"SECTION_NAME": "Gitlab",
"APP_TITLE": "Gitlab - {{projectName}}",
"PAGE_TITLE": "Gitlab - {{projectName}}",
"INFO_VERIFYING_IP": "Las peticiones de Gitlab no van firmadas, con la IP de origen verificamos su procedencia. Déjalo vacío y no se verificarán."
},
"GITHUB": {
"SECTION_NAME": "Github",
"APP_TITLE": "Github - {{projectName}}"
"PAGE_TITLE": "Github - {{projectName}}"
},
"WEBHOOKS": {
"APP_TITLE": "Webhooks - {{projectName}}",
"PAGE_TITLE": "Webhooks - {{projectName}}",
"SECTION_NAME": "Webhooks",
"SUBTITLE": "Los Webhooks notificarán a servicios externos sobre todos los eventos que ocurren en Taiga como comentarios, historias de usuario...",
"ADD_NEW": "Añadir un Nuevo Webhook",
@ -441,6 +543,7 @@
"WEBHOOK_NAME": "Webhook '{{name}}'"
},
"CUSTOM_ATTRIBUTES": {
"PAGE_TITLE": "{{sectionName}} - Atributos Personalizados - {{projectName}}",
"ADD": "Añadir atributo personalizado",
"EDIT": "Editar atributo personalizado",
"DELETE": "Eliminar atributo personalizado",
@ -518,9 +621,36 @@
"TITLE": "Servicios"
}
},
"USER": {
"PROFILE": {
"PAGE_TITLE": "{{userFullName}} (@{{userUsername}})",
"EDIT": "Editar perfil",
"FOLLOW": "Seguir",
"PROJECTS": "Proyectos",
"CLOSED_US": "Historias cerradas",
"CONTACTS": "Contactos",
"REPORT": "Reportar Abuso",
"ACTIVITY_TAB": "Pestaña de Actividad",
"PROJECTS_TAB": "Pestaña de Proyectos",
"CONTACTS_TAB": "Pestaña de Contactos",
"FAVORITES_TAB": "Pestaña de Favoritos",
"CONTACTS_EMPTY": "{{username}} no tiene contactos todavía",
"CURRENT_USER_CONTACTS_EMPTY": "No tienes contactos todavía",
"CURRENT_USER_CONTACTS_EMPTY_EXPLAIN": "Las personas con las que trabajas en Taiga serán tus contactos automáticamente",
"PROJECTS_EMPTY": "{{username}} no tiene proyectos todavía"
},
"PROFILE_SIDEBAR": {
"TITLE": "Tu perfil",
"DESCRIPTION": "La gente puede ver aquello que haces y en qué estás trabajando. Añade una buena bio para que puedan ver la mejor versión de tu perfil.",
"ADD_INFO": "Edita tu bio"
}
},
"PROJECT": {
"PAGE_TITLE": "{{projectName}}",
"WELCOME": "Bienvenido",
"SECTION_PROJECTS": "Proyectos",
"HELP": "Reordena tus proyectos para ver arriba los que más utilizas.<br/> Los 10 primeros aparecerán en la lista de proyectos de la barra de navegación de arriba.",
"PRIVATE": "Proyecto privado",
"STATS": {
"PROJECT": "puntos<br/> proyecto",
"DEFINED": "puntos<br/> definidos",
@ -529,6 +659,7 @@
},
"SECTION": {
"SEARCH": "Buscar",
"TIMELINE": "Timeline",
"BACKLOG": "Backlog",
"KANBAN": "Kanban",
"ISSUES": "Peticiones",
@ -541,9 +672,32 @@
"SECTION_TITLE": "Tus proyectos",
"PLACEHOLDER_SEARCH": "Buscar en...",
"ACTION_CREATE_PROJECT": "Crear proyecto",
"TITLE_ACTION_IMPORT": "Importar proyecto",
"ACTION_IMPORT_PROJECT": "Importar proyecto",
"SEE_MORE_PROJECTS": "Ver más proyectos",
"TITLE_CREATE_PROJECT": "Crear proyecto",
"TITLE_IMPORT_PROJECT": "Importar proyecto",
"TITLE_PRVIOUS_PROJECT": "Mostrar proyectos anteriores",
"TITLE_NEXT_PROJECT": "Mostrar siguientes proyectos"
"TITLE_NEXT_PROJECT": "Mostrar siguientes proyectos",
"HELP_TITLE": "Página de Soporte de Taiga",
"HELP": "Ayuda",
"FEEDBACK_TITLE": "Envíanos tu feedback",
"FEEDBACK": "Feedback",
"NOTIFICATIONS_TITLE": "Edita la configuración de tus notificaciones",
"NOTIFICATIONS": "Notificaciones",
"ORGANIZATIONS_TITLE": "Edita tus organizaciónes",
"ORGANIZATIONS": "Editar organizaciones",
"SETTINGS_TITLE": "Editar tus configuraciones",
"SETTINGS": "Configuraciones",
"VIEW_PROFILE_TITLE": "Ver perfil",
"VIEW_PROFILE": "Ver perfil",
"EDIT_PROFILE_TITLE": "Editar tu perfil",
"EDIT_PROFILE": "Editar Perfil",
"CHANGE_PASSWORD_TITLE": "Cambiar contraseña",
"CHANGE_PASSWORD": "Cambiar contraseña",
"DASHBOARD_TITLE": "Dashboard",
"DISCOVER_TITLE": "Descubre los proyectos más relevantes",
"DISCOVER": "Descubrir",
"ACTION_REORDER": "Arrastrar y soltar para reordenar"
},
"IMPORT": {
"TITLE": "Importando Proyecto",
@ -624,6 +778,8 @@
}
},
"US": {
"PAGE_TITLE": "{{userStorySubject}} - Historia de Usuario {{userStoryRef}} - {{projectName}}",
"PAGE_DESCRIPTION": "Estado: {{userStoryStatus }}. Completado el {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} de {{userStoryTotalTasks}} tareas cerradas). Puntos: {{userStoryPoints}}. Descripción: {{userStoryDescription}}",
"SECTION_NAME": "Detalles de historia de usuario",
"LINK_TASKBOARD": "Panel de tareas",
"TITLE_LINK_TASKBOARD": "Ir al panel de tareas",
@ -711,6 +867,8 @@
}
},
"BACKLOG": {
"PAGE_TITLE": "Backlog - {{projectName}}",
"PAGE_DESCRIPTION": "El panel del backlog, con las historias de usuario y sprints del proyecto {{projectName}}: {{projectDescription}}",
"SECTION_NAME": "Backlog",
"MOVE_US_TO_CURRENT_SPRINT": "Mover al Sprint en curso",
"SHOW_FILTERS": "Mostrar filtros",
@ -790,6 +948,8 @@
"VERSION_ERROR": "Algún compañero se te ha adelantado y ha actualizado esto, nuestros Oompa Loompas no pueden aplicar tus cambios. Por favor, recarga la página y aplícalos nuevamente (se perderán)."
},
"TASKBOARD": {
"PAGE_TITLE": "{{sprintName}} - Sprint - {{projectName}}",
"PAGE_DESCRIPTION": "Sprint {{sprintName}} (desde el {{startDate}} al {{endDate}}) de {{projectName}}. Completado el {{completedPercentage}}% ({{completedPoints}} de {{totalPoints}} puntos). {{openTasks}} de {{totalTasks}} tareas abiertas.",
"SECTION_NAME": "Panel de Tareas",
"TITLE_ACTION_ADD": "Añade una nueva tarea",
"TITLE_ACTION_ADD_BULK": "Añadir nuevas tareas en bloque",
@ -813,6 +973,8 @@
}
},
"TASK": {
"PAGE_TITLE": "{{taskSubject}} - Tarea {{taskRef}} - {{projectName}}",
"PAGE_DESCRIPTION": "Estado: {{taskStatus }}. Descripción: {{taskDescription}}",
"SECTION_NAME": "Detalles de tarea",
"LINK_TASKBOARD": "Panel de tareas",
"TITLE_LINK_TASKBOARD": "Ir al panel de tareas",
@ -846,7 +1008,7 @@
},
"CANCEL_ACCOUNT": {
"TITLE": "Cancelar tu cuenta de usuario",
"SUBTITLE": "Sentimos que estés abandonando la taiga, esperamos que hayas disfrutado de tu estancia :)",
"SUBTITLE": "Sentimos mucho que abandones nuestra taiga, esperamos que hayas disfrutado de tu estancia :)",
"PLACEHOLDER_INPUT_TOKEN": "token de cancelación de cuenta",
"ACTION_LEAVING": "¡Sí, me largo!",
"SUCCESS": "Nuestros Oompa Loompas eliminaron tu cuenta"
@ -858,56 +1020,9 @@
"ACTION_CHANGE_EMAIL": "Cambiar email",
"SUCCESS": "Nuestros Oompa Loompas actualizaron tu correo"
},
"CHANGE_PASSWORD_RECOVERY_FORM": {
"TITLE": "Crear una nueva contraseña de Taiga",
"SUBTITLE": "Y bueno, es posible que quieras comer un poco más de alimentos ricos en hierro, son buenos para tu cerebro :P",
"PLACEHOLDER_RECOVER_PASSWORD_TOKEN": "token de recuperación de contraseña",
"LINK_NEED_TOKEN": "¿Necesitas una?",
"TITLE_LINK_NEED_TOKEN": "¿Necesitas un token para recuperar tu contraseña porque la has olvidado?",
"PLACEHOLDER_NEW_PASSWORD": "Nueva contraseña",
"PLACEHOLDER_RE_TYPE_NEW_PASSWORD": "Reescriba la nueva contraseña",
"ACTION_RESET_PASSWORD": "Restablecer contraseña",
"SUCCESS": "Nuestro Oompa Loopas guardaron tu nueva contraseña.<br /> Intenta <strong>registrarte</strong> con ella"
},
"FORGOT_PASSWORD_FORM": {
"TITLE": "Vaya, ¿has olvidado tu contraseña?",
"SUBTITLE": "Escribe tu nombre de usuario o email para obtener una nueva",
"PLACEHOLDER_FIELD": "Nombre de usuario o email",
"ACTION_RESET_PASSWORD": "Restablecer Contraseña",
"LINK_CANCEL": "Nah, llévame de vuelta, creo que lo recordé.",
"SUCCESS": "<strong>¡Revisa tu bandeja de entrada!</strong><br />Te hemos enviado un mail con las instrucciones necesarias para restablecer tu contraseña\n",
"ERROR": "Según nuestros Oompa Loompas tú no estás registrado"
},
"LOGIN_COMMON": {
"HEADER": "Ya tengo una cuenta en Taiga",
"PLACEHOLDER_AUTH_NAME": "Nombre de usuario o email (distingue mayúsculas y minúsculas)",
"LINK_FORGOT_PASSWORD": "¿La olvidaste?",
"TITLE_LINK_FORGOT_PASSWORD": "¿Has olvidado tu contraseña?",
"ACTION_ENTER": "Entrar",
"ACTION_SIGN_IN": "Ingresar",
"PLACEHOLDER_AUTH_PASSWORD": "Contraseña (distingue mayúsculas y minúsculas)"
},
"LOGIN_FORM": {
"ERROR_AUTH_INCORRECT": "Nuestros Oompa Loompas indican que tu nombre de usuario/correo o contraseña son incorrectos",
"ERROR_GENERIC": "Nuestros Oompa Loompas indican que ha habido un error.",
"SUCCESS": "Nuestros Oompa Loompas están felices, bienvenido a Taiga."
},
"INVITATION_LOGIN_FORM": {
"NOT_FOUND": "<strong>Ooops, tenemos un problema</strong><br /> Nuestros Oompa Loompas no pueden encontrar tu invitación.",
"SUCCESS": "¡Acabas de unirte al proyecto! Bienvenido a {{project_name}}",
"ERROR": "Según nuestros Oompa Loompas tú no estás registrado o has escrito mal tu contraseña."
},
"REGISTER_FORM": {
"TITLE": "Crea tu nueva cuenta en Taiga, ¡es gratis!",
"PLACEHOLDER_NAME": "Escribe un nombre de usuario (distingue mayúsculas y minúsculas)",
"PLACEHOLDER_FULL_NAME": "Escribe tu nombre completo",
"PLACEHOLDER_EMAIL": "Tu email",
"PLACEHOLDER_PASSWORD": "Establece una contraseña (distingue mayúsculas y minúsculas)",
"ACTION_SIGN_UP": "Registrarme",
"TITLE_LINK_LOGIN": "Iniciar sesión",
"LINK_LOGIN": "¿Ya te has registrado? Inicia sesión"
},
"ISSUES": {
"PAGE_TITLE": "Peticiones - {{projectName}}",
"PAGE_DESCRIPTION": "El panel de peticiones del proyecto {{projectName}}: {{projectDescription}}\n",
"LIST_SECTION_NAME": "Peticiones",
"SECTION_NAME": "Detalles de petición",
"ACTION_NEW_ISSUE": "+ NUEVA PETICIÓN",
@ -921,6 +1036,11 @@
"TITLE_NEXT_ISSUE": "petición siguiente",
"ACTION_DELETE": "Borrar petición",
"LIGHTBOX_TITLE_BLOKING_ISSUE": "Petición bloqueada",
"FIELDS": {
"PRIORITY": "Prioridad",
"SEVERITY": "Gravedad",
"TYPE": "Tipo"
},
"CONFIRM_PROMOTE": {
"TITLE": "Promover esta petición a una nueva historia de usuario",
"MESSAGE": "¿Está seguro de que desea crear una nueva Historia de Usuario a partir de esta Petición?"
@ -966,7 +1086,13 @@
}
}
},
"ISSUE": {
"PAGE_TITLE": "{{issueSubject}} - Petición {{issueRef}} - {{projectName}}",
"PAGE_DESCRIPTION": "Estado: {{issueStatus }}. Tipo: {{issueType}}, Prioridad: {{issuePriority}}. Gravedad: {{issueSeverity}}. Descripción: {{issueDescription}}"
},
"KANBAN": {
"PAGE_TITLE": "Kanban - {{projectName}}",
"PAGE_DESCRIPTION": "El panel Kanban, con las historias de usuario del proyecto {{projectName}}: {{projectDescription}}",
"SECTION_NAME": "Kanban",
"TITLE_ACTION_FOLD": "Plegar columna",
"TITLE_ACTION_UNFOLD": "Desplegar columna",
@ -981,6 +1107,8 @@
"UNDO_ARCHIVED": "Arrástrala y suéltala de nuevo para deshacer el cambio"
},
"SEARCH": {
"PAGE_TITLE": "Buscar - {{projectName}}",
"PAGE_DESCRIPTION": "Busca cualquier cosa: historias de usuario, peticiones, tareas o páginas del wiki en el proyecto {{projectName}}: {{projectDescription}}",
"FILTER_USER_STORIES": "Historias de Usuario",
"FILTER_ISSUES": "Peticiones",
"FILTER_TASKS": "Tareas",
@ -991,6 +1119,8 @@
"EMPTY_DESCRIPTION": "Prueba con otra pestaña de las de arriba o busca de nuevo"
},
"TEAM": {
"PAGE_TITLE": "Equipo - {{projectName}}",
"PAGE_DESCRIPTION": "EL panel de equipo, para mostrar a todos los miembros del proyecto {{projectName}}: {{projectDescription}}",
"SECTION_NAME": "Equipo",
"APP_TITLE": "EQUIPO - {{projectName}}",
"PLACEHOLDER_INPUT_SEARCH": "Buscar por nombre completo...",
@ -1011,16 +1141,6 @@
"CONFIRM_LEAVE_PROJECT": "¿Esta seguro que desea dejar el proyecto?",
"ACTION_LEAVE_PROJECT": "Abandonar este proyecto"
},
"CHANGE_PASSWORD": {
"SECTION_NAME": "Cambiar contraseña",
"FIELD_CURRENT_PASSWORD": "Contraseña actual",
"PLACEHOLDER_CURRENT_PASSWORD": "Tu contraseña actual (o déjalo vacío si todavía no tienes contraseña)",
"FIELD_NEW_PASSWORD": "Nueva contraseña",
"PLACEHOLDER_NEW_PASSWORD": "Escribe una contraseña nueva",
"FIELD_RETYPE_PASSWORD": "Reescribe la nueva contraseña",
"PLACEHOLDER_RETYPE_PASSWORD": "Reescribe la nueva contraseña",
"ERROR_PASSWORD_MATCH": "Las contraseñas no coinciden"
},
"USER_SETTINGS": {
"AVATAR_MAX_SIZE": "[Tamaño Max.: {{maxFileSize}}]",
"MENU": {
@ -1059,7 +1179,7 @@
"EMAIL": "Correo",
"FULL_NAME": "Nombre completo",
"PLACEHOLDER_FULL_NAME": "Indica tu nombre completo (p. e. Íñigo Montoya)",
"BIO": "Bio",
"BIO": "Bio (max. 210 caracteres)",
"PLACEHOLDER_BIO": "Dinos algo acerca de ti",
"LANGUAGE": "Idioma",
"LANGUAGE_DEFAULT": "- usar idioma por defecto -"
@ -1074,6 +1194,8 @@
"PROGRESS_NAME_DESCRIPTION": "Nombre y descripción"
},
"WIKI": {
"PAGE_TITLE": "{{wikiPageName}} - Wiki - {{projectName}}",
"PAGE_DESCRIPTION": "última edición el {{lastModifiedDate}} ({{totalEditions}} ediciones en total) Contenido: {{ wikiPageContent }}",
"DATETIME": "DD MMM YYYY HH:mm",
"PLACEHOLDER_PAGE": "Escribe el contenido de tu página",
"REMOVE": "Eliminar esta página del wiki",
@ -1087,5 +1209,43 @@
"LAST_EDIT": "última <br />edición",
"LAST_MODIFICATION": "ultima modificación"
}
},
"HINTS": {
"SECTION_NAME": "Consejo",
"LINK": "Si quieres saber como funciona visita nuestra página de soporte",
"LINK_TITLE": "Visita nuestra página de soporte",
"HINT1_TITLE": "¿Sabes que puedes importar y exportar proyectos?",
"HINT1_TEXT": "Esto permite extraer todos tus datos para moverlos de una instancia de Taiga a otra.\n",
"HINT2_TITLE": "¿Sabías que puedes crear atributos personalizados?",
"HINT2_TEXT": "Los equipos ahora pueden crear atributos personalizados como un medio flexible para introducir datos específicos útiles para su flujo de trabajo particular",
"HINT3_TITLE": "Reordenar tus proyectos para facilitar el acceso a los más relevantes para ti",
"HINT3_TEXT": "Los 10 primeros proyectos aparecerán en el listado del acceso directo de la barra superior.",
"HINT4_TITLE": "Has olvidado en qué estabas trabajando?",
"HINT4_TEXT": "No te preocupes, en el dashboard encontrarás las tareas, peticiones e historias de usuario abiertas en el orden en el que trabajste en ellas."
},
"TIMELINE": {
"UPLOAD_ATTACHMENT": "{{username}} ha subido un nuevo adjunto en {{obj_name}}\n",
"US_CREATED": "{{username}} ha creado una nueva historia {{obj_name}} en {{project_name}}",
"ISSUE_CREATED": "{{username}} ha creado una nueva petición {{obj_name}} en {{project_name}}",
"TASK_CREATED": "{{username}} ha creado una nueva tarea {{obj_name}} en {{project_name}}",
"TASK_CREATED_WITH_US": "{{username}} ha creado una nueva tarea {{obj_name}} en {{project_name}} que proviene de la historia {{us_name}}",
"WIKI_CREATED": "{{username}} ha creado una nueva página de wiki {{obj_name}} en {{project_name}}\n",
"MILESTONE_CREATED": "{{username}} ha creado un nuevo sprint {{obj_name}} en {{project_name}}",
"NEW_PROJECT": "{{username}} creó el proyecto {{project_name}}",
"MILESTONE_UPDATED": "{{username}} ha actualizado el sprint {{obj_name}}",
"US_UPDATED": "{{username}} ha actualizado el atributo \"{{field_name}}\" de la historia {{obj_name}}",
"ISSUE_UPDATED": "{{username}} ha actualizado el atributo \"{{field_name}}\" de la petición {{obj_name}}",
"TASK_UPDATED": "{{username}} ha actualizado el atributo \"{{field_name}}\" de la tarea {{obj_name}}",
"TASK_UPDATED_WITH_US": "{{username}} ha actualizado el atributo \"{{field_name}}\" de la tarea {{obj_name}} que proviene de la historia {{us_name}}",
"WIKI_UPDATED": "{{username}} ha actualizado la página del wiki {{obj_name}}",
"NEW_COMMENT_US": "{{username}} ha añadido un comentado en la historia {{obj_name}}",
"NEW_COMMENT_ISSUE": "{{username}} ha añadido un comentado en la petición {{obj_name}}",
"NEW_COMMENT_TASK": "{{username}} ha añadido un comentado en la tarea {{obj_name}}",
"NEW_MEMBER": "{{project_name}} tiene un nuevo miembro",
"US_ADDED_MILESTONE": "{{username}} ha añadido la historia {{obj_name}} a {{sprint_name}}",
"US_REMOVED_FROM_MILESTONE": "{{username}} ha añadido la historia {{obj_name}} al backlog",
"BLOCKED": "{{username}} ha bloqueado {{obj_name}}",
"UNBLOCKED": "{{username}} ha desbloqueado {{obj_name}}",
"NEW_USER": "{{username}} se ha unido a Taiga"
}
}

View File

@ -100,6 +100,10 @@
"SAT": "Lau"
}
},
"SEE_USER_PROFILE": "See {{username }} profile",
"USER_STORY": "Käyttäjätarina",
"TASK": "Task",
"ISSUE": "Issue",
"TAGS": {
"PLACEHOLDER": "Anna avainsana...",
"DELETE": "Poista avainsana",
@ -125,7 +129,8 @@
"ASSIGNED_TO": "Tekijä",
"POINTS": "Pisteet",
"BLOCKED_NOTE": "estetty muistiinpano",
"IS_BLOCKED": "on estetty"
"IS_BLOCKED": "on estetty",
"REF": "Ref"
},
"ROLES": {
"ALL": "Kaikki"
@ -231,14 +236,107 @@
"ADD_WIKI_LINKS": "Lisää wiki-linkkejä",
"DELETE_WIKI_LINKS": "Poista wiki-linkkejä"
}
},
"META": {
"PAGE_TITLE": "Taiga",
"PAGE_DESCRIPTION": "Taiga is a project management platform for startups and agile developers & designers who want a simple, beautiful tool that makes work truly enjoyable."
}
},
"LOGIN": {
"PAGE_TITLE": "Login - Taiga",
"PAGE_DESCRIPTION": "Logging in to Taiga, a project management platform for startups and agile developers & designers who want a simple, beautiful tool that makes work truly enjoyable."
},
"AUTH": {
"INVITED_YOU": "on kutsunut sinut projektiin",
"NOT_REGISTERED_YET": "Etkö ole vielä rekisteröitynyt?",
"REGISTER": "Rekisteröidy",
"CREATE_ACCOUNT": "luo ilmainen tunnuksesi täällä"
},
"LOGIN_COMMON": {
"HEADER": "Minulla on jo Taiga tunnus",
"PLACEHOLDER_AUTH_NAME": "Käyttäjänimi tai sähköposti (kirjainkoko merkitsevä)",
"LINK_FORGOT_PASSWORD": "Unohditko?",
"TITLE_LINK_FORGOT_PASSWORD": "Unohditko salasanasi?",
"ACTION_ENTER": "Sisään",
"ACTION_SIGN_IN": "Kirjaudu sisään",
"PLACEHOLDER_AUTH_PASSWORD": "Salasana (kirjainkoko merkitsevä)"
},
"LOGIN_FORM": {
"ERROR_AUTH_INCORRECT": "Oompa Loompas sanovat että käyttäjänimesi tai sähköpostisi tai salasanasi on väärä.",
"SUCCESS": "Oompa Loompas ovat onnellisia, tervetuloa Taigaan."
},
"REGISTER": {
"PAGE_TITLE": "Register - Taiga",
"PAGE_DESCRIPTION": "Create your account in Taiga, a project management platform for startups and agile developers & designers who want a simple, beautiful tool that makes work truly enjoyable."
},
"REGISTER_FORM": {
"TITLE": "Rekisteröi uusi Taiga tunnus (ilmainen)",
"PLACEHOLDER_NAME": "Anna käyttäjänimi (kirjainkoko on merkitsevä)",
"PLACEHOLDER_FULL_NAME": "Anna koko nimesi",
"PLACEHOLDER_EMAIL": "Sähköpostisi",
"PLACEHOLDER_PASSWORD": "Anna salasana (kirjainkoko merkitsevä)",
"ACTION_SIGN_UP": "Kirjaudu sisään",
"TITLE_LINK_LOGIN": "Kirjaudu sisään",
"LINK_LOGIN": "Oletko jo rekisteröitynyt? Kirjaudu sisään"
},
"FORGOT_PASSWORD": {
"PAGE_TITLE": "Forgot password - Taiga",
"PAGE_DESCRIPTION": "Enter your username or email to get a new password and you can access to Taiga again."
},
"FORGOT_PASSWORD_FORM": {
"TITLE": "Oops, unohditko salasanasi?",
"SUBTITLE": "Anna käyttäjänimesi tai sähköpostisi saadaksesi uuden",
"PLACEHOLDER_FIELD": "Käyttäjänimi tai sähköposti",
"ACTION_RESET_PASSWORD": "Uusi salsanasi",
"LINK_CANCEL": "Vie minut takaisin, muistan sen.",
"SUCCESS": "<strong>Tarkista sähköpostisi!</strong><br />Olemme lähettäneet sinulle sähköpostissa kirjautumisohjeet",
"ERROR": "Oompa Loompas sanovat että käyttäjänimesi tai sähköpostisi tai salasanasi on väärä."
},
"CHANGE_PASSWORD": {
"PAGE_TITLE": "Change you password - Taiga",
"PAGE_DESCRIPTION": "Set a new passoword for your Taiga account and hey!, you may want to eat some more iron-rich food, it's good for your brain :P",
"SECTION_NAME": "Muuta salasanaa",
"FIELD_CURRENT_PASSWORD": "Nykyinen salasana",
"PLACEHOLDER_CURRENT_PASSWORD": "Nykyinen salasanasi (tai on tyhjä jos sinulla ei vielä ole)",
"FIELD_NEW_PASSWORD": "Uusi salasana",
"PLACEHOLDER_NEW_PASSWORD": "Anna uusi salasana",
"FIELD_RETYPE_PASSWORD": "Anna salasana uudelleen",
"PLACEHOLDER_RETYPE_PASSWORD": "Anna salasana uudelleen",
"ERROR_PASSWORD_MATCH": "Salasanat eivät täsmää"
},
"CHANGE_PASSWORD_RECOVERY_FORM": {
"TITLE": "Luo uusi pääsy Taigaan",
"SUBTITLE": "Rautapitoinen ruoka on hyväksi aivoille :P",
"PLACEHOLDER_RECOVER_PASSWORD_TOKEN": "Palauta sähköposti-tokeni",
"LINK_NEED_TOKEN": "Tarvitsetko yhden?",
"TITLE_LINK_NEED_TOKEN": "Tarvitsitko tokenia palauttaakseni salasanan koska unohdit sen?",
"PLACEHOLDER_NEW_PASSWORD": "Uusi salasana",
"PLACEHOLDER_RE_TYPE_NEW_PASSWORD": "Anna salasana uudelleen",
"ACTION_RESET_PASSWORD": "Uusi salsanasi",
"SUCCESS": "Oompa Loompas tallensivat uuden salasanasi.<br /> Yritä <strong>kirjautua sisään</strong> sillä."
},
"INVITATION": {
"PAGE_TITLE": "Invitation acceptance - Taiga",
"PAGE_DESCRIPTION": "Accept the invitation to join a project in Taiga, a project management platform for startups and agile developers & designers who want a simple, beautiful tool that makes work truly enjoyable."
},
"INVITATION_LOGIN_FORM": {
"NOT_FOUND": "<strong>Ooops, meillä on ongelma</strong><br />Our Oompa Loompas eivät löydä kutsuasi.",
"SUCCESS": "Olet onnistuneesti liittynyt projektiin {{project_name}}. Tervetuloa!",
"ERROR": "Oompa Loompas sanovat että käyttäjänimesi tai sähköpostisi tai salasanasi on väärä."
},
"HOME": {
"PAGE_TITLE": "Home - Taiga",
"PAGE_DESCRIPTION": "The Taiga home page with your main projects and all your assigned and watched user stories, tasks and issues",
"EMPTY_WATCHING": "<strong>Follow</strong> the projects, User Stories, Tasks, Issues... that you want to know about :)",
"EMPTY_PROJECT_LIST": "You don't have any projects yet",
"WORKING_ON_SECTION": "Working on",
"WATCHING_SECTION": "Watching"
},
"PROJECTS": {
"PAGE_TITLE": "My projects - Taiga",
"PAGE_DESCRIPTION": "A list with all your projects, you can reorder or create a new one.",
"MY_PROJECTS": "My projects"
},
"ATTACHMENT": {
"SECTION_NAME": "liitteet",
"TITLE": "{{ fileName }} ladattu {{ date }}\n",
@ -276,6 +374,7 @@
},
"MEMBERSHIPS": {
"TITLE": "Hallinnoi jäseniä",
"PAGE_TITLE": "Memberships - {{projectName}}",
"ADD_BUTTON": "+ Uusi jäsen",
"ADD_BUTTON_TITLE": "Lisää jäsen"
},
@ -311,7 +410,7 @@
"SALT_CHAT_ROOM": "Voit halutessasi lisätä suolaan chat-huoneen nimeen"
},
"PROJECT_PROFILE": {
"PAGE_TITLE": "Projektin profiili - {{sectionName}} - {{projectName}}",
"PAGE_TITLE": "{{sectionName}} - Projektin profiili - {{projectName}}",
"PROJECT_DETAILS": "Projektin tiedot",
"PROJECT_NAME": "Projektin nimi",
"PROJECT_SLUG": "Projektin hukka-aika",
@ -352,23 +451,26 @@
"ISSUE_ADD": "Lisää oma kenttä pyynnöille"
},
"PROJECT_VALUES": {
"APP_TITLE": "Projektin arvot - {{sectionName}} - {{projectName}}",
"PAGE_TITLE": "{{sectionName}} - Project values - {{projectName}}",
"REPLACEMENT": "Kaikkii riveihin joissa on tämä arvo muutetaan ",
"ERROR_DELETE_ALL": "Et voi poistaa kaikkia arvoja."
},
"PROJECT_VALUES_POINTS": {
"TITLE": "Kt pisteet",
"TITLE": "Points",
"SUBTITLE": "Määrittele pisteet joihin käyttäjätarinat voidaan arvioida",
"US_TITLE": "US points",
"ACTION_ADD": "Lisää uusi piste"
},
"PROJECT_VALUES_PRIORITIES": {
"TITLE": "Pyyntöjen tärkeydet",
"TITLE": "Priorities",
"SUBTITLE": "Määrittele tärkeydet pyynnöille",
"ISSUE_TITLE": "Issue priorities",
"ACTION_ADD": "Lisää uusi tärkeys"
},
"PROJECT_VALUES_SEVERITIES": {
"TITLE": "Pyyntöjen vakavuudet",
"TITLE": "Severities",
"SUBTITLE": "Määrittele pyyntöjen vakavuudet",
"ISSUE_TITLE": "Issue severities",
"ACTION_ADD": "Lisää uusi vakavuus"
},
"PROJECT_VALUES_STATUS": {
@ -382,10 +484,10 @@
"TITLE": "Tyypit",
"SUBTITLE": "Määrittele pyyntöjen tyypit",
"ISSUE_TITLE": "Pyyntöjen tyypit",
"ACTION_ADD": "Lisää uusi tyyppi"
"ACTION_ADD": "Lisää uusi {{objName}}"
},
"ROLES": {
"SECTION_NAME": "Roolit - {{projectName}}",
"PAGE_TITLE": "Roles - {{projectName}}",
"WARNING_NO_ROLE": "Ole varovainen, yksikään rooli projektissasi ei voi arvioida käyttäjätarinoidesi kokoa",
"HELP_ROLE_ENABLED": "Tämän roolin omaavat jäsenet voivat arvioida käyttäjätarinoiden kokoja",
"COUNT_MEMBERS": "{{ role.members_count }} jäsentä joilla tämä rooli",
@ -402,20 +504,20 @@
},
"BITBUCKET": {
"SECTION_NAME": "Bitbucket",
"APP_TITLE": "Bitbucket - {{projectName}}",
"PAGE_TITLE": "Bitbucket - {{projectName}}",
"INFO_VERIFYING_IP": "Bitbucket pyynnöt eivät ole allekirjoitettuja joten tarkista IP. Jos IP on tyhjä, ei sitä tarkisteta."
},
"GITLAB": {
"SECTION_NAME": "Gitlab",
"APP_TITLE": "Gitlab - {{projectName}}",
"PAGE_TITLE": "Gitlab - {{projectName}}",
"INFO_VERIFYING_IP": "Gitlab pyynnöt eivät ole allekirjoitettuja joten tarkista IP. Jos IP on tyhjä, ei sitä tarkisteta."
},
"GITHUB": {
"SECTION_NAME": "Github",
"APP_TITLE": "Github - {{projectName}}"
"PAGE_TITLE": "Github - {{projectName}}"
},
"WEBHOOKS": {
"APP_TITLE": "Webhooks - {{projectName}}",
"PAGE_TITLE": "Webhooks - {{projectName}}",
"SECTION_NAME": "Webhookit",
"SUBTITLE": "Webhookit ilmoittvat ulkoisille palveluille Taigan kommenteista, käyttäjätarinoista ...",
"ADD_NEW": "Lisää webhook",
@ -441,6 +543,7 @@
"WEBHOOK_NAME": "Webhook '{{name}}'"
},
"CUSTOM_ATTRIBUTES": {
"PAGE_TITLE": "{{sectionName}} - Custom Attributes - {{projectName}}",
"ADD": "Anna oma kenttä",
"EDIT": "Muokkaa omaa kenttää",
"DELETE": "Poista oma kenttä",
@ -518,9 +621,36 @@
"TITLE": "Palvelut"
}
},
"USER": {
"PROFILE": {
"PAGE_TITLE": "{{userFullName}} (@{{userUsername}})",
"EDIT": "Edit profile",
"FOLLOW": "Follow",
"PROJECTS": "Projektit",
"CLOSED_US": "Closed US",
"CONTACTS": "Contacts",
"REPORT": "Report Abuse",
"ACTIVITY_TAB": "Activity Tab",
"PROJECTS_TAB": "Projects Tab",
"CONTACTS_TAB": "Contacts Tab",
"FAVORITES_TAB": "Favorites Tab",
"CONTACTS_EMPTY": "{{username}} doesn't have contacts yet",
"CURRENT_USER_CONTACTS_EMPTY": "You don't have contacts yet",
"CURRENT_USER_CONTACTS_EMPTY_EXPLAIN": "The people with whom you work at Taiga will be your contacts automatically",
"PROJECTS_EMPTY": "{{username}} doesn't' have projects yet"
},
"PROFILE_SIDEBAR": {
"TITLE": "Your profile",
"DESCRIPTION": "People can see everything you do and what are you working on. Add a nice bio to give an enhanced version of your information.",
"ADD_INFO": "Edit your bio"
}
},
"PROJECT": {
"PAGE_TITLE": "{{projectName}}",
"WELCOME": "Tervetuloa",
"SECTION_PROJECTS": "Projektit",
"HELP": "Reorder your projects to set in the top the most used ones.<br/> The top 10 projects will appear in the top navigation bar project list",
"PRIVATE": "Yksityinen projekti",
"STATS": {
"PROJECT": "projekti<br/> pisteet",
"DEFINED": "määritely<br/> pistettä",
@ -529,6 +659,7 @@
},
"SECTION": {
"SEARCH": "Hae",
"TIMELINE": "Timeline",
"BACKLOG": "Odottavat",
"KANBAN": "Kanban",
"ISSUES": "Pyynnöt",
@ -541,9 +672,32 @@
"SECTION_TITLE": "Projektisi",
"PLACEHOLDER_SEARCH": "Hae täältä...",
"ACTION_CREATE_PROJECT": "Luo projekti",
"TITLE_ACTION_IMPORT": "Luo projekti tiedostosta",
"ACTION_IMPORT_PROJECT": "Import project",
"SEE_MORE_PROJECTS": "See more projects",
"TITLE_CREATE_PROJECT": "Create project",
"TITLE_IMPORT_PROJECT": "Import project",
"TITLE_PRVIOUS_PROJECT": "Näytä aikaisemmat projektit",
"TITLE_NEXT_PROJECT": "Näytä seuraavat projektit"
"TITLE_NEXT_PROJECT": "Näytä seuraavat projektit",
"HELP_TITLE": "Taiga Support Page",
"HELP": "Help",
"FEEDBACK_TITLE": "Lähetä palautetta",
"FEEDBACK": "Palaute",
"NOTIFICATIONS_TITLE": "Edit your notification settings",
"NOTIFICATIONS": "Ilmoitukset",
"ORGANIZATIONS_TITLE": "Edit your organizations",
"ORGANIZATIONS": "Edit organizations",
"SETTINGS_TITLE": "Edit your settings",
"SETTINGS": "Settings",
"VIEW_PROFILE_TITLE": "View Profile",
"VIEW_PROFILE": "View Profile",
"EDIT_PROFILE_TITLE": "Edit your profile",
"EDIT_PROFILE": "Edit Profile",
"CHANGE_PASSWORD_TITLE": "Muuta salasanaa",
"CHANGE_PASSWORD": "Muuta salasanaa",
"DASHBOARD_TITLE": "Dashboard",
"DISCOVER_TITLE": "Discover trending projects",
"DISCOVER": "Discover",
"ACTION_REORDER": "Drag & drop to reorder"
},
"IMPORT": {
"TITLE": "Luetaan sisään projektia",
@ -624,6 +778,8 @@
}
},
"US": {
"PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}",
"PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Completed {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). Points: {{userStoryPoints}}. Description: {{userStoryDescription}}",
"SECTION_NAME": "Käyttäjätarinan tiedot",
"LINK_TASKBOARD": "Tehtävätaulu",
"TITLE_LINK_TASKBOARD": "Siirry tehtävätauluun",
@ -711,6 +867,8 @@
}
},
"BACKLOG": {
"PAGE_TITLE": "Backlog - {{projectName}}",
"PAGE_DESCRIPTION": "The backlog panel, with user stories and sprints of the project {{projectName}}: {{projectDescription}}",
"SECTION_NAME": "Odottavat",
"MOVE_US_TO_CURRENT_SPRINT": "Siirrä nykyiseen kierrokseen",
"SHOW_FILTERS": "Näytä suodattimet",
@ -790,6 +948,8 @@
"VERSION_ERROR": "Joku Taigassa on päivittänyt tätä ennen sinua. Muutoksiasi ei voida tallentaa. Lataa sivu uudestaan ja korjaa tilanne."
},
"TASKBOARD": {
"PAGE_TITLE": "{{sprintName}} - Sprint taskboard - {{projectName}}",
"PAGE_DESCRIPTION": "Sprint {{sprintName}} (from {{startDate}} to {{endDate}}) of {{projectName}}. Completed {{completedPercentage}}% ({{completedPoints}} of {{totalPoints}} points). {{openTasks}} opened tasks of {{totalTasks}}.",
"SECTION_NAME": "Tehtävätaulu",
"TITLE_ACTION_ADD": "Lisää uusi tehtävä",
"TITLE_ACTION_ADD_BULK": "Lisää monta tehtävää",
@ -813,6 +973,8 @@
}
},
"TASK": {
"PAGE_TITLE": "{{taskSubject}} - Task {{taskRef}} - {{projectName}}",
"PAGE_DESCRIPTION": "Status: {{taskStatus }}. Description: {{taskDescription}}",
"SECTION_NAME": "Tehtävän tiedot",
"LINK_TASKBOARD": "Tehtävätaulu",
"TITLE_LINK_TASKBOARD": "Siirry tehtävätauluun",
@ -858,56 +1020,9 @@
"ACTION_CHANGE_EMAIL": "Muuta sähköpostisi",
"SUCCESS": "Oompa Loompas päivittivät sähköpostisi"
},
"CHANGE_PASSWORD_RECOVERY_FORM": {
"TITLE": "Luo uusi pääsy Taigaan",
"SUBTITLE": "Rautapitoinen ruoka on hyväksi aivoille :P",
"PLACEHOLDER_RECOVER_PASSWORD_TOKEN": "Palauta sähköposti-tokeni",
"LINK_NEED_TOKEN": "Tarvitsetko yhden?",
"TITLE_LINK_NEED_TOKEN": "Tarvitsitko tokenia palauttaakseni salasanan koska unohdit sen?",
"PLACEHOLDER_NEW_PASSWORD": "Uusi salasana",
"PLACEHOLDER_RE_TYPE_NEW_PASSWORD": "Anna salasana uudelleen",
"ACTION_RESET_PASSWORD": "Uusi salsanasi",
"SUCCESS": "Oompa Loompas tallensivat uuden salasanasi.<br /> Yritä <strong>kirjautua sisään</strong> sillä."
},
"FORGOT_PASSWORD_FORM": {
"TITLE": "Oops, unohditko salasanasi?",
"SUBTITLE": "Anna käyttäjänimesi tai sähköpostisi saadaksesi uuden",
"PLACEHOLDER_FIELD": "Käyttäjänimi tai sähköposti",
"ACTION_RESET_PASSWORD": "Uusi salsanasi",
"LINK_CANCEL": "Vie minut takaisin, muistan sen.",
"SUCCESS": "<strong>Tarkista sähköpostisi!</strong><br />Olemme lähettäneet sinulle sähköpostissa kirjautumisohjeet",
"ERROR": "Oompa Loompas sanovat että käyttäjänimesi tai sähköpostisi tai salasanasi on väärä."
},
"LOGIN_COMMON": {
"HEADER": "Minulla on jo Taiga tunnus",
"PLACEHOLDER_AUTH_NAME": "Käyttäjänimi tai sähköposti (kirjainkoko merkitsevä)",
"LINK_FORGOT_PASSWORD": "Unohditko?",
"TITLE_LINK_FORGOT_PASSWORD": "Unohditko salasanasi?",
"ACTION_ENTER": "Sisään",
"ACTION_SIGN_IN": "Kirjaudu sisään",
"PLACEHOLDER_AUTH_PASSWORD": "Salasana (kirjainkoko merkitsevä)"
},
"LOGIN_FORM": {
"ERROR_AUTH_INCORRECT": "Oompa Loompas sanovat että käyttäjänimesi tai sähköpostisi tai salasanasi on väärä.",
"ERROR_GENERIC": "Oompa Loompas havaitsivat virheen",
"SUCCESS": "Oompa Loompas ovat onnellisia, tervetuloa Taigaan."
},
"INVITATION_LOGIN_FORM": {
"NOT_FOUND": "<strong>Ooops, meillä on ongelma</strong><br />Our Oompa Loompas eivät löydä kutsuasi.",
"SUCCESS": "Olet onnistuneesti liittynyt projektiin {{project_name}}. Tervetuloa!",
"ERROR": "Oompa Loompas sanovat että käyttäjänimesi tai sähköpostisi tai salasanasi on väärä."
},
"REGISTER_FORM": {
"TITLE": "Rekisteröi uusi Taiga tunnus (ilmainen)",
"PLACEHOLDER_NAME": "Anna käyttäjänimi (kirjainkoko on merkitsevä)",
"PLACEHOLDER_FULL_NAME": "Anna koko nimesi",
"PLACEHOLDER_EMAIL": "Sähköpostisi",
"PLACEHOLDER_PASSWORD": "Anna salasana (kirjainkoko merkitsevä)",
"ACTION_SIGN_UP": "Kirjaudu sisään",
"TITLE_LINK_LOGIN": "Kirjaudu sisään",
"LINK_LOGIN": "Oletko jo rekisteröitynyt? Kirjaudu sisään"
},
"ISSUES": {
"PAGE_TITLE": "Issues - {{projectName}}",
"PAGE_DESCRIPTION": "The issues list panel of the project {{projectName}}: {{projectDescription}}",
"LIST_SECTION_NAME": "Pyynnöt",
"SECTION_NAME": "Pyynnön tiedot",
"ACTION_NEW_ISSUE": "+ UUSI PYYNTÖ",
@ -921,6 +1036,11 @@
"TITLE_NEXT_ISSUE": "seuraava pyyntö",
"ACTION_DELETE": "Poista pyyntö",
"LIGHTBOX_TITLE_BLOKING_ISSUE": "Estävä pyyntö",
"FIELDS": {
"PRIORITY": "Tärkeys",
"SEVERITY": "Vakavuus",
"TYPE": "Tyyppi"
},
"CONFIRM_PROMOTE": {
"TITLE": "Liitä tämä pyyntö uuteen käyttäjätarinaan",
"MESSAGE": "Haluatko varmasti lisätä uuden käyttäjätarinan tästä pyynnöstä?"
@ -966,7 +1086,13 @@
}
}
},
"ISSUE": {
"PAGE_TITLE": "{{issueSubject}} - Issue {{issueRef}} - {{projectName}}",
"PAGE_DESCRIPTION": "Status: {{issueStatus }}. Type: {{issueType}}, Priority: {{issuePriority}}. Severity: {{issueSeverity}}. Description: {{issueDescription}}"
},
"KANBAN": {
"PAGE_TITLE": "Kanban - {{projectName}}",
"PAGE_DESCRIPTION": "The kanban panel, with user stories of the project {{projectName}}: {{projectDescription}}",
"SECTION_NAME": "Kanban",
"TITLE_ACTION_FOLD": "Kavenna sarake",
"TITLE_ACTION_UNFOLD": "Laajenna sarake",
@ -981,6 +1107,8 @@
"UNDO_ARCHIVED": "Raahaa ja pudota uudelleen peruaksesi"
},
"SEARCH": {
"PAGE_TITLE": "Search - {{projectName}}",
"PAGE_DESCRIPTION": "Search anything, user stories, issues, tasks or wiki pages, in the project {{projectName}}: {{projectDescription}}",
"FILTER_USER_STORIES": "Käyttäjätarinat",
"FILTER_ISSUES": "Pyynnöt",
"FILTER_TASKS": "Tehtävät",
@ -991,6 +1119,8 @@
"EMPTY_DESCRIPTION": "Kokeile ylempiä välilehtiä ja hae uudestaan"
},
"TEAM": {
"PAGE_TITLE": "Team - {{projectName}}",
"PAGE_DESCRIPTION": "The team panel to show all the members of the project {{projectName}}: {{projectDescription}}",
"SECTION_NAME": "Tiimi",
"APP_TITLE": "TIIMI - {{projectName}}",
"PLACEHOLDER_INPUT_SEARCH": "Etsi koko nimellä...",
@ -1011,16 +1141,6 @@
"CONFIRM_LEAVE_PROJECT": "Oletko varma että haluat poistua projektista?",
"ACTION_LEAVE_PROJECT": "Poistu projektista"
},
"CHANGE_PASSWORD": {
"SECTION_NAME": "Muuta salasanaa",
"FIELD_CURRENT_PASSWORD": "Nykyinen salasana",
"PLACEHOLDER_CURRENT_PASSWORD": "Nykyinen salasanasi (tai on tyhjä jos sinulla ei vielä ole)",
"FIELD_NEW_PASSWORD": "Uusi salasana",
"PLACEHOLDER_NEW_PASSWORD": "Anna uusi salasana",
"FIELD_RETYPE_PASSWORD": "Anna salasana uudelleen",
"PLACEHOLDER_RETYPE_PASSWORD": "Anna salasana uudelleen",
"ERROR_PASSWORD_MATCH": "Salasanat eivät täsmää"
},
"USER_SETTINGS": {
"AVATAR_MAX_SIZE": "Maksimi koko {{maxFileSize}}",
"MENU": {
@ -1059,7 +1179,7 @@
"EMAIL": "Sähköposti",
"FULL_NAME": "Koko nimi",
"PLACEHOLDER_FULL_NAME": "Anna koko nimesi",
"BIO": "Bio",
"BIO": "Bio (max. 210 chars)",
"PLACEHOLDER_BIO": "Kerro jotain itsestäsi",
"LANGUAGE": "Kieli",
"LANGUAGE_DEFAULT": "-- käytä oletuskieltä --"
@ -1074,6 +1194,8 @@
"PROGRESS_NAME_DESCRIPTION": "Nimi ja kuvaus"
},
"WIKI": {
"PAGE_TITLE": "{{wikiPageName}} - Wiki - {{projectName}}",
"PAGE_DESCRIPTION": "Last edition on {{lastModifiedDate}} ({{totalEditions}} editions in total) Content: {{ wikiPageContent }}",
"DATETIME": "DD.MM.YY - HH:mm",
"PLACEHOLDER_PAGE": "Kirjoita wiki-sivu",
"REMOVE": "Poista tämä wiki-sivu",
@ -1087,5 +1209,43 @@
"LAST_EDIT": "viimeinen<br/>muokkaus",
"LAST_MODIFICATION": "viimeinen muokkaus"
}
},
"HINTS": {
"SECTION_NAME": "Hint",
"LINK": "If you want to know how to use it visit our support page",
"LINK_TITLE": "Visit our support page",
"HINT1_TITLE": "Did you know you can import and export projects?",
"HINT1_TEXT": "This allow you to extract all your data from one Taiga and move it to another one.",
"HINT2_TITLE": "Did you know you can create custom fields?",
"HINT2_TEXT": "Teams can now create custom fields as a flexible means to enter specific data useful for their particular workflow",
"HINT3_TITLE": "Reorder your projects to feature those most relevant to you",
"HINT3_TEXT": "The top 10 project will be in your top bar direct access",
"HINT4_TITLE": "Did you forgot what were you working on?",
"HINT4_TEXT": "Don't worry, on your dashboard you'll find your open tasks, issues, and user stories in the order you worked on them."
},
"TIMELINE": {
"UPLOAD_ATTACHMENT": "{{username}} has uploaded a new attachment in {{obj_name}}",
"US_CREATED": "{{username}} has created a new US {{obj_name}} in {{project_name}}",
"ISSUE_CREATED": "{{username}} has created a new issue {{obj_name}} in {{project_name}}",
"TASK_CREATED": "{{username}} has created a new task {{obj_name}} in {{project_name}}",
"TASK_CREATED_WITH_US": "{{username}} has created a new task {{obj_name}} in {{project_name}} which belongs to the US {{us_name}}",
"WIKI_CREATED": "{{username}} has created a new wiki page {{obj_name}} in {{project_name}}",
"MILESTONE_CREATED": "{{username}} has created a new sprint {{obj_name}} in {{project_name}}",
"NEW_PROJECT": "{{username}} created the project {{project_name}}",
"MILESTONE_UPDATED": "{{username}} has updated the sprint {{obj_name}}",
"US_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the US {{obj_name}}",
"ISSUE_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the issue {{obj_name}}",
"TASK_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}}",
"TASK_UPDATED_WITH_US": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}} which belongs to the US {{us_name}}",
"WIKI_UPDATED": "{{username}} has updated the wiki page {{obj_name}}",
"NEW_COMMENT_US": "{{username}} has commented in the US {{obj_name}}",
"NEW_COMMENT_ISSUE": "{{username}} has commented in the issue {{obj_name}}",
"NEW_COMMENT_TASK": "{{username}} has commented in the task {{obj_name}}",
"NEW_MEMBER": "{{project_name}} has a new member",
"US_ADDED_MILESTONE": "{{username}} has added the US {{obj_name}} to {{sprint_name}}",
"US_REMOVED_FROM_MILESTONE": "{{username}} has added the US {{obj_name}} to the backlog",
"BLOCKED": "{{username}} has blocked {{obj_name}}",
"UNBLOCKED": "{{username}} has unblocked {{obj_name}}",
"NEW_USER": "{{username}} has joined Taiga"
}
}

File diff suppressed because it is too large Load Diff

1251
app/locales/locale-nl.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -100,6 +100,10 @@
"SAT": "週六"
}
},
"SEE_USER_PROFILE": "See {{username }} profile",
"USER_STORY": "使用者故事",
"TASK": "Task",
"ISSUE": "Issue",
"TAGS": {
"PLACEHOLDER": "我在這裏,請標注我",
"DELETE": "刪除Tag",
@ -125,7 +129,8 @@
"ASSIGNED_TO": "指派給 ",
"POINTS": "點數",
"BLOCKED_NOTE": "已封鎖之筆記",
"IS_BLOCKED": "封鎖"
"IS_BLOCKED": "封鎖",
"REF": "Ref"
},
"ROLES": {
"ALL": "所有"
@ -231,14 +236,107 @@
"ADD_WIKI_LINKS": "新增維基連結",
"DELETE_WIKI_LINKS": "刪除維基連結"
}
},
"META": {
"PAGE_TITLE": "Taiga",
"PAGE_DESCRIPTION": "Taiga is a project management platform for startups and agile developers & designers who want a simple, beautiful tool that makes work truly enjoyable."
}
},
"LOGIN": {
"PAGE_TITLE": "Login - Taiga",
"PAGE_DESCRIPTION": "Logging in to Taiga, a project management platform for startups and agile developers & designers who want a simple, beautiful tool that makes work truly enjoyable."
},
"AUTH": {
"INVITED_YOU": "已邀請您加入此專案",
"NOT_REGISTERED_YET": "還沒註冊嗎?",
"REGISTER": "註冊",
"CREATE_ACCOUNT": "在此建立您的免費帳號"
},
"LOGIN_COMMON": {
"HEADER": "我已登入Taiga",
"PLACEHOLDER_AUTH_NAME": "使用者名稱或電子郵件(注意大小寫)",
"LINK_FORGOT_PASSWORD": "忘記了?",
"TITLE_LINK_FORGOT_PASSWORD": "你忘了密碼嗎",
"ACTION_ENTER": "Enter",
"ACTION_SIGN_IN": "登入",
"PLACEHOLDER_AUTH_PASSWORD": "密碼(大小寫有別)"
},
"LOGIN_FORM": {
"ERROR_AUTH_INCORRECT": "按我們的箹統記錄,你的帳戶名稱/電子郵件或密碼並不正確 ",
"SUCCESS": "我們系統歡迎你加入Taiga"
},
"REGISTER": {
"PAGE_TITLE": "Register - Taiga",
"PAGE_DESCRIPTION": "Create your account in Taiga, a project management platform for startups and agile developers & designers who want a simple, beautiful tool that makes work truly enjoyable."
},
"REGISTER_FORM": {
"TITLE": "註冊一個新的Taiga帳戶(免費 )",
"PLACEHOLDER_NAME": "選一個用戶帳號(注意大小寫)",
"PLACEHOLDER_FULL_NAME": "選取你的全名",
"PLACEHOLDER_EMAIL": "你的電子郵件",
"PLACEHOLDER_PASSWORD": "設定密碼(注意大小寫不同) ",
"ACTION_SIGN_UP": "註冊",
"TITLE_LINK_LOGIN": "登入",
"LINK_LOGIN": "你是否已註冊? 登入"
},
"FORGOT_PASSWORD": {
"PAGE_TITLE": "Forgot password - Taiga",
"PAGE_DESCRIPTION": "Enter your username or email to get a new password and you can access to Taiga again."
},
"FORGOT_PASSWORD_FORM": {
"TITLE": "你是否忘了密碼嗎",
"SUBTITLE": "輸入你的帳戶名稱或電子郵件以進行註冊",
"PLACEHOLDER_FIELD": "使用者名稱或電子郵件",
"ACTION_RESET_PASSWORD": "重設密碼 ",
"LINK_CANCEL": "不,請帶我回去,我記起來了",
"SUCCESS": "<strong>檢查你的收信箱</strong><br />我們送出了一封信<br />裏面有設定你新電子郵件的相關指示",
"ERROR": "按我們的記錄,你尚未註冊"
},
"CHANGE_PASSWORD": {
"PAGE_TITLE": "Change you password - Taiga",
"PAGE_DESCRIPTION": "Set a new passoword for your Taiga account and hey!, you may want to eat some more iron-rich food, it's good for your brain :P",
"SECTION_NAME": "改變密碼 ",
"FIELD_CURRENT_PASSWORD": "現用密碼 ",
"PLACEHOLDER_CURRENT_PASSWORD": "你目前的密碼(如果你未有密碼,此處請空白)",
"FIELD_NEW_PASSWORD": "新密碼 ",
"PLACEHOLDER_NEW_PASSWORD": "輸入新密碼",
"FIELD_RETYPE_PASSWORD": "重新輸入新密碼",
"PLACEHOLDER_RETYPE_PASSWORD": "重新輸入新密碼",
"ERROR_PASSWORD_MATCH": "密碼不符"
},
"CHANGE_PASSWORD_RECOVERY_FORM": {
"TITLE": "創建新Taiga通過",
"SUBTITLE": "你也許該吃點鐵質豐富的食物,它對你的大腦有益處:p",
"PLACEHOLDER_RECOVER_PASSWORD_TOKEN": "回復密碼代碼 ",
"LINK_NEED_TOKEN": "需要一個?",
"TITLE_LINK_NEED_TOKEN": "你是否需要代碼來恢復你所忘記的密碼?",
"PLACEHOLDER_NEW_PASSWORD": "新密碼 ",
"PLACEHOLDER_RE_TYPE_NEW_PASSWORD": "重新輸入新密碼",
"ACTION_RESET_PASSWORD": "重設密碼 ",
"SUCCESS": "系統已儲存你的新密碼<br />試試 <strong>用它登入</strong> "
},
"INVITATION": {
"PAGE_TITLE": "Invitation acceptance - Taiga",
"PAGE_DESCRIPTION": "Accept the invitation to join a project in Taiga, a project management platform for startups and agile developers & designers who want a simple, beautiful tool that makes work truly enjoyable."
},
"INVITATION_LOGIN_FORM": {
"NOT_FOUND": "<strong>Ooops, 有點問題</strong><br />我們的系統無法找到你的邀請信",
"SUCCESS": "你成功地加入此專案,歡迎來到 {{project_name}}",
"ERROR": "按我們的記錄,你尚未註冊或是未輸入有效的密碼 "
},
"HOME": {
"PAGE_TITLE": "Home - Taiga",
"PAGE_DESCRIPTION": "The Taiga home page with your main projects and all your assigned and watched user stories, tasks and issues",
"EMPTY_WATCHING": "<strong>Follow</strong> the projects, User Stories, Tasks, Issues... that you want to know about :)",
"EMPTY_PROJECT_LIST": "You don't have any projects yet",
"WORKING_ON_SECTION": "Working on",
"WATCHING_SECTION": "Watching"
},
"PROJECTS": {
"PAGE_TITLE": "My projects - Taiga",
"PAGE_DESCRIPTION": "A list with all your projects, you can reorder or create a new one.",
"MY_PROJECTS": "My projects"
},
"ATTACHMENT": {
"SECTION_NAME": "附件",
"TITLE": "{{ fileName }} 上傳於 {{ date }}",
@ -276,6 +374,7 @@
},
"MEMBERSHIPS": {
"TITLE": "管理成員",
"PAGE_TITLE": "Memberships - {{projectName}}",
"ADD_BUTTON": "+ 新成員",
"ADD_BUTTON_TITLE": "增加新成員"
},
@ -311,7 +410,7 @@
"SALT_CHAT_ROOM": "你可以把聊天室名稱加上salt code亂數密碼 "
},
"PROJECT_PROFILE": {
"PAGE_TITLE": "專案檔案 - {{sectionName}} - {{projectName}}",
"PAGE_TITLE": "{{sectionName}} - 專案檔案 - {{projectName}}",
"PROJECT_DETAILS": "專案細節",
"PROJECT_NAME": "專案名稱",
"PROJECT_SLUG": "專案代稱",
@ -352,23 +451,26 @@
"ISSUE_ADD": "在問題中加入客制欄位"
},
"PROJECT_VALUES": {
"APP_TITLE": "專案數值- {{sectionName}} - {{projectName}}",
"PAGE_TITLE": "{{sectionName}} - Project values - {{projectName}}",
"REPLACEMENT": "所有此數值物品將改成",
"ERROR_DELETE_ALL": "你不能刪除所有數值"
},
"PROJECT_VALUES_POINTS": {
"TITLE": "使用者故事點數",
"TITLE": "點數",
"SUBTITLE": "指定你的使用者故事點數可能預估為",
"US_TITLE": "US points",
"ACTION_ADD": "增加點數"
},
"PROJECT_VALUES_PRIORITIES": {
"TITLE": "問題優先性",
"TITLE": "優先性",
"SUBTITLE": "指明你將遇到的問題優先程度",
"ISSUE_TITLE": "問題優先性",
"ACTION_ADD": "新增優先性"
},
"PROJECT_VALUES_SEVERITIES": {
"TITLE": "問題急迫性",
"TITLE": "急迫性",
"SUBTITLE": "指明你將遇到問題的嚴重程度",
"ISSUE_TITLE": "問題急迫性",
"ACTION_ADD": "新增急迫性"
},
"PROJECT_VALUES_STATUS": {
@ -382,10 +484,10 @@
"TITLE": "類型",
"SUBTITLE": "指定你的問題類型可能是",
"ISSUE_TITLE": "問題類型",
"ACTION_ADD": "新增類別"
"ACTION_ADD": "新增{{objName}}"
},
"ROLES": {
"SECTION_NAME": "角色- {{projectName}}",
"PAGE_TITLE": "角色- {{projectName}}",
"WARNING_NO_ROLE": "注意,你的專案中無角色可以評估使用者故事的點數",
"HELP_ROLE_ENABLED": "當啟動時,被指派此角色的成員將可以評估使用者故事點數",
"COUNT_MEMBERS": "{{ role.members_count }} 這類角色的成員",
@ -402,20 +504,20 @@
},
"BITBUCKET": {
"SECTION_NAME": "Bitbucket",
"APP_TITLE": "Bitbucket - {{projectName}}",
"PAGE_TITLE": "Bitbucket - {{projectName}}",
"INFO_VERIFYING_IP": "Bitbucket要求不指派因此最佳確認方式為IP位置來源。如果此處空白表示IP未有效確核。"
},
"GITLAB": {
"SECTION_NAME": "Gitlob",
"APP_TITLE": "Gitlab - {{projectName}}",
"PAGE_TITLE": "Gitlab - {{projectName}}",
"INFO_VERIFYING_IP": "GitHub要求不指派因此最佳確認方式為IP位置來源。如果此處空白表示IP未有效確核。"
},
"GITHUB": {
"SECTION_NAME": "Githun",
"APP_TITLE": "Gitlab - {{projectName}}"
"PAGE_TITLE": "Gitlab - {{projectName}}"
},
"WEBHOOKS": {
"APP_TITLE": "Webhooks- {{projectName}}",
"PAGE_TITLE": "Webhooks- {{projectName}}",
"SECTION_NAME": "網頁觸發 ",
"SUBTITLE": "網頁觸發通知關於Taiga事件的外部服務例如評論使用者故事等。 ",
"ADD_NEW": "新增一個網頁觸發 ",
@ -441,6 +543,7 @@
"WEBHOOK_NAME": "網頁觸發 '{{name}}'"
},
"CUSTOM_ATTRIBUTES": {
"PAGE_TITLE": "{{sectionName}} - Custom Attributes - {{projectName}}",
"ADD": "加入客製化欄位",
"EDIT": "編輯客製化欄位",
"DELETE": "刪除客製欄位",
@ -518,9 +621,36 @@
"TITLE": "服務 "
}
},
"USER": {
"PROFILE": {
"PAGE_TITLE": "{{userFullName}} (@{{userUsername}})",
"EDIT": "Edit profile",
"FOLLOW": "Follow",
"PROJECTS": "專案",
"CLOSED_US": "Closed US",
"CONTACTS": "Contacts",
"REPORT": "Report Abuse",
"ACTIVITY_TAB": "Activity Tab",
"PROJECTS_TAB": "Projects Tab",
"CONTACTS_TAB": "Contacts Tab",
"FAVORITES_TAB": "Favorites Tab",
"CONTACTS_EMPTY": "{{username}} doesn't have contacts yet",
"CURRENT_USER_CONTACTS_EMPTY": "You don't have contacts yet",
"CURRENT_USER_CONTACTS_EMPTY_EXPLAIN": "The people with whom you work at Taiga will be your contacts automatically",
"PROJECTS_EMPTY": "{{username}} doesn't' have projects yet"
},
"PROFILE_SIDEBAR": {
"TITLE": "Your profile",
"DESCRIPTION": "People can see everything you do and what are you working on. Add a nice bio to give an enhanced version of your information.",
"ADD_INFO": "Edit your bio"
}
},
"PROJECT": {
"PAGE_TITLE": "{{projectName}}",
"WELCOME": "歡迎",
"SECTION_PROJECTS": "專案",
"HELP": "Reorder your projects to set in the top the most used ones.<br/> The top 10 projects will appear in the top navigation bar project list",
"PRIVATE": "不公開專案",
"STATS": {
"PROJECT": "專案<br/> 點數",
"DEFINED": "已定義<br/> 點數",
@ -529,6 +659,7 @@
},
"SECTION": {
"SEARCH": "搜尋",
"TIMELINE": "Timeline",
"BACKLOG": "待辦任務優先表",
"KANBAN": "Kanban(看板)",
"ISSUES": "問題 ",
@ -541,9 +672,32 @@
"SECTION_TITLE": "你的專案",
"PLACEHOLDER_SEARCH": "搜尋",
"ACTION_CREATE_PROJECT": "創建專案",
"TITLE_ACTION_IMPORT": "滙入專案",
"ACTION_IMPORT_PROJECT": "滙入專案",
"SEE_MORE_PROJECTS": "See more projects",
"TITLE_CREATE_PROJECT": "創建專案",
"TITLE_IMPORT_PROJECT": "滙入專案",
"TITLE_PRVIOUS_PROJECT": "顯示過去專案",
"TITLE_NEXT_PROJECT": "顯示下一個任務"
"TITLE_NEXT_PROJECT": "顯示下一個任務",
"HELP_TITLE": "Taiga Support Page",
"HELP": "Help",
"FEEDBACK_TITLE": "送出回饋 ",
"FEEDBACK": "回饋 ",
"NOTIFICATIONS_TITLE": "Edit your notification settings",
"NOTIFICATIONS": "通知",
"ORGANIZATIONS_TITLE": "Edit your organizations",
"ORGANIZATIONS": "Edit organizations",
"SETTINGS_TITLE": "Edit your settings",
"SETTINGS": "Settings",
"VIEW_PROFILE_TITLE": "View Profile",
"VIEW_PROFILE": "View Profile",
"EDIT_PROFILE_TITLE": "Edit your profile",
"EDIT_PROFILE": "Edit Profile",
"CHANGE_PASSWORD_TITLE": "更換密碼 ",
"CHANGE_PASSWORD": "更換密碼 ",
"DASHBOARD_TITLE": "Dashboard",
"DISCOVER_TITLE": "Discover trending projects",
"DISCOVER": "Discover",
"ACTION_REORDER": "Drag & drop to reorder"
},
"IMPORT": {
"TITLE": "滙入專案中",
@ -624,6 +778,8 @@
}
},
"US": {
"PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}",
"PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Completed {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). Points: {{userStoryPoints}}. Description: {{userStoryDescription}}",
"SECTION_NAME": "使用者故事細節",
"LINK_TASKBOARD": "任務板",
"TITLE_LINK_TASKBOARD": "到任務板去",
@ -711,6 +867,8 @@
}
},
"BACKLOG": {
"PAGE_TITLE": "Backlog - {{projectName}}",
"PAGE_DESCRIPTION": "The backlog panel, with user stories and sprints of the project {{projectName}}: {{projectDescription}}",
"SECTION_NAME": "待辦任務優先表",
"MOVE_US_TO_CURRENT_SPRINT": "移到目前的 Sprint",
"SHOW_FILTERS": "顯示過濾器",
@ -790,6 +948,8 @@
"VERSION_ERROR": "Taiga某人之前更改了這個而我們的工程師無法再做改變。請重新載入頁面來使用你的更新之前設定將消失"
},
"TASKBOARD": {
"PAGE_TITLE": "{{sprintName}} - Sprint taskboard - {{projectName}}",
"PAGE_DESCRIPTION": "Sprint {{sprintName}} (from {{startDate}} to {{endDate}}) of {{projectName}}. Completed {{completedPercentage}}% ({{completedPoints}} of {{totalPoints}} points). {{openTasks}} opened tasks of {{totalTasks}}.",
"SECTION_NAME": "任務板",
"TITLE_ACTION_ADD": "增加新任務 ",
"TITLE_ACTION_ADD_BULK": "批次加入新任務 ",
@ -813,6 +973,8 @@
}
},
"TASK": {
"PAGE_TITLE": "{{taskSubject}} - Task {{taskRef}} - {{projectName}}",
"PAGE_DESCRIPTION": "Status: {{taskStatus }}. Description: {{taskDescription}}",
"SECTION_NAME": "任務細節",
"LINK_TASKBOARD": "任務板",
"TITLE_LINK_TASKBOARD": "到任務板去",
@ -858,56 +1020,9 @@
"ACTION_CHANGE_EMAIL": "變更電郵地址",
"SUCCESS": "我們系統已更新你的電子郵件"
},
"CHANGE_PASSWORD_RECOVERY_FORM": {
"TITLE": "創建新Taiga通過",
"SUBTITLE": "你也許該吃點鐵質豐富的食物,它對你的大腦有益處:p",
"PLACEHOLDER_RECOVER_PASSWORD_TOKEN": "回復密碼代碼 ",
"LINK_NEED_TOKEN": "需要一個?",
"TITLE_LINK_NEED_TOKEN": "你是否需要代碼來恢復你所忘記的密碼?",
"PLACEHOLDER_NEW_PASSWORD": "新密碼 ",
"PLACEHOLDER_RE_TYPE_NEW_PASSWORD": "重新輸入新密碼",
"ACTION_RESET_PASSWORD": "重設密碼 ",
"SUCCESS": "系統已儲存你的新密碼<br />試試 <strong>用它登入</strong> "
},
"FORGOT_PASSWORD_FORM": {
"TITLE": "你是否忘了密碼嗎",
"SUBTITLE": "輸入你的帳戶名稱或電子郵件以進行註冊",
"PLACEHOLDER_FIELD": "使用者名稱或電子郵件",
"ACTION_RESET_PASSWORD": "重設密碼 ",
"LINK_CANCEL": "不,請帶我回去,我記起來了",
"SUCCESS": "<strong>檢查你的收信箱</strong><br />我們送出了一封信<br />裏面有設定你新電子郵件的相關指示",
"ERROR": "按我們的記錄,你尚未註冊"
},
"LOGIN_COMMON": {
"HEADER": "我已登入Taiga",
"PLACEHOLDER_AUTH_NAME": "使用者名稱或電子郵件(注意大小寫)",
"LINK_FORGOT_PASSWORD": "忘記了?",
"TITLE_LINK_FORGOT_PASSWORD": "你忘了密碼嗎",
"ACTION_ENTER": "Enter",
"ACTION_SIGN_IN": "登入",
"PLACEHOLDER_AUTH_PASSWORD": "密碼(大小寫有別)"
},
"LOGIN_FORM": {
"ERROR_AUTH_INCORRECT": "按我們的箹統記錄,你的帳戶名稱/電子郵件或密碼並不正確 ",
"ERROR_GENERIC": "根據系統,此處有錯誤",
"SUCCESS": "我們系統歡迎你加入Taiga"
},
"INVITATION_LOGIN_FORM": {
"NOT_FOUND": "<strong>Ooops, 有點問題</strong><br />我們的系統無法找到你的邀請信",
"SUCCESS": "你成功地加入此專案,歡迎來到 {{project_name}}",
"ERROR": "按我們的記錄,你尚未註冊或是未輸入有效的密碼 "
},
"REGISTER_FORM": {
"TITLE": "註冊一個新的Taiga帳戶(免費 )",
"PLACEHOLDER_NAME": "選一個用戶帳號(注意大小寫)",
"PLACEHOLDER_FULL_NAME": "選取你的全名",
"PLACEHOLDER_EMAIL": "你的電子郵件",
"PLACEHOLDER_PASSWORD": "設定密碼(注意大小寫不同) ",
"ACTION_SIGN_UP": "註冊",
"TITLE_LINK_LOGIN": "登入",
"LINK_LOGIN": "你是否已註冊? 登入"
},
"ISSUES": {
"PAGE_TITLE": "Issues - {{projectName}}",
"PAGE_DESCRIPTION": "The issues list panel of the project {{projectName}}: {{projectDescription}}",
"LIST_SECTION_NAME": "問題 ",
"SECTION_NAME": "問題細節",
"ACTION_NEW_ISSUE": "+ 新問題 ",
@ -921,6 +1036,11 @@
"TITLE_NEXT_ISSUE": "下一個問題 ",
"ACTION_DELETE": "删除議題 ",
"LIGHTBOX_TITLE_BLOKING_ISSUE": "封鎖中的問題",
"FIELDS": {
"PRIORITY": "優先性",
"SEVERITY": "急迫性",
"TYPE": "類型"
},
"CONFIRM_PROMOTE": {
"TITLE": "將此問題提到使用者故事",
"MESSAGE": "你確定此問題要創建一個新的使用者故事?"
@ -966,7 +1086,13 @@
}
}
},
"ISSUE": {
"PAGE_TITLE": "{{issueSubject}} - Issue {{issueRef}} - {{projectName}}",
"PAGE_DESCRIPTION": "Status: {{issueStatus }}. Type: {{issueType}}, Priority: {{issuePriority}}. Severity: {{issueSeverity}}. Description: {{issueDescription}}"
},
"KANBAN": {
"PAGE_TITLE": "Kanban - {{projectName}}",
"PAGE_DESCRIPTION": "The kanban panel, with user stories of the project {{projectName}}: {{projectDescription}}",
"SECTION_NAME": "Kanban(看板)",
"TITLE_ACTION_FOLD": "隱藏欄位",
"TITLE_ACTION_UNFOLD": "未隱藏欄位",
@ -981,6 +1107,8 @@
"UNDO_ARCHIVED": "拖移 & 丟到未做"
},
"SEARCH": {
"PAGE_TITLE": "Search - {{projectName}}",
"PAGE_DESCRIPTION": "Search anything, user stories, issues, tasks or wiki pages, in the project {{projectName}}: {{projectDescription}}",
"FILTER_USER_STORIES": "使用者故事",
"FILTER_ISSUES": "問題 ",
"FILTER_TASKS": "任務 ",
@ -991,6 +1119,8 @@
"EMPTY_DESCRIPTION": "請試試上方某一個分頁或再搜尋一次"
},
"TEAM": {
"PAGE_TITLE": "Team - {{projectName}}",
"PAGE_DESCRIPTION": "The team panel to show all the members of the project {{projectName}}: {{projectDescription}}",
"SECTION_NAME": "團隊",
"APP_TITLE": "團隊- {{projectName}}",
"PLACEHOLDER_INPUT_SEARCH": "以全名搜尋",
@ -1011,16 +1141,6 @@
"CONFIRM_LEAVE_PROJECT": "你確定要退出這個專案嗎?",
"ACTION_LEAVE_PROJECT": "退出此專案"
},
"CHANGE_PASSWORD": {
"SECTION_NAME": "改變密碼 ",
"FIELD_CURRENT_PASSWORD": "現用密碼 ",
"PLACEHOLDER_CURRENT_PASSWORD": "你目前的密碼(如果你未有密碼,此處請空白)",
"FIELD_NEW_PASSWORD": "新密碼 ",
"PLACEHOLDER_NEW_PASSWORD": "輸入新密碼",
"FIELD_RETYPE_PASSWORD": "重新輸入新密碼",
"PLACEHOLDER_RETYPE_PASSWORD": "重新輸入新密碼",
"ERROR_PASSWORD_MATCH": "密碼不符"
},
"USER_SETTINGS": {
"AVATAR_MAX_SIZE": "[最大. 尺寸: {{maxFileSize}}] ",
"MENU": {
@ -1059,7 +1179,7 @@
"EMAIL": "電子郵件",
"FULL_NAME": "全名",
"PLACEHOLDER_FULL_NAME": "你的全名",
"BIO": "簡介",
"BIO": "Bio (max. 210 chars)",
"PLACEHOLDER_BIO": "請自我介紹",
"LANGUAGE": "語言",
"LANGUAGE_DEFAULT": "-- 使用設預語言 -- "
@ -1074,6 +1194,8 @@
"PROGRESS_NAME_DESCRIPTION": "名稱與描述"
},
"WIKI": {
"PAGE_TITLE": "{{wikiPageName}} - Wiki - {{projectName}}",
"PAGE_DESCRIPTION": "Last edition on {{lastModifiedDate}} ({{totalEditions}} editions in total) Content: {{ wikiPageContent }}",
"DATETIME": "DD MMM YYYY HH:mm",
"PLACEHOLDER_PAGE": "編寫你的維基頁",
"REMOVE": "移除此維基頁 ",
@ -1087,5 +1209,43 @@
"LAST_EDIT": "上次 <br />編輯 ",
"LAST_MODIFICATION": "上回修改"
}
},
"HINTS": {
"SECTION_NAME": "Hint",
"LINK": "If you want to know how to use it visit our support page",
"LINK_TITLE": "Visit our support page",
"HINT1_TITLE": "Did you know you can import and export projects?",
"HINT1_TEXT": "This allow you to extract all your data from one Taiga and move it to another one.",
"HINT2_TITLE": "Did you know you can create custom fields?",
"HINT2_TEXT": "Teams can now create custom fields as a flexible means to enter specific data useful for their particular workflow",
"HINT3_TITLE": "Reorder your projects to feature those most relevant to you",
"HINT3_TEXT": "The top 10 project will be in your top bar direct access",
"HINT4_TITLE": "Did you forgot what were you working on?",
"HINT4_TEXT": "Don't worry, on your dashboard you'll find your open tasks, issues, and user stories in the order you worked on them."
},
"TIMELINE": {
"UPLOAD_ATTACHMENT": "{{username}} has uploaded a new attachment in {{obj_name}}",
"US_CREATED": "{{username}} has created a new US {{obj_name}} in {{project_name}}",
"ISSUE_CREATED": "{{username}} has created a new issue {{obj_name}} in {{project_name}}",
"TASK_CREATED": "{{username}} has created a new task {{obj_name}} in {{project_name}}",
"TASK_CREATED_WITH_US": "{{username}} has created a new task {{obj_name}} in {{project_name}} which belongs to the US {{us_name}}",
"WIKI_CREATED": "{{username}} has created a new wiki page {{obj_name}} in {{project_name}}",
"MILESTONE_CREATED": "{{username}} has created a new sprint {{obj_name}} in {{project_name}}",
"NEW_PROJECT": "{{username}} created the project {{project_name}}",
"MILESTONE_UPDATED": "{{username}} has updated the sprint {{obj_name}}",
"US_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the US {{obj_name}}",
"ISSUE_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the issue {{obj_name}}",
"TASK_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}}",
"TASK_UPDATED_WITH_US": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}} which belongs to the US {{us_name}}",
"WIKI_UPDATED": "{{username}} has updated the wiki page {{obj_name}}",
"NEW_COMMENT_US": "{{username}} has commented in the US {{obj_name}}",
"NEW_COMMENT_ISSUE": "{{username}} has commented in the issue {{obj_name}}",
"NEW_COMMENT_TASK": "{{username}} has commented in the task {{obj_name}}",
"NEW_MEMBER": "{{project_name}} has a new member",
"US_ADDED_MILESTONE": "{{username}} has added the US {{obj_name}} to {{sprint_name}}",
"US_REMOVED_FROM_MILESTONE": "{{username}} has added the US {{obj_name}} to the backlog",
"BLOCKED": "{{username}} has blocked {{obj_name}}",
"UNBLOCKED": "{{username}} has unblocked {{obj_name}}",
"NEW_USER": "{{username}} has joined Taiga"
}
}

View File

@ -0,0 +1,94 @@
class ProjectMenuController
@.$inject = [
"tgProjectService",
"tgLightboxFactory"
]
constructor: (@projectService, @lightboxFactory) ->
@.project = null
@.menu = Immutable.Map()
show: () ->
@.project = @projectService.project
@.active = @._getActiveSection()
@._setVideoConference()
@._setMenuPermissions()
hide: () ->
@.project = null
@.menu = {}
search: () ->
@lightboxFactory.create("tg-search-box", {
"class": "lightbox lightbox-search"
})
_setVideoConference: () ->
videoconferenceUrl = @._videoConferenceUrl()
if videoconferenceUrl
@.project = @.project.set("videoconferenceUrl", videoconferenceUrl)
_setMenuPermissions: () ->
@.menu = Immutable.Map({
backlog: false,
kanban: false,
issues: false,
wiki: false
})
if @.project.get("is_backlog_activated") && @.project.get("my_permissions").indexOf("view_us") != -1
@.menu = @.menu.set("backlog", true)
if @.project.get("is_kanban_activated") && @.project.get("my_permissions").indexOf("view_us") != -1
@.menu = @.menu.set("kanban", true)
if @.project.get("is_issues_activated") && @.project.get("my_permissions").indexOf("view_issues") != -1
@.menu = @.menu.set("issues", true)
if @.project.get("is_wiki_activated") && @.project.get("my_permissions").indexOf("view_wiki_pages") != -1
@.menu = @.menu.set("wiki", true)
_getActiveSection: () ->
sectionName = @projectService.section
sectionsBreadcrumb = @projectService.sectionsBreadcrumb
indexBacklog = sectionsBreadcrumb.lastIndexOf("backlog")
indexKanban = sectionsBreadcrumb.lastIndexOf("kanban")
if indexBacklog != -1 || indexKanban != -1
if indexKanban == -1 || indexBacklog < indexKanban
oldSectionName = "backlog"
else
oldSectionName = "kanban"
if sectionName == "backlog-kanban"
if oldSectionName in ["backlog", "kanban"]
sectionName = oldSectionName
else if @.project.get("is_backlog_activated") && !@.project.get("is_kanban_activated")
sectionName = "backlog"
else if !@.project.get("is_backlog_activated") && @.project.get("is_kanban_activated")
sectionName = "kanban"
return sectionName
_videoConferenceUrl: () ->
if @.project.get("videoconferences") == "appear-in"
baseUrl = "https://appear.in/"
else if @.project.get("videoconferences") == "talky"
baseUrl = "https://talky.io/"
else if @.project.get("videoconferences") == "jitsi"
baseUrl = "https://meet.jit.si/"
else
return ""
if @.project.get("videoconferences_salt")
url = @.project.get("slug") + "-" + @.project.get("videoconferences_salt")
else
url = @.project.get("slug")
return baseUrl + url
angular.module("taigaComponents").controller("ProjectMenu", ProjectMenuController)

View File

@ -0,0 +1,248 @@
describe "ProjectMenu", ->
$provide = null
$controller = null
mocks = {}
_mockProjectService = ->
mocks.projectService = {}
$provide.value("tgProjectService", mocks.projectService)
_mockLightboxFactory = ->
mocks.lightboxFactory = {
create: sinon.spy()
}
$provide.value("tgLightboxFactory", mocks.lightboxFactory)
_mocks = ->
module (_$provide_) ->
$provide = _$provide_
_mockProjectService()
_mockLightboxFactory()
return null
_inject = ->
inject (_$controller_) ->
$controller = _$controller_
_setup = ->
_mocks()
_inject()
beforeEach ->
module "taigaComponents"
_setup()
it "open search lightbox", () ->
ctrl = $controller("ProjectMenu")
ctrl.search()
expectation = mocks.lightboxFactory.create.calledWithExactly("tg-search-box", {
"class": "lightbox lightbox-search"
})
expect(expectation).to.be.true
describe "show menu", ->
it "project filled", () ->
project = Immutable.fromJS({})
mocks.projectService.project = project
mocks.projectService.sectionsBreadcrumb = Immutable.List()
ctrl = $controller("ProjectMenu")
ctrl.show()
expect(ctrl.project).to.be.equal(project)
it "videoconference url", () ->
project = Immutable.fromJS({
"videoconferences": "appear-in",
"videoconferences_salt": "123",
"slug": "project-slug"
})
mocks.projectService.project = project
mocks.projectService.sectionsBreadcrumb = Immutable.List()
ctrl = $controller("ProjectMenu")
ctrl.show()
url = "https://appear.in/project-slug-123"
expect(ctrl.project.get("videoconferenceUrl")).to.be.equal(url)
describe "menu permissions", () ->
it "all options disable", () ->
project = Immutable.fromJS({})
mocks.projectService.project = project
mocks.projectService.sectionsBreadcrumb = Immutable.List()
ctrl = $controller("ProjectMenu")
ctrl.show()
menu = ctrl.menu.toJS()
expect(menu).to.be.eql({
backlog: false,
kanban: false,
issues: false,
wiki: false
})
it "all options enabled", () ->
project = Immutable.fromJS({
is_backlog_activated: true,
is_kanban_activated: true,
is_issues_activated: true,
is_wiki_activated: true,
my_permissions: ["view_us", "view_issues", "view_wiki_pages"]
})
mocks.projectService.project = project
mocks.projectService.sectionsBreadcrumb = Immutable.List()
ctrl = $controller("ProjectMenu")
ctrl.show()
menu = ctrl.menu.toJS()
expect(menu).to.be.eql({
backlog: true,
kanban: true,
issues: true,
wiki: true
})
it "all options disabled because the user doesn't have permissions", () ->
project = Immutable.fromJS({
is_backlog_activated: true,
is_kanban_activated: true,
is_issues_activated: true,
is_wiki_activated: true,
my_permissions: []
})
mocks.projectService.project = project
mocks.projectService.sectionsBreadcrumb = Immutable.List()
ctrl = $controller("ProjectMenu")
ctrl.show()
menu = ctrl.menu.toJS()
expect(menu).to.be.eql({
backlog: false,
kanban: false,
issues: false,
wiki: false
})
describe "menu active", () ->
it "backlog", () ->
project = Immutable.fromJS({})
mocks.projectService.project = project
mocks.projectService.section = "backlog"
mocks.projectService.sectionsBreadcrumb = Immutable.List()
ctrl = $controller("ProjectMenu")
ctrl.show()
expect(ctrl.active).to.be.equal("backlog")
it "backlog-kanban without parent", () ->
project = Immutable.fromJS({})
mocks.projectService.project = project
mocks.projectService.section = "backlog-kanban"
mocks.projectService.sectionsBreadcrumb = Immutable.List()
ctrl = $controller("ProjectMenu")
ctrl.show()
expect(ctrl.active).to.be.equal("backlog-kanban")
it "backlog-kanban when only kanban is enabled", () ->
project = Immutable.fromJS({
is_kanban_activated: true,
my_permissions: []
})
mocks.projectService.project = project
mocks.projectService.section = "backlog-kanban"
mocks.projectService.sectionsBreadcrumb = Immutable.List()
ctrl = $controller("ProjectMenu")
ctrl.show()
expect(ctrl.active).to.be.equal("kanban")
it "backlog-kanban when only backlog is enabled", () ->
project = Immutable.fromJS({
is_backlog_activated: true,
my_permissions: []
})
mocks.projectService.project = project
mocks.projectService.section = "backlog-kanban"
mocks.projectService.sectionsBreadcrumb = Immutable.List()
ctrl = $controller("ProjectMenu")
ctrl.show()
expect(ctrl.active).to.be.equal("backlog")
it "backlog-kanban when is child of kanban", () ->
project = Immutable.fromJS({})
mocks.projectService.project = project
mocks.projectService.section = "backlog-kanban"
mocks.projectService.sectionsBreadcrumb = Immutable.List.of("oo", "backlog", "kanban")
ctrl = $controller("ProjectMenu")
ctrl.show()
expect(ctrl.active).to.be.equal("kanban")
it "backlog-kanban when is child of backlog", () ->
project = Immutable.fromJS({})
mocks.projectService.project = project
mocks.projectService.section = "backlog-kanban"
mocks.projectService.sectionsBreadcrumb = Immutable.List.of("kanban", "oo", "backlog")
ctrl = $controller("ProjectMenu")
ctrl.show()
expect(ctrl.active).to.be.equal("backlog")
it "backlog-kanban when kanban is not in the breadcrumb", () ->
project = Immutable.fromJS({})
mocks.projectService.project = project
mocks.projectService.section = "backlog-kanban"
mocks.projectService.sectionsBreadcrumb = Immutable.List.of("oo", "backlog")
ctrl = $controller("ProjectMenu")
ctrl.show()
expect(ctrl.active).to.be.equal("backlog")

View File

@ -0,0 +1,28 @@
taiga = @.taiga
ProjectMenuDirective = (projectService, lightboxFactory) ->
link = (scope, el, attrs, ctrl) ->
projectChange = () ->
if projectService.project
ctrl.show()
else
ctrl.hide()
scope.$watch ( () ->
return projectService.project
), projectChange
return {
scope: {},
controller: "ProjectMenu",
controllerAs: "vm",
templateUrl: "components/project-menu/project-menu.html",
link: link
}
ProjectMenuDirective.$inject = [
"tgProjectService",
"tgLightboxFactory"
]
angular.module("taigaComponents").directive("tgProjectMenu", ProjectMenuDirective)

View File

@ -0,0 +1,47 @@
nav.menu(ng-if="vm.project")
div(class="menu-container")
ul(class="main-nav")
li(id="nav-search")
a(href="", ng-class="{active: vm.active == 'search'}", title="{{'PROJECT.SECTION.SEARCH' | translate}}", tabindex="1", ng-click="vm.search()")
span(class="icon icon-search")
span.helper(translate="PROJECT.SECTION.SEARCH")
li(id="nav-timeline")
a(ng-class="{active: vm.active == 'project-timeline'}", title="{{'PROJECT.SECTION.TIMELINE' | translate}}", tg-nav="project:project=vm.project.get('slug')", tabindex="2")
include ../../../svg/timeline.svg
span.helper(translate="PROJECT.SECTION.TIMELINE")
li(id="nav-backlog", ng-if="vm.menu.get('backlog')")
a(ng-class="{active: vm.active == 'backlog'}", title="{{'PROJECT.SECTION.BACKLOG' | translate}}", tg-nav="project-backlog:project=vm.project.get('slug')", tabindex="2")
span(class="icon icon-backlog")
span.helper(translate="PROJECT.SECTION.BACKLOG")
li(id="nav-kanban", ng-if="vm.menu.get('kanban')")
a(ng-class="{active: vm.active == 'kanban'}", title="{{'PROJECT.SECTION.KANBAN' | translate}}", tg-nav="project-kanban:project=vm.project.get('slug')", tabindex="3")
span(class="icon icon-kanban")
span.helper(translate="PROJECT.SECTION.KANBAN")
li(id="nav-issues", ng-if="vm.menu.get('issues')")
a(ng-class="{active: vm.active == 'issues'}", title="{{'PROJECT.SECTION.ISSUES' | translate}}", tg-nav="project-issues:project=vm.project.get('slug')", tabindex="4")
span(class="icon icon-issues")
span.helper(translate="PROJECT.SECTION.ISSUES")
li(id="nav-wiki", ng-if="vm.menu.get('wiki')")
a(ng-class="{active: vm.active == 'wiki'}", title="{{'PROJECT.SECTION.WIKI' | translate}}", tg-nav="project-wiki:project=vm.project.get('slug')", tabindex="5")
span(class="icon icon-wiki")
span.helper(translate="PROJECT.SECTION.WIKI")
li(id="nav-team")
a(ng-class="{active: vm.active == 'team'}", title="{{'PROJECT.SECTION.TEAM' | translate}}", tg-nav="project-team:project=vm.project.get('slug')", tabindex="6")
span(class="icon icon-team")
span.helper(translate="PROJECT.SECTION.TEAM")
li(id="nav-video", ng-if="vm.project.get('videoconferenceUrl')")
a(ng-href="{{vm.project.get('videoconferenceUrl')}}", target="_blank", title="{{'PROJECT.SECTION.MEETUP' | translate}}", tabindex="7")
span(class="icon icon-video")
span.helper(translate="PROJECT.SECTION.MEETUP")
li(id="nav-admin", ng-if="vm.project.get('i_am_owner')")
a(ng-class="{active: vm.active == 'admin'}", tg-nav="project-admin-home:project=vm.project.get('slug')", title="{{'PROJECT.SECTION.ADMIN' | translate}}", tabindex="8")
span(class="icon icon-settings")
span.helper(translate="PROJECT.SECTION.ADMIN")

View File

@ -0,0 +1,11 @@
class FeedbackService extends taiga.Service
@.$inject = ["tgLightboxFactory"]
constructor: (@lightboxFactory) ->
sendFeedback: ->
@lightboxFactory.create("tg-lb-feedback", {
"class": "lightbox lightbox-feedback lightbox-generic-form"
})
angular.module("taigaFeedback").service("tgFeedbackService", FeedbackService)

View File

@ -0,0 +1,38 @@
describe "tgFeedbackService", ->
feedbackService = provide = null
mocks = {}
_mockTgLightboxFactory = () ->
mocks.tgLightboxFactory = {
create: sinon.stub()
}
provide.value "tgLightboxFactory", mocks.tgLightboxFactory
_inject = (callback) ->
inject (_tgFeedbackService_) ->
feedbackService = _tgFeedbackService_
callback() if callback
_mocks = () ->
module ($provide) ->
provide = $provide
_mockTgLightboxFactory()
return null
_setup = ->
_mocks()
beforeEach ->
module "taigaFeedback"
_setup()
_inject()
it "work in progress filled", () ->
expect(mocks.tgLightboxFactory.create.callCount).to.be.equal(0)
feedbackService.sendFeedback()
expect(mocks.tgLightboxFactory.create.callCount).to.be.equal(1)
params = {
"class": "lightbox lightbox-feedback lightbox-generic-form"
}
expect(mocks.tgLightboxFactory.create.calledWith("tg-lb-feedback", params)).to.be.true()

View File

@ -0,0 +1,28 @@
DutyDirective = (navurls, $translate) ->
link = (scope, el, attrs, ctrl) ->
scope.vm = {}
scope.vm.duty = scope.duty
scope.vm.getDutyType = () ->
if scope.vm.duty
if scope.vm.duty.get('_name') == "userstories"
return $translate.instant("COMMON.USER_STORY")
if scope.vm.duty.get('_name') == "tasks"
return $translate.instant("COMMON.TASK")
if scope.vm.duty.get('_name') == "issues"
return $translate.instant("COMMON.ISSUE")
return {
templateUrl: "home/duties/duty.html"
scope: {
"duty": "=tgDuty"
}
link: link
}
DutyDirective.$inject = [
"$tgNavUrls",
"$translate"
]
angular.module("taigaHome").directive("tgDuty", DutyDirective)

View File

@ -0,0 +1,82 @@
describe "dutyDirective", () ->
scope = compile = provide = null
mockTgProjectsService = null
mockTgNavUrls = null
mockTranslate = null
template = "<div tg-duty='duty'></div>"
createDirective = () ->
elm = compile(template)(scope)
return elm
_mockTgNavUrls = () ->
mockTgNavUrls = {
resolve: sinon.stub()
}
provide.value "$tgNavUrls", mockTgNavUrls
_mockTranslateFilter = () ->
mockTranslateFilter = (value) ->
return value
provide.value "translateFilter", mockTranslateFilter
_mockTgProjectsService = () ->
mockTgProjectsService = {
projectsById: {
get: sinon.stub()
}
}
provide.value "tgProjectsService", mockTgProjectsService
_mockTranslate = () ->
mockTranslate = {
instant: sinon.stub()
}
provide.value "$translate", mockTranslate
_mocks = () ->
module ($provide) ->
provide = $provide
_mockTgNavUrls()
_mockTgProjectsService()
_mockTranslate()
_mockTranslateFilter()
return null
beforeEach ->
module "templates"
module "taigaHome"
_mocks()
inject ($rootScope, $compile) ->
scope = $rootScope.$new()
compile = $compile
it "duty directive scope content", () ->
scope.duty = Immutable.fromJS({
project: 1
ref: 1
_name: "userstories"
assigned_to_extra_info: {
photo: "http://jstesting.taiga.io/photo"
full_name_display: "Taiga testing js"
}
})
mockTgProjectsService.projectsById.get
.withArgs("1")
.returns({slug: "project-slug", "name": "testing js project"})
mockTgNavUrls.resolve
.withArgs("project-userstories-detail", {project: "project-slug", ref: 1})
.returns("http://jstesting.taiga.io")
mockTranslate.instant
.withArgs("COMMON.USER_STORY")
.returns("User story translated")
elm = createDirective()
scope.$apply()
expect(elm.isolateScope().vm.getDutyType()).to.be.equal("User story translated")

View File

@ -0,0 +1,18 @@
a(href="{{ ::vm.duty.get('url') }}", title="{{ ::duty.get('subject') }}")
img.avatar(ng-if="::vm.duty.get('assigned_to_extra_info')"
ng-src="{{ ::vm.duty.get('assigned_to_extra_info').get('photo') }}"
title="{{ ::vm.duty.get('assigned_to_extra_info').get('full_name_display') }}")
img.avatar(ng-if="::!vm.duty.get('assigned_to_extra_info')"
src="/images/unnamed.png"
title="{{'ACTIVITY.VALUES.UNASSIGNED' | translate}}")
div.duty-data
div
span.duty-type {{ ::vm.getDutyType() }}
span.duty-status(ng-style="{'color': vm.duty.get('status_extra_info').get('color')}") {{ ::vm.duty.get('status_extra_info').get('name') }}
span.duty-title
span.duty-id(tg-bo-ref="duty.get('ref')")
span.duty-name {{ ::duty.get('subject') }}
div.duty-project {{ ::vm.duty.get('projectName')}}

View File

@ -0,0 +1,75 @@
.working-on,
.watching {
margin-bottom: 2rem;
.duty-single {
border-bottom: 1px solid $whitish;
cursor: pointer;
padding: .5rem;
&:last-child {
border: 0;
}
&.blocked {
background: rgba($red-light, .2);
.duty-type,
.duty-status {
color: $red;
}
}
>a {
align-items: center;
display: flex;
flex-direction: row;
}
}
.avatar {
flex-basis: 47px;
height: 47px;
margin-right: .5rem;
width: 47px;
}
.duty-data {
flex: 1;
margin-right: .5rem;
}
.duty-type,
.duty-status {
@extend %small;
color: $gray;
margin-right: .3rem;
}
.duty-title {
display: block;
margin-top: .25rem;
}
.duty-id {
color: $gray-light;
margin-right: .3rem;
}
.duty-project {
@extend %small;
align-self: flex-start;
color: $gray-light;
margin-left: auto;
text-align: right;
width: 120px;
}
.see-more {
display: block;
margin: 2rem 30%;
}
}
.watching-empty {
padding: 5vh;
text-align: center;
svg {
margin: 2rem auto;
max-width: 160px;
text-align: center;
path {
fill: $whitish;
}
}
p {
@extend %small;
}
}

View File

@ -0,0 +1,8 @@
doctype html
include ../../partials/includes/components/beta
div.home-wrapper.centered
div.duty-summary
div(tg-working-on)
aside.project-list(tg-home-project-list)

View File

@ -0,0 +1 @@
module = angular.module("taigaHome", [])

View File

@ -0,0 +1,23 @@
.home-wrapper {
display: flex;
padding-top: 2rem;
.duty-summary {
flex: 1;
margin-right: 2rem;
}
.project-list {
width: 250px;
}
.see-more-projects-btn {
display: block;
}
.title-bar {
@extend %title;
@extend %larger;
align-content: center;
background: $whitish;
display: flex;
margin: 0 0 .5rem;
padding: .9rem 1rem;
}
}

View File

@ -0,0 +1,140 @@
groupBy = @.taiga.groupBy
class HomeService extends taiga.Service
@.$inject = [
"$tgNavUrls",
"tgResources",
"tgProjectsService"
]
constructor: (@navurls, @rs, @projectsService) ->
_attachProjectInfoToWorkInProgress: (workInProgress, projectsById) ->
_attachProjectInfoToDuty = (duty, objType) =>
project = projectsById.get(String(duty.get('project')))
ctx = {
project: project.get('slug')
ref: duty.get('ref')
}
url = @navurls.resolve("project-#{objType}-detail", ctx)
duty = duty.set('url', url)
duty = duty.set('projectName', project.get('name'))
duty = duty.set("_name", objType)
return duty
assignedTo = workInProgress.get("assignedTo")
if assignedTo.get("userStories")
_duties = assignedTo.get("userStories").map (duty) ->
return _attachProjectInfoToDuty(duty, "userstories")
assignedTo = assignedTo.set("userStories", _duties)
if assignedTo.get("tasks")
_duties = assignedTo.get("tasks").map (duty) ->
return _attachProjectInfoToDuty(duty, "tasks")
assignedTo = assignedTo.set("tasks", _duties)
if assignedTo.get("issues")
_duties = assignedTo.get("issues").map (duty) ->
return _attachProjectInfoToDuty(duty, "issues")
assignedTo = assignedTo.set("issues", _duties)
watching = workInProgress.get("watching")
if watching.get("userStories")
_duties = watching.get("userStories").map (duty) ->
return _attachProjectInfoToDuty(duty, "userstories")
watching = watching.set("userStories", _duties)
if watching.get("tasks")
_duties = watching.get("tasks").map (duty) ->
return _attachProjectInfoToDuty(duty, "tasks")
watching = watching.set("tasks", _duties)
if watching.get("issues")
_duties = watching.get("issues").map (duty) ->
return _attachProjectInfoToDuty(duty, "issues")
watching = watching.set("issues", _duties)
workInProgress = workInProgress.set("assignedTo", assignedTo)
workInProgress = workInProgress.set("watching", watching)
getWorkInProgress: (userId) ->
projectsById = Immutable.Map()
projectsPromise = @projectsService.getProjectsByUserId(userId).then (projects) ->
projectsById = Immutable.fromJS(groupBy(projects.toJS(), (p) -> p.id))
assignedTo = Immutable.Map()
params = {
status__is_closed: false
assigned_to: userId
}
params_us = {
is_closed: false
assigned_to: userId
}
assignedUserStoriesPromise = @rs.userstories.listInAllProjects(params_us).then (userstories) ->
assignedTo = assignedTo.set("userStories", userstories)
assignedTasksPromise = @rs.tasks.listInAllProjects(params).then (tasks) ->
assignedTo = assignedTo.set("tasks", tasks)
assignedIssuesPromise = @rs.issues.listInAllProjects(params).then (issues) ->
assignedTo = assignedTo.set("issues", issues)
params = {
status__is_closed: false
watchers: userId
}
params_us = {
is_closed: false
watchers: userId
}
watching = Immutable.Map()
watchingUserStoriesPromise = @rs.userstories.listInAllProjects(params_us).then (userstories) ->
watching = watching.set("userStories", userstories)
watchingTasksPromise = @rs.tasks.listInAllProjects(params).then (tasks) ->
watching = watching.set("tasks", tasks)
watchingIssuesPromise = @rs.issues.listInAllProjects(params).then (issues) ->
watching = watching.set("issues", issues)
workInProgress = Immutable.Map()
Promise.all([
projectsPromise
assignedUserStoriesPromise,
assignedTasksPromise,
assignedIssuesPromise,
watchingUserStoriesPromise,
watchingTasksPromise,
watchingIssuesPromise
]).then =>
workInProgress = workInProgress.set("assignedTo", assignedTo)
workInProgress = workInProgress.set("watching", watching)
workInProgress = @._attachProjectInfoToWorkInProgress(workInProgress, projectsById)
return workInProgress
angular.module("taigaHome").service("tgHomeService", HomeService)

View File

@ -0,0 +1,143 @@
describe "tgHome", ->
homeService = provide = null
mocks = {}
_mockResources = () ->
mocks.resources = {}
mocks.resources.userstories = {}
mocks.resources.tasks = {}
mocks.resources.issues = {}
mocks.resources.userstories.listInAllProjects = sinon.stub()
mocks.resources.tasks.listInAllProjects = sinon.stub()
mocks.resources.issues.listInAllProjects = sinon.stub()
provide.value "tgResources", mocks.resources
_mockTgNavUrls = () ->
mocks.tgNavUrls = {
resolve: sinon.stub()
}
provide.value "$tgNavUrls", mocks.tgNavUrls
_mockProjectsService = () ->
mocks.projectsService = {
getProjectsByUserId: sinon.stub().promise()
}
provide.value "tgProjectsService", mocks.projectsService
_inject = (callback) ->
inject (_tgHomeService_) ->
homeService = _tgHomeService_
callback() if callback
_mocks = () ->
module ($provide) ->
provide = $provide
_mockResources()
_mockTgNavUrls()
_mockProjectsService()
return null
_setup = ->
_mocks()
beforeEach ->
module "taigaHome"
_setup()
_inject()
it "get work in progress by user", (done) ->
userId = 3
mocks.projectsService.getProjectsByUserId
.withArgs(userId)
.resolve(Immutable.fromJS([
{id: 1, name: "fake1", slug: "project-1"},
{id: 2, name: "fake2", slug: "project-2"}
]))
mocks.resources.userstories.listInAllProjects.promise()
.resolve(Immutable.fromJS([{id: 1, ref: 1, project: "1"}]))
mocks.resources.tasks.listInAllProjects.promise()
.resolve(Immutable.fromJS([{id: 2, ref: 2, project: "1"}]))
mocks.resources.issues.listInAllProjects.promise()
.resolve(Immutable.fromJS([{id: 3, ref: 3, project: "1"}]))
# mock urls
mocks.tgNavUrls.resolve
.withArgs("project-userstories-detail", {project: "project-1", ref: 1})
.returns("/testing-project/us/1")
mocks.tgNavUrls.resolve
.withArgs("project-tasks-detail", {project: "project-1", ref: 2})
.returns("/testing-project/tasks/1")
mocks.tgNavUrls.resolve
.withArgs("project-issues-detail", {project: "project-1", ref: 3})
.returns("/testing-project/issues/1")
homeService.getWorkInProgress(userId)
.then (workInProgress) ->
expect(workInProgress.toJS()).to.be.eql({
assignedTo: {
userStories: [{
id: 1,
ref: 1,
project: '1',
url: '/testing-project/us/1',
projectName: 'fake1',
_name: 'userstories'
}]
tasks: [{
id: 2,
ref: 2,
project: '1',
url: '/testing-project/tasks/1',
projectName: 'fake1',
_name: 'tasks'
}]
issues: [{
id: 3,
ref: 3,
project: '1',
url: '/testing-project/issues/1',
projectName: 'fake1',
_name: 'issues'
}]
}
watching: {
userStories: [{
id: 1,
ref: 1,
project: '1',
url: '/testing-project/us/1',
projectName: 'fake1',
_name: 'userstories'
}]
tasks: [{
id: 2,
ref: 2,
project: '1',
url: '/testing-project/tasks/1',
projectName: 'fake1',
_name: 'tasks'
}]
issues: [{
id: 3,
ref: 3,
project: '1',
url: '/testing-project/issues/1',
projectName: 'fake1',
_name: 'issues'
}]
}
})
done()

View File

@ -0,0 +1,73 @@
describe "homeProjectListDirective", () ->
scope = compile = provide = null
mocks = {}
template = "<div tg-home-project-list></div>"
projects = Immutable.fromJS({
recents: [
{id: 1},
{id: 2},
{id: 3}
]
})
createDirective = () ->
elm = compile(template)(scope)
return elm
_mockTgCurrentUserService = () ->
mocks.currentUserService = {
projects: projects
}
provide.value "tgCurrentUserService", mocks.currentUserService
_mockTgProjectsService = () ->
mocks.projectsService = {
newProject: sinon.stub()
}
provide.value "tgProjectsService", mocks.projectsService
_mockTranslateFilter = () ->
mockTranslateFilter = (value) ->
return value
provide.value "translateFilter", mockTranslateFilter
_mocks = () ->
module ($provide) ->
provide = $provide
_mockTgCurrentUserService()
_mockTgProjectsService()
_mockTranslateFilter()
return null
beforeEach ->
module "templates"
module "taigaHome"
_mocks()
inject ($rootScope, $compile) ->
scope = $rootScope.$new()
compile = $compile
recents = Immutable.fromJS([
{
id:1
},
{
id: 2
}
])
it "home project list directive scope content", () ->
elm = createDirective()
scope.$apply()
expect(elm.isolateScope().vm.projects.size).to.be.equal(3)
it "home project list directive newProject", () ->
elm = createDirective()
scope.$apply()
expect(mocks.projectsService.newProject.callCount).to.be.equal(0)
elm.isolateScope().vm.newProject()
expect(mocks.projectsService.newProject.callCount).to.be.equal(1)

View File

@ -0,0 +1,23 @@
HomeProjectListDirective = (currentUserService, projectsService) ->
link = (scope, el, attrs, ctrl) ->
scope.vm = {}
taiga.defineImmutableProperty(scope.vm, "projects", () -> currentUserService.projects.get("recents"))
scope.vm.newProject = ->
projectsService.newProject()
directive = {
templateUrl: "home/projects/home-project-list.html"
scope: {}
link: link
}
return directive
HomeProjectListDirective.$inject = [
"tgCurrentUserService",
"tgProjectsService"
]
angular.module("taigaHome").directive("tgHomeProjectList", HomeProjectListDirective)

View File

@ -0,0 +1,27 @@
ul.home-project-list(ng-show="vm.projects.size")
li.home-project-list-single(tg-bind-scope, tg-repeat="project in vm.projects")
a(href="#", tg-nav="project:project=project.get('slug')")
h2.home-project-list-single-title
span.project-name(title="{{ ::project.get('name') }}") {{::project.get('name')}}
span.private(ng-if="project.get('is_private')", title="{{'PROJECT.PRIVATE' | translate}}")
include ../../../svg/lock.svg
p {{ ::project.get('description') | limitTo:150 }}
span(ng-if="::project.get('description').size > 150") ...
a.see-more-projects-btn.button-gray(href="#",
ng-show="vm.projects.size",
tg-nav="projects",
title="{{'PROJECT.NAVIGATION.SEE_MORE_PROJECTS' | translate}}",
translate="PROJECT.NAVIGATION.SEE_MORE_PROJECTS")
section.projects-empty(ng-hide="vm.projects.size")
include ../../../svg/empty-project.svg
p(translate="HOME.EMPTY_PROJECT_LIST")
a.create-project-button.button-green(href="#", ng-click="vm.newProject()",
title="{{'PROJECT.NAVIGATION.TITLE_CREATE_PROJECT' | translate}}",
translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT")
span(tg-import-project-button)
a.import-project-button.button-blackish(href="#",
title="{{'PROJECT.NAVIGATION.TITLE_IMPORT_PROJECT' | translate}}",
translate="PROJECT.NAVIGATION.ACTION_IMPORT_PROJECT")
input.import-file.hidden(type="file")

View File

@ -0,0 +1,74 @@
.home-project-list {
li {
border: 1px solid lighten($gray-light, 15%);
border-radius: 3px;
cursor: pointer;
margin-bottom: .75rem;
padding: 1rem;
text-overflow: ellipsis;
&:hover {
border-color: $fresh-taiga;
transition: all .3s linear;
p {
color: $gray;
transition: color .3s linear;
}
.private path {
fill: $gray;
transition: fill .3s linear;
}
}
}
a {
display: flex;
flex-direction: column;
min-height: 5rem;
}
h2 {
@extend %text;
color: $gray;
font-size: 1.5rem;
line-height: 1.3;
margin-bottom: .5rem;
text-transform: none;
.project-name {
display: inline-block;
max-width: 90%;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
white-space: nowrap;
}
}
p {
@extend %text;
@extend %xsmall;
color: $gray-light;
line-height: 125%;
margin: 0;
word-wrap: break-word;
}
}
.projects-empty {
text-align: center;
svg {
height: 100px;
margin: 1rem auto;
text-align: center;
width: 100%;
path {
fill: $whitish;
}
}
p {
@extend %small;
}
.create-project-button {
display: block;
margin-bottom: .25rem;
}
.import-project-button {
display: block;
}
}

View File

@ -0,0 +1,33 @@
class WorkingOnController
@.$inject = [
"tgHomeService"
]
constructor: (@homeService) ->
@.assignedTo = Immutable.Map()
@.watching = Immutable.Map()
_setAssignedTo: (workInProgress) ->
userStories = workInProgress.get("assignedTo").get("userStories")
tasks = workInProgress.get("assignedTo").get("tasks")
issues = workInProgress.get("assignedTo").get("issues")
@.assignedTo = userStories.concat(tasks).concat(issues)
if @.assignedTo.size > 0
@.assignedTo = @.assignedTo.sortBy((elem) -> elem.get("modified_date")).reverse()
_setWatching: (workInProgress) ->
userStories = workInProgress.get("watching").get("userStories")
tasks = workInProgress.get("watching").get("tasks")
issues = workInProgress.get("watching").get("issues")
@.watching = userStories.concat(tasks).concat(issues)
if @.watching.size > 0
@.watching = @.watching.sortBy((elem) -> elem.get("modified_date")).reverse()
getWorkInProgress: (userId) ->
return @homeService.getWorkInProgress(userId).then (workInProgress) =>
@._setAssignedTo(workInProgress)
@._setWatching(workInProgress)
angular.module("taigaHome").controller("WorkingOn", WorkingOnController)

View File

@ -0,0 +1,69 @@
describe "WorkingOn", ->
$controller = null
$provide = null
mocks = {}
_mockHomeService = () ->
mocks.homeService = {
getWorkInProgress: sinon.stub()
}
$provide.value("tgHomeService", mocks.homeService)
_mocks = () ->
module (_$provide_) ->
$provide = _$provide_
_mockHomeService()
return null
_inject = () ->
inject (_$controller_) ->
$controller = _$controller_
beforeEach ->
module "taigaHome"
_mocks()
_inject()
it "get work in progress items", (done) ->
userId = 3
workInProgress = Immutable.fromJS({
assignedTo: {
userStories: [{id: 1, modified_date: "2015-01-01"}, {id: 2, modified_date: "2015-01-04"}],
tasks: [{id: 3, modified_date: "2015-01-02"}, {id: 4, modified_date: "2015-01-05"}],
issues: [{id: 5, modified_date: "2015-01-03"}, {id: 6, modified_date: "2015-01-06"}]
},
watching: {
userStories: [{id: 7, modified_date: "2015-01-01"}, {id: 8, modified_date: "2015-01-04"}],
tasks: [{id: 9, modified_date: "2015-01-02"}, {id: 10, modified_date: "2015-01-05"}],
issues: [{id: 11, modified_date: "2015-01-03"}, {id: 12, modified_date: "2015-01-06"}]
}
})
mocks.homeService.getWorkInProgress.withArgs(userId).promise().resolve(workInProgress)
ctrl = $controller("WorkingOn")
ctrl.getWorkInProgress(userId).then () ->
expect(ctrl.assignedTo.toJS()).to.be.eql([
{id: 6, modified_date: '2015-01-06'},
{id: 4, modified_date: '2015-01-05'},
{id: 2, modified_date: '2015-01-04'},
{id: 5, modified_date: '2015-01-03'},
{id: 3, modified_date: '2015-01-02'},
{id: 1, modified_date: '2015-01-01'}
])
expect(ctrl.watching.toJS()).to.be.eql([
{id: 12, modified_date: '2015-01-06'},
{id: 10, modified_date: '2015-01-05'},
{id: 8, modified_date: '2015-01-04'},
{id: 11, modified_date: '2015-01-03'},
{id: 9, modified_date: '2015-01-02'},
{id: 7, modified_date: '2015-01-01'}
])
done()

View File

@ -0,0 +1,20 @@
WorkingOnDirective = (homeService, currentUserService) ->
link = (scope, el, attrs, ctrl) ->
userId = currentUserService.getUser().get("id")
ctrl.getWorkInProgress(userId)
return {
controller: "WorkingOn",
controllerAs: "vm",
templateUrl: "home/working-on/working-on.html",
scope: {},
link: link
}
WorkingOnDirective.$inject = [
"tgHomeService",
"tgCurrentUserService"
]
angular.module("taigaHome").directive("tgWorkingOn", WorkingOnDirective)

View File

@ -0,0 +1,12 @@
div.title-bar.working-on-title(ng-show="vm.assignedTo.size", translate="HOME.WORKING_ON_SECTION")
section.working-on(ng-show="vm.assignedTo.size")
div.duty-single(tg-duty="duty", tg-repeat="duty in vm.assignedTo", ng-class="{blocked: duty.is_blocked}")
div.title-bar.watching-title(translate="HOME.WATCHING_SECTION")
section.watching-empty(ng-show="!vm.watching.size")
include ../../../svg/hide.svg
p(translate="HOME.EMPTY_WATCHING")
section.watching(ng-show="vm.watching.size")
div.duty-single(tg-duty="duty", tg-repeat="duty in vm.watching", ng-class="{blocked: duty.is_blocked}")

View File

@ -0,0 +1,10 @@
#TODO: fill correctly when implemented
a(href="#", title="Organizations")
include ../../../svg/organizations.svg
div.navbar-dropdown.dropdown-organization-list
ul
- for (var x = 0; x < 4; x++)
li
a(href="#", title="{{ project.title }}") Organization 1
a.create-organization-btn.button-green(href="#", title="Create Organization") Create Organization

View File

@ -0,0 +1,23 @@
DropdownProjectListDirective = (currentUserService, projectsService) ->
link = (scope, el, attrs, ctrl) ->
scope.vm = {}
taiga.defineImmutableProperty(scope.vm, "projects", () -> currentUserService.projects.get("recents"))
scope.vm.newProject = ->
projectsService.newProject()
directive = {
templateUrl: "navigation-bar/dropdown-project-list/dropdown-project-list.html"
scope: {}
link: link
}
return directive
DropdownProjectListDirective.$inject = [
"tgCurrentUserService",
"tgProjectsService"
]
angular.module("taigaNavigationBar").directive("tgDropdownProjectList", DropdownProjectListDirective)

Some files were not shown because too many files have changed in this diff Show More