Migrate appTitle to tgAppMetaService

stable
David Barragán Merino 2015-06-01 21:20:21 +02:00
parent 000f5b8722
commit 54b779ff48
34 changed files with 646 additions and 233 deletions

View File

@ -17,6 +17,7 @@
- Activity timeline
- Global user configuration
- User contacts
- Improve SEO, fix meta tags and added social meta tags
### Misc
- Improve performance: remove some unnecessary calls to the api.

View File

@ -28,21 +28,26 @@ 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, $compileProvider, $translateProvider) ->
configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEventsProvider,
$compileProvider, $translateProvider) ->
$routeProvider.when("/",
{
templateUrl: "home/home.html",
access: {
requiresLogin: true
},
title: "PROJECT.WELCOME",
title: "HOME.PAGE_TITLE",
description: "HOME.PAGE_DESCRIPTION",
loader: true
}
)
@ -53,7 +58,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
access: {
requiresLogin: true
},
title: "PROJECT.SECTION_PROJECTS",
title: "PROJECTS.PAGE_TITLE",
description: "PROJECTS.PAGE_DESCRIPTION",
loader: true,
controller: "ProjectsListing",
controllerAs: "vm"
@ -305,17 +311,47 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
# 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",
@ -482,7 +518,7 @@ i18nInit = (lang, $translate) ->
checksley.updateMessages('default', messages)
init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $navUrls, $appTitle, projectService, loaderService) ->
init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $navUrls, appMetaService, projectService, loaderService) ->
$log.debug("Initialize application")
# Taiga Plugins
@ -517,8 +553,11 @@ init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $na
else
projectService.cleanProject()
if next.title
$translate(next.title).then (text) => $appTitle.set(text)
if next.title or next.description
title = next.title or ""
description = next.description or ""
$translate([title, description]).then (translations) =>
appMetaService.setAll(translations[title], translations[description])
if next.loader
loaderService.startWithAutoClose()
@ -592,7 +631,7 @@ module.run([
"$translate",
"$tgLocation",
"$tgNavUrls",
"$appTitle",
"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(@)
@ -378,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")
@ -415,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,28 +47,31 @@ 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.getBySlug(@params.pslug).then (project) =>
@ -119,7 +122,9 @@ ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location, proje
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)
@ -137,7 +142,9 @@ ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location, proje
return {link:link}
module.directive("tgProjectProfile", ["$tgRepo", "$tgConfirm", "$tgLoading", "$tgNavUrls", "$tgLocation", "tgProjectService", ProjectProfileDirective])
module.directive("tgProjectProfile", ["$tgRepo", "$tgConfirm", "$tgLoading", "$tgNavUrls", "$tgLocation",
"tgProjectService", ProjectProfileDirective])
#############################################################################
## Project Default Values Directive
@ -224,7 +231,8 @@ ProjectModulesDirective = ($repo, $confirm, $loading, projectService) ->
return {link:link}
module.directive("tgProjectModules", ["$tgRepo", "$tgConfirm", "$tgLoading", "tgProjectService", 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,12 +59,12 @@ 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(@)
@ -383,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
@ -640,4 +650,5 @@ ProjectCustomAttributesDirective = ($log, $confirm, animationFrame, $translate)
return {link: link}
module.directive("tgProjectCustomAttributes", ["$log", "$tgConfirm", "animationFrame", "$translate", 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,8 +59,9 @@ 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(@)

View File

@ -41,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"
@ -54,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(@)
@ -292,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")
@ -305,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(@)
@ -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(@)
@ -388,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")
@ -400,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(@)

View File

@ -45,7 +45,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
"$routeParams",
"$q",
"$tgLocation",
"$appTitle",
"tgAppMetaService",
"$tgNavUrls",
"$tgEvents",
"$tgAnalytics",
@ -53,7 +53,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q,
@location, @appTitle, @navUrls, @events, @analytics, @translate) ->
@location, @appMetaService, @navUrls, @events, @analytics, @translate) ->
bindMethods(@)
@scope.sectionName = @translate.instant("BACKLOG.SECTION_NAME")
@ -66,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

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,9 +39,6 @@ class ContribController extends taiga.Controller
promise = @.loadInitialData()
promise.then () =>
@appTitle.set(@scope.project.name)
promise.then null, =>
@confirm.notify("error")

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

@ -44,14 +44,14 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
"$q",
"$tgLocation",
"$log",
"$appTitle",
"tgAppMetaService",
"$tgAnalytics",
"$tgNavUrls",
"$translate"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
@log, @appTitle, @analytics, @navUrls, @translate) ->
@log, @appMetaService, @analytics, @navUrls, @translate) ->
@scope.issueRef = @params.issueref
@scope.sectionName = @translate.instant("ISSUES.SECTION_NAME")
@.initializeEventHandlers()
@ -60,12 +60,27 @@ 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(@)
_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("object:updated")

View File

@ -47,14 +47,15 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
"$routeParams",
"$q",
"$tgLocation",
"$appTitle",
"tgAppMetaService",
"$tgNavUrls",
"$tgEvents",
"$tgAnalytics"
"$tgAnalytics",
"$translate"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @urls, @params, @q, @location, @appTitle,
@navUrls, @events, @analytics) ->
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @urls, @params, @q, @location, @appMetaService,
@navUrls, @events, @analytics, @translate) ->
@scope.sectionName = "Issues"
@scope.filters = {}
@ -69,7 +70,12 @@ 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(@)

View File

@ -58,7 +58,7 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
"$routeParams",
"$q",
"$tgLocation",
"$appTitle",
"tgAppMetaService",
"$tgNavUrls",
"$tgEvents",
"$tgAnalytics",
@ -66,7 +66,7 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
@appTitle, @navUrls, @events, @analytics, @translate) ->
@appMetaService, @navUrls, @events, @analytics, @translate) ->
bindMethods(@)
@ -78,7 +78,12 @@ 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(@)

View File

@ -43,17 +43,23 @@ class SearchController extends mixOf(taiga.Controller, taiga.PageMixin)
"$routeParams",
"$q",
"$tgLocation",
"$appTitle",
"$tgNavUrls"
"tgAppMetaService",
"$tgNavUrls",
"$translate"
]
constructor: (@scope, @repo, @rs, @params, @q, @location, @appTitle, @navUrls) ->
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(@)

View File

@ -44,7 +44,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
"$tgResources",
"$routeParams",
"$q",
"$appTitle",
"tgAppMetaService",
"$tgLocation",
"$tgNavUrls"
"$tgEvents"
@ -52,7 +52,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
"$translate"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @appTitle, @location, @navUrls,
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @appMetaService, @location, @navUrls,
@events, @analytics, @translate) ->
bindMethods(@)
@ -62,12 +62,31 @@ 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(@)
_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
# a big overhead. It should be optimized in near future.
@ -255,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,14 +42,14 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
"$q",
"$tgLocation",
"$log",
"$appTitle",
"tgAppMetaService",
"$tgNavUrls",
"$tgAnalytics",
"$translate"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
@log, @appTitle, @navUrls, @analytics, @translate) ->
@log, @appMetaService, @navUrls, @analytics, @translate) ->
@scope.taskRef = @params.taskref
@scope.sectionName = @translate.instant("TASK.SECTION_NAME")
@.initializeEventHandlers()
@ -57,11 +57,23 @@ 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(@)
_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)

View File

@ -39,21 +39,26 @@ class TeamController extends mixOf(taiga.Controller, taiga.PageMixin)
"$q",
"$location",
"$tgNavUrls",
"$appTitle",
"tgAppMetaService",
"$tgAuth",
"$translate",
"tgProjectService"
]
constructor: (@scope, @rootscope, @repo, @rs, @params, @q, @location, @navUrls, @appTitle, @auth, @translate, @projectService) ->
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(@)

View File

@ -42,14 +42,14 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
"$q",
"$tgLocation",
"$log",
"$appTitle",
"tgAppMetaService",
"$tgNavUrls",
"$tgAnalytics",
"$translate"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
@log, @appTitle, @navUrls, @analytics, @translate) ->
@log, @appMetaService, @navUrls, @analytics, @translate) ->
@scope.usRef = @params.usref
@scope.sectionName = @translate.instant("US.SECTION_NAME")
@.initializeEventHandlers()
@ -58,12 +58,33 @@ 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(@)
_setMeta: ->
totalTasks = @scope.tasks.length
closedTasks = _.filter(@scope.tasks, (t) => @scope.taskStatusById[t.status].is_closed).length
progressPercentage = if totalTasks > 0 then 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", =>
@.loadUs()

View File

@ -46,14 +46,14 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
"$tgLocation",
"$filter",
"$log",
"$appTitle",
"tgAppMetaService",
"$tgNavUrls",
"$tgAnalytics",
"$translate"
]
constructor: (@scope, @rootscope, @repo, @model, @confirm, @rs, @params, @q, @location,
@filter, @log, @appTitle, @navUrls, @analytics, @translate) ->
@filter, @log, @appMetaService, @navUrls, @analytics, @translate) ->
@scope.projectSlug = @params.pslug
@scope.wikiSlug = @params.slug
@scope.sectionName = "Wiki"
@ -61,12 +61,23 @@ 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(@)
_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) =>
if not project.is_wiki_activated

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 "-"
@ -187,6 +200,7 @@ taiga.cancelTimeout = cancelTimeout
taiga.scopeDefer = scopeDefer
taiga.toString = toString
taiga.joinStr = joinStr
taiga.truncate = truncate
taiga.debounce = debounce
taiga.debounceLeading = debounceLeading
taiga.startswith = startswith

View File

@ -3,10 +3,12 @@ html(lang="en")
head
meta(charset="utf-8")
meta(http-equiv="content-type", content="text/html; charset=utf-8")
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="fragment", content="!")
//-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")

View File

@ -236,20 +236,105 @@
"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, beautif ul 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.",
"ERROR_GENERIC": "According to our Oompa Loompas there was an error.",
"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, beautif ul 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, beautif ul 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 :)",
"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": {
@ -289,6 +374,7 @@
},
"MEMBERSHIPS": {
"TITLE": "Manage members",
"PAGE_TITLE": "Memberships - {{projectName}}",
"ADD_BUTTON": "+ New member",
"ADD_BUTTON_TITLE": "Add new member"
},
@ -324,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",
@ -365,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": {
@ -398,7 +487,7 @@
"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",
@ -415,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",
@ -454,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",
@ -533,6 +623,7 @@
},
"USER": {
"PROFILE": {
"PAGE_TITLE": "{{userFullName}} (@{{userUsername}})",
"EDIT": "Edit profile",
"FOLLOW": "Follow",
"PROJECTS": "Projects",
@ -555,6 +646,7 @@
}
},
"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",
@ -685,6 +777,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",
@ -772,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": "Move to Current Sprint",
"SHOW_FILTERS": "Show filters",
@ -852,6 +949,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",
@ -875,6 +974,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",
@ -920,56 +1021,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",
@ -1033,7 +1087,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",
@ -1048,6 +1108,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",
@ -1058,6 +1120,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...",
@ -1078,16 +1142,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": {
@ -1141,6 +1195,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",

View File

@ -1,13 +1,14 @@
class ProfileController
@.$inject = [
"$appTitle",
"tgAppMetaService",
"tgCurrentUserService",
"$routeParams",
"tgUserService",
"tgXhrErrorService"
"tgXhrErrorService",
"$translate"
]
constructor: (@appTitle, @currentUserService, @routeParams, @userService, @xhrError) ->
constructor: (@appMetaService, @currentUserService, @routeParams, @userService, @xhrError, @translate) ->
@.isCurrentUser = false
if @routeParams.slug
@ -16,13 +17,21 @@ class ProfileController
.then (user) =>
@.user = user
@.isCurrentUser = false
@appTitle.set(@.user.get('full_name'))
@._setMeta(@.user)
.catch (xhr) =>
@xhrError.response(xhr)
else
@.user = @currentUserService.getUser()
@.isCurrentUser = true
@appTitle.set(@.user.get('full_name_display'))
@._setMeta(@.user)
_setMeta: (user) ->
title = @translate.instant("USER.PROFILE.PAGE_TITLE", {
userFullName: user.get("full_name_display"),
userUsername: user.get("username")
})
description = user.get("bio")
@appMetaService.setAll(title, description)
angular.module("taigaProfile").controller("Profile", ProfileController)

View File

@ -10,18 +10,21 @@ describe "ProfileController", ->
{id: 3}
])
_mockAppTitle = () ->
stub = sinon.stub()
mocks.appTitle = {
set: sinon.spy()
_mockTranslate = () ->
mocks.translate = {
instant: sinon.stub()
}
provide.value "$appTitle", mocks.appTitle
provide.value "$translate", mocks.translate
_mockAppMetaService = () ->
mocks.appMetaService = {
setAll: sinon.spy()
}
provide.value "tgAppMetaService", mocks.appMetaService
_mockCurrentUser = () ->
stub = sinon.stub()
mocks.currentUser = {
getUser: sinon.stub()
}
@ -29,8 +32,6 @@ describe "ProfileController", ->
provide.value "tgCurrentUserService", mocks.currentUser
_mockUserService = () ->
stub = sinon.stub()
mocks.userService = {
getUserByUserName: sinon.stub()
}
@ -38,15 +39,11 @@ describe "ProfileController", ->
provide.value "tgUserService", mocks.userService
_mockRouteParams = () ->
stub = sinon.stub()
mocks.routeParams = {}
provide.value "$routeParams", mocks.routeParams
_mockXhrErrorService = () ->
stub = sinon.stub()
mocks.xhrErrorService = {
response: sinon.spy()
}
@ -56,12 +53,12 @@ describe "ProfileController", ->
_mocks = () ->
module ($provide) ->
provide = $provide
_mockAppTitle()
_mockTranslate()
_mockAppMetaService()
_mockCurrentUser()
_mockRouteParams()
_mockUserService()
_mockXhrErrorService()
return null
_inject = (callback) ->
@ -81,9 +78,18 @@ describe "ProfileController", ->
mocks.routeParams.slug = "user-slug"
user = Immutable.fromJS({
full_name: "full-name"
username: "username"
full_name_display: "full-name-display"
bio: "bio"
})
mocks.translate.instant
.withArgs('USER.PROFILE.PAGE_TITLE', {
userFullName: user.get("full_name_display"),
userUsername: user.get("username")
})
.returns('user-profile-page-title')
mocks.userService.getUserByUserName.withArgs(mocks.routeParams.slug).promise().resolve(user)
ctrl = $controller("Profile")
@ -91,8 +97,7 @@ describe "ProfileController", ->
setTimeout ( ->
expect(ctrl.user).to.be.equal(user)
expect(ctrl.isCurrentUser).to.be.false
expect(mocks.appTitle.set.calledWithExactly("full-name")).to.be.true
expect(mocks.appMetaService.setAll.calledWithExactly("user-profile-page-title", "bio")).to.be.true
done()
)
@ -111,7 +116,6 @@ describe "ProfileController", ->
setTimeout ( ->
expect(mocks.xhrErrorService.response.withArgs(xhr)).to.be.calledOnce
done()
)
@ -119,13 +123,22 @@ describe "ProfileController", ->
$scope = $rootScope.$new()
user = Immutable.fromJS({
username: "username"
full_name_display: "full-name-display"
bio: "bio"
})
mocks.translate.instant
.withArgs('USER.PROFILE.PAGE_TITLE', {
userFullName: user.get("full_name_display"),
userUsername: user.get("username")
})
.returns('user-profile-page-title')
mocks.currentUser.getUser.returns(user)
ctrl = $controller("Profile")
expect(ctrl.user).to.be.equal(user)
expect(ctrl.isCurrentUser).to.be.true
expect(mocks.appTitle.set.calledWithExactly("full-name-display")).to.be.true
expect(mocks.appMetaService.setAll.withArgs("user-profile-page-title", "bio")).to.be.calledOnce

View File

@ -2,22 +2,28 @@ class ProjectController
@.$inject = [
"tgProjectsService",
"$routeParams",
"$appTitle",
"tgAppMetaService",
"$tgAuth",
"tgXhrErrorService"
"tgXhrErrorService",
"$translate"
]
constructor: (@projectsService, @routeParams, @appTitle, @auth, @xhrError) ->
constructor: (@projectsService, @routeParams, @appMetaService, @auth, @xhrError, @translate) ->
projectSlug = @routeParams.pslug
@.user = @auth.userData
@projectsService
.getProjectBySlug(projectSlug)
.then (project) =>
@appTitle.set(project.get("name"))
@.project = project
@._setMeta(@.project)
.catch (xhr) =>
@xhrError.response(xhr)
_setMeta: (project)->
title = @translate.instant("PROJECT.PAGE_TITLE", {projectName: project.get("name")})
description = project.get("description")
@appMetaService.setAll(title, description)
angular.module("taigaProjects").controller("Project", ProjectController)

View File

@ -12,12 +12,12 @@ describe "ProjectController", ->
provide.value "tgProjectsService", mocks.projectService
_mockAppTitle = () ->
mocks.appTitle = {
set: sinon.stub()
_mockAppMetaService = () ->
mocks.appMetaService = {
setAll: sinon.stub()
}
provide.value "$appTitle", mocks.appTitle
provide.value "tgAppMetaService", mocks.appMetaService
_mockAuth = () ->
mocks.auth = {
@ -38,15 +38,22 @@ describe "ProjectController", ->
provide.value "tgXhrErrorService", mocks.xhrErrorService
_mockTranslate = () ->
mocks.translate = {
instant: sinon.stub()
}
provide.value "$translate", mocks.translate
_mocks = () ->
module ($provide) ->
provide = $provide
_mockProjectsService()
_mockRouteParams()
_mockAppTitle()
_mockAppMetaService()
_mockAuth()
_mockXhrErrorService()
_mockTranslate()
return null
_inject = (callback) ->
@ -73,16 +80,24 @@ describe "ProjectController", ->
expect(ctrl.user).to.be.equal(mocks.auth.userData)
it "set page title", (done) ->
$scope = $rootScope.$new()
project = Immutable.fromJS({
name: "projectName"
description: "projectDescription"
})
mocks.translate.instant
.withArgs('PROJECT.PAGE_TITLE', {
projectName: project.get("name")
})
.returns('projectTitle')
mocks.projectService.getProjectBySlug.withArgs("project-slug").promise().resolve(project)
ctrl = $controller("Project")
setTimeout ( ->
expect(mocks.appTitle.set.withArgs("projectName")).to.be.calledOnce
expect(mocks.appMetaService.setAll.calledWithExactly("projectTitle", "projectDescription")).to.be.true
done()
)

View File

@ -0,0 +1,63 @@
taiga = @.taiga
truncate = taiga.truncate
class AppMetaService extends taiga.Service = ->
_set: (key, value) ->
return if not key
if key == "title"
meta = $("title")
if meta.length == 0
meta = $("<title></title>")
$("head").append(meta)
meta.text(value or "")
if key.indexOf("og:") == 0
meta = $("meta[property='#{key}']")
if meta.length == 0
meta = $("<meta property='#{key}'/>")
$("head").append(meta)
meta.attr("content", value or "")
else
meta = $("meta[name='#{key}']")
if meta.length == 0
meta = $("<meta name='#{key}'/>")
$("head").append(meta)
meta.attr("content", value or "")
setTitle: (title) ->
@._set('title', title)
setDescription: (description) ->
@._set("description", truncate(description, 250))
setTwitterMetas: (title, description) ->
@._set("twitter:card", "summary")
@._set("twitter:site", "@taigaio")
@._set("twitter:title", title)
@._set("twitter:description", truncate(description, 250))
@._set("twitter:image", "#{window.location.origin}/images/favicon.png")
setOpenGraphMetas: (title, description) ->
@._set("og:type", "object")
@._set("og:site_name", "Taiga - Love your projects")
@._set("og:title", title)
@._set("og:description", truncate(description, 250))
@._set("og:image", "#{window.location.origin}/images/favicon.png")
@._set("og:url", window.location.href)
setAll: (title, description) ->
@.setTitle(title)
@.setDescription(description)
@.setTwitterMetas(title, description)
@.setOpenGraphMetas(title, description)
angular.module("taigaCommon").service("tgAppMetaService", AppMetaService)

View File

@ -0,0 +1,55 @@
describe "AppMetaService", ->
appMetaService = null
data = {
title: "--title--",
description: "--description--"
}
_inject = () ->
inject (_tgAppMetaService_) ->
appMetaService = _tgAppMetaService_
beforeEach ->
module "taigaCommon"
_inject()
it "set meta title", () ->
appMetaService.setTitle(data.title)
expect($("title")).to.have.text(data.title)
it "set meta description", () ->
appMetaService.setDescription(data.description)
expect($("meta[name='description']")).to.have.attr("content", data.description)
it "set meta for twitter", () ->
appMetaService.setTwitterMetas(data.title, data.description)
expect($("meta[name='twitter:card']")).to.have.attr("content", "summary")
expect($("meta[name='twitter:site']")).to.have.attr("content", "@taigaio")
expect($("meta[name='twitter:title']")).to.have.attr("content", data.title)
expect($("meta[name='twitter:description']")).to.have.attr("content", data.description)
expect($("meta[name='twitter:image']")).to.have.attr("content", "#{window.location.origin}/images/favicon.png")
it "set meta for open graph", () ->
appMetaService.setOpenGraphMetas(data.title, data.description)
expect($("meta[property='og:type']")).to.have.attr("content", "object")
expect($("meta[property='og:site_name']")).to.have.attr("content", "Taiga - Love your projects")
expect($("meta[property='og:title']")).to.have.attr("content", data.title)
expect($("meta[property='og:description']")).to.have.attr("content", data.description)
expect($("meta[property='og:image']")).to.have.attr("content", "#{window.location.origin}/images/favicon.png")
expect($("meta[property='og:url']")).to.have.attr("content", window.location.href)
it "set all meta", () ->
appMetaService.setAll(data.title, data.description)
expect($("title")).to.have.text(data.title)
expect($("meta[name='description']")).to.have.attr("content", data.description)
expect($("meta[name='twitter:card']")).to.have.attr("content", "summary")
expect($("meta[name='twitter:site']")).to.have.attr("content", "@taigaio")
expect($("meta[name='twitter:title']")).to.have.attr("content", data.title)
expect($("meta[name='twitter:description']")).to.have.attr("content", data.description)
expect($("meta[name='twitter:image']")).to.have.attr("content", "#{window.location.origin}/images/favicon.png")
expect($("meta[property='og:type']")).to.have.attr("content", "object")
expect($("meta[property='og:site_name']")).to.have.attr("content", "Taiga - Love your projects")
expect($("meta[property='og:title']")).to.have.attr("content", data.title)
expect($("meta[property='og:description']")).to.have.attr("content", data.description)
expect($("meta[property='og:image']")).to.have.attr("content", "#{window.location.origin}/images/favicon.png")
expect($("meta[property='og:url']")).to.have.attr("content", window.location.href)

View File

@ -1,6 +1,7 @@
doctype html
div.wrapper(ng-controller="ProjectValuesSectionController")
div.wrapper(ng-controller="ProjectValuesSectionController",
ng-init="sectionName='ADMIN.PROJECT_VALUES_POINTS.TITLE'")
tg-project-menu
@ -15,7 +16,7 @@ div.wrapper(ng-controller="ProjectValuesSectionController")
p.admin-subtitle(translate="ADMIN.PROJECT_VALUES_POINTS.SUBTITLE")
div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl",
ng-init="section='admin'; resource='userstories'; type='points'; sectionName='ADMIN.PROJECT_VALUES_POINTS.TITLE'",
ng-init="section='admin'; resource='userstories'; type='points'; sectionName='ADMIN.PROJECT_VALUES_POINTS.US_TITLE'",
objName="points",
type="points")
include ../includes/modules/admin/project-points

View File

@ -1,6 +1,8 @@
doctype html
div.wrapper(ng-controller="ProjectValuesSectionController")
div.wrapper(ng-controller="ProjectValuesSectionController",
ng-init="sectionName='ADMIN.PROJECT_VALUES_PRIORITIES.TITLE'")
tg-project-menu
sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="project-values")
@ -14,7 +16,7 @@ div.wrapper(ng-controller="ProjectValuesSectionController")
p.admin-subtitle(translate="ADMIN.PROJECT_VALUES_PRIORITIES.SUBTITLE")
div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl",
ng-init="section='admin'; resource='issues'; type='priorities'; sectionName='ADMIN.PROJECT_VALUES_PRIORITIES.TITLE';",
ng-init="section='admin'; resource='issues'; type='priorities'; sectionName='ADMIN.PROJECT_VALUES_PRIORITIES.ISSUE_TITLE';",
objName="priorities",
type="priorities")
include ../includes/modules/admin/project-types

View File

@ -1,6 +1,8 @@
doctype html
div.wrapper(ng-controller="ProjectValuesSectionController")
div.wrapper(ng-controller="ProjectValuesSectionController",
ng-init="sectionName='ADMIN.PROJECT_VALUES_SEVERITIES.TITLE'")
tg-project-menu
sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="project-values")
@ -14,7 +16,7 @@ div.wrapper(ng-controller="ProjectValuesSectionController")
p.admin-subtitle(translate="ADMIN.PROJECT_VALUES_SEVERITIES.SUBTITLE")
div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl",
ng-init="section='admin'; resource='issues'; type='severities'; sectionName='ADMIN.PROJECT_VALUES_SEVERITIES.TITLE';",
ng-init="section='admin'; resource='issues'; type='severities'; sectionName='ADMIN.PROJECT_VALUES_SEVERITIES.ISSUE_TITLE';",
objName="severities",
type="severities")
include ../includes/modules/admin/project-types

View File

@ -2,6 +2,7 @@ doctype html
div.wrapper(ng-controller="ProjectValuesSectionController",
ng-init="section='admin'; sectionName='ADMIN.PROJECT_VALUES_STATUS.TITLE'")
tg-project-menu
sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="project-values")

View File

@ -2,6 +2,7 @@ doctype html
div.wrapper(ng-controller="ProjectValuesSectionController"
ng-init="sectionName='ADMIN.PROJECT_VALUES_TYPES.TITLE'")
tg-project-menu
sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="project-values")

View File

@ -1,11 +1,16 @@
.view-wiki-content
section.wysiwyg(tg-bind-html='wiki.html')
span.edit.icon.icon-edit(title="{{'COMMON.EDIT' | translate}}", ng-if="wiki")
.edit-wiki-content(style='display: none;')
textarea(ng-attr-placeholder="{{'WIKI.PLACEHOLDER_PAGE' | translate}}", ng-model='wiki.content', tg-markitup='tg-markitup')
a.help-markdown(href='https://taiga.io/support/taiga-markdown-syntax/', target='_blank', title="{{'COMMON.WYSIWYG.MARKDOWN_HELP' | translate}}")
span.icon.icon-help
span(translate="COMMON.WYSIWYG.MARKDOWN_HELP")
span.action-container
a.save.icon.icon-floppy(href='', title="{{'COMMON.SAVE' | translate}}")
a.cancel.icon.icon-delete(href='', title="{{'COMMON.CANCEL' | translate}}")
textarea(ng-attr-placeholder="{{'WIKI.PLACEHOLDER_PAGE' | translate}}",
ng-model='wiki.content', tg-markitup='tg-markitup')
a.help-markdown(href='https://taiga.io/support/taiga-markdown-syntax/', target='_blank',
title="{{'COMMON.WYSIWYG.MARKDOWN_HELP' | translate}}")
span.icon.icon-help
span(translate="COMMON.WYSIWYG.MARKDOWN_HELP")
span.action-container
a.save.icon.icon-floppy(href='', title="{{'COMMON.SAVE' | translate}}")
a.cancel.icon.icon-delete(href='', title="{{'COMMON.CANCEL' | translate}}")