Merge branch 'master' into stable

stable
Alejandro Alonso 2015-05-21 10:38:06 +02:00
commit f154afb5d9
305 changed files with 10331 additions and 2884 deletions

View File

@ -1,3 +1,3 @@
{ {
"directory" : "app/vendor" "directory" : "vendor"
} }

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
dist/ dist/
node_modules/ node_modules/
app/vendor app/vendor
vendor/
config/main.coffee config/main.coffee
bower_components bower_components
app/coffee/modules/locales/locale*.coffee app/coffee/modules/locales/locale*.coffee

9
.tx/config Normal file
View File

@ -0,0 +1,9 @@
[main]
host = https://www.transifex.com
lang_map = sr@latin:sr-latn, zh-Hans:zh-hans, zh-Hant:zh-hant
[taiga-front.locale-enjson]
file_filter = app/locales/locale-<lang>.json
source_file = app/locales/locale-en.json
source_lang = en
type = KEYVALUEJSON

View File

@ -1,6 +1,23 @@
# Changelog # # Changelog #
## 1.7.0 Empetrum Nigrum (unreleased)
### Features
- Make Taiga translatable (i18n support).
- i18n.
- Add spanish (es) translation.
- Add french (fr) translation.
- Add finish (fi) translation.
- Add catalan (ca) translation.
- Add traditional chinese (zh-Hant) translation.
- Add Jitsi to our supported videoconference apps list
### Misc
- New contrib plugin for letschat (by Δndrea Stagi)
- Lots of small and not so small bugfixes.
## 1.6.0 Abies Bifolia (2015-03-17) ## 1.6.0 Abies Bifolia (2015-03-17)
### Features ### Features

View File

@ -44,3 +44,10 @@ All the information about the different installation methods (production, develo
[Taiga has a mailing list](http://groups.google.com/d/forum/taigaio). Feel free to join it and ask any questions you may have. [Taiga has a mailing list](http://groups.google.com/d/forum/taigaio). Feel free to join it and ask any questions you may have.
To subscribe for announcements of releases, important changes and so on, please follow [@taigaio](https://twitter.com/taigaio) on Twitter. To subscribe for announcements of releases, important changes and so on, please follow [@taigaio](https://twitter.com/taigaio) on Twitter.
## Donations ##
We are grateful for your emails volunteering donations to Taiga. We feel comfortable accepting them under these conditions: The first that we will only do so while we are in the current beta / pre-revenue stage and that whatever money is donated will go towards a bounty fund. Starting Q2 2015 we will be engaging much more actively with our community to help further the development of Taiga, and we will use these donations to reward people working alongside us.
If you wish to make a donation to this Taiga fund, you can do so via http://www.paypal.com using the email: eposner@taiga.io

View File

@ -3,6 +3,7 @@ window.taigaConfig = {
"api": "http://localhost:8000/api/v1/", "api": "http://localhost:8000/api/v1/",
"eventsUrl": null, "eventsUrl": null,
"debug": true, "debug": true,
"defaultLanguage": "en",
"publicRegisterEnabled": true, "publicRegisterEnabled": true,
"feedbackEnabled": true, "feedbackEnabled": true,
"privacyPolicyUrl": null, "privacyPolicyUrl": null,

View File

@ -36,7 +36,8 @@ taiga.generateUniqueSessionIdentifier = ->
taiga.sessionId = taiga.generateUniqueSessionIdentifier() taiga.sessionId = taiga.generateUniqueSessionIdentifier()
configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEventsProvider, tgLoaderProvider, $compileProvider) -> configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEventsProvider, tgLoaderProvider,
$compileProvider, $translateProvider) ->
$routeProvider.when("/", $routeProvider.when("/",
{templateUrl: "project/projects.html", resolve: {loader: tgLoaderProvider.add()}}) {templateUrl: "project/projects.html", resolve: {loader: tgLoaderProvider.add()}})
@ -159,12 +160,12 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
$routeProvider.when("/permission-denied", $routeProvider.when("/permission-denied",
{templateUrl: "error/permission-denied.html"}) {templateUrl: "error/permission-denied.html"})
$routeProvider.otherwise({redirectTo: '/not-found'}) $routeProvider.otherwise({redirectTo: "/not-found"})
$locationProvider.html5Mode({enabled: true, requireBase: false}) $locationProvider.html5Mode({enabled: true, requireBase: false})
defaultHeaders = { defaultHeaders = {
"Content-Type": "application/json" "Content-Type": "application/json"
"Accept-Language": "en" "Accept-Language": window.taigaConfig.defaultLanguage || "en"
"X-Session-Id": taiga.sessionId "X-Session-Id": taiga.sessionId
} }
@ -195,30 +196,30 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
responseError: httpResponseError responseError: httpResponseError
} }
$provide.factory("authHttpIntercept", ["$q", "$location", "$tgNavUrls", "lightboxService", authHttpIntercept]) $provide.factory("authHttpIntercept", ["$q", "$location", "$tgNavUrls", "lightboxService",
authHttpIntercept])
$httpProvider.interceptors.push('authHttpIntercept') $httpProvider.interceptors.push("authHttpIntercept")
# If there is an error in the version throw a notify error
versionCheckHttpIntercept = ($q, $confirm) ->
versionErrorMsg = "Someone inside Taiga has changed this before and our Oompa Loompas cannot apply your changes.
Please reload and apply your changes again (they will be lost)." #TODO: i18n
# 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) ->
httpResponseError = (response) -> httpResponseError = (response) ->
if response.status == 400 && response.data.version if response.status == 400 && response.data.version
$confirm.notify("error", versionErrorMsg, null, 10000) # HACK: to prevent circular dependencies with [$tgConfirm, $translate]
$injector = angular.element("body").injector()
return $q.reject(response) $injector.invoke(["$tgConfirm", "$translate", ($confirm, $translate) =>
versionErrorMsg = $translate.instant("ERROR.VERSION_ERROR")
$confirm.notify("error", versionErrorMsg, null, 10000)
])
return $q.reject(response) return $q.reject(response)
return { return {responseError: httpResponseError}
responseError: httpResponseError
}
$provide.factory("versionCheckHttpIntercept", ["$q", "$tgConfirm", versionCheckHttpIntercept]) $provide.factory("versionCheckHttpIntercept", ["$q", versionCheckHttpIntercept])
$httpProvider.interceptors.push('versionCheckHttpIntercept'); $httpProvider.interceptors.push("versionCheckHttpIntercept")
window.checksley.updateValidators({ window.checksley.updateValidators({
linewidth: (val, width) -> linewidth: (val, width) ->
@ -230,21 +231,78 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
return valid return valid
}) })
window.checksley.updateMessages("default", {
linewidth: "The subject must have a maximum size of %s"
})
$compileProvider.debugInfoEnabled(window.taigaConfig.debugInfo || false) $compileProvider.debugInfoEnabled(window.taigaConfig.debugInfo || false)
init = ($log, $i18n, $config, $rootscope, $auth, $events, $analytics) -> if localStorage.userInfo
$i18n.initialize($config.get("defaultLanguage")) userInfo = JSON.parse(localStorage.userInfo)
# i18n
preferedLangCode = userInfo?.lang || window.taigaConfig.defaultLanguage || "en"
$translateProvider
.useStaticFilesLoader({
prefix: "/locales/locale-",
suffix: ".json"
})
.addInterpolation('$translateMessageFormatInterpolation')
.preferredLanguage(preferedLangCode)
if not window.taigaConfig.debugInfo
$translateProvider.fallbackLanguage(preferedLangCode)
i18nInit = (lang, $translate) ->
# i18n - moment.js
moment.locale(lang)
# i18n - checksley.js
messages = {
defaultMessage: $translate.instant("COMMON.FORM_ERRORS.DEFAULT_MESSAGE")
type: {
email: $translate.instant("COMMON.FORM_ERRORS.TYPE_EMAIL")
url: $translate.instant("COMMON.FORM_ERRORS.TYPE_URL")
urlstrict: $translate.instant("COMMON.FORM_ERRORS.TYPE_URLSTRICT")
number: $translate.instant("COMMON.FORM_ERRORS.TYPE_NUMBER")
digits: $translate.instant("COMMON.FORM_ERRORS.TYPE_DIGITS")
dateIso: $translate.instant("COMMON.FORM_ERRORS.TYPE_DATEISO")
alphanum: $translate.instant("COMMON.FORM_ERRORS.TYPE_ALPHANUM")
phone: $translate.instant("COMMON.FORM_ERRORS.TYPE_PHONE")
}
notnull: $translate.instant("COMMON.FORM_ERRORS.NOTNULL")
notblank: $translate.instant("COMMON.FORM_ERRORS.NOT_BLANK")
required: $translate.instant("COMMON.FORM_ERRORS.REQUIRED")
regexp: $translate.instant("COMMON.FORM_ERRORS.REGEXP")
min: $translate.instant("COMMON.FORM_ERRORS.MIN")
max: $translate.instant("COMMON.FORM_ERRORS.MAX")
range: $translate.instant("COMMON.FORM_ERRORS.RANGE")
minlength: $translate.instant("COMMON.FORM_ERRORS.MIN_LENGTH")
maxlength: $translate.instant("COMMON.FORM_ERRORS.MAX_LENGTH")
rangelength: $translate.instant("COMMON.FORM_ERRORS.RANGE_LENGTH")
mincheck: $translate.instant("COMMON.FORM_ERRORS.MIN_CHECK")
maxcheck: $translate.instant("COMMON.FORM_ERRORS.MAX_CHECK")
rangecheck: $translate.instant("COMMON.FORM_ERRORS.RANGE_CHECK")
equalto: $translate.instant("COMMON.FORM_ERRORS.EQUAL_TO")
}
checksley.updateMessages('default', messages)
init = ($log, $config, $rootscope, $auth, $events, $analytics, $translate) ->
$log.debug("Initialize application") $log.debug("Initialize application")
# Taiga Plugins
$rootscope.contribPlugins = @.taigaContribPlugins $rootscope.contribPlugins = @.taigaContribPlugins
$rootscope.adminPlugins = _.where(@.taigaContribPlugins, {"type": "admin"}) $rootscope.adminPlugins = _.where(@.taigaContribPlugins, {"type": "admin"})
$rootscope.$on "$translateChangeEnd", (e, ctx) ->
lang = ctx.language
i18nInit(lang, $translate)
# Load user
if $auth.isAuthenticated() if $auth.isAuthenticated()
$events.setupConnection() $events.setupConnection()
user = $auth.getUser()
# Analytics
$analytics.initialize() $analytics.initialize()
@ -253,7 +311,6 @@ modules = [
"taigaBase", "taigaBase",
"taigaCommon", "taigaCommon",
"taigaResources", "taigaResources",
"taigaLocales",
"taigaAuth", "taigaAuth",
"taigaEvents", "taigaEvents",
@ -261,7 +318,7 @@ modules = [
"taigaRelatedTasks", "taigaRelatedTasks",
"taigaBacklog", "taigaBacklog",
"taigaTaskboard", "taigaTaskboard",
"taigaKanban" "taigaKanban",
"taigaIssues", "taigaIssues",
"taigaUserStories", "taigaUserStories",
"taigaTasks", "taigaTasks",
@ -275,13 +332,15 @@ modules = [
"taigaFeedback", "taigaFeedback",
"taigaPlugins", "taigaPlugins",
"taigaIntegrations", "taigaIntegrations",
"taigaComponents",
# template cache # template cache
"templates" "templates",
# Vendor modules # Vendor modules
"ngRoute", "ngRoute",
"ngAnimate", "ngAnimate",
"pascalprecht.translate"
].concat(_.map(@.taigaContribPlugins, (plugin) -> plugin.module)) ].concat(_.map(@.taigaContribPlugins, (plugin) -> plugin.module))
# Main module definition # Main module definition
@ -295,16 +354,17 @@ module.config([
"$tgEventsProvider", "$tgEventsProvider",
"tgLoaderProvider", "tgLoaderProvider",
"$compileProvider", "$compileProvider",
"$translateProvider",
configure configure
]) ])
module.run([ module.run([
"$log", "$log",
"$tgI18n",
"$tgConfig", "$tgConfig",
"$rootScope", "$rootScope",
"$tgAuth", "$tgAuth",
"$tgEvents", "$tgEvents",
"$tgAnalytics", "$tgAnalytics",
"$translate"
init init
]) ])

View File

@ -30,19 +30,19 @@ MAX_MEMBERSHIP_FIELDSETS = 4
## Create Members Lightbox Directive ## Create Members Lightbox Directive
############################################################################# #############################################################################
CreateMembersDirective = ($rs, $rootScope, $confirm, $loading ,lightboxService) -> CreateMembersDirective = ($rs, $rootScope, $confirm, $loading, lightboxService, $compile) ->
extraTextTemplate = """ extraTextTemplate = """
<fieldset class="extra-text"> <fieldset class="extra-text">
<textarea placeholder="(Optional) Add a personalized text to the invitation. Tell something lovely to your new members ;-)" <textarea ng-attr-placeholder="{{'LIGHTBOX.CREATE_MEMBER.PLACEHOLDER_INVITATION_TEXT' | translate}}"
maxlength="255"> maxlength="255"></textarea>
</textarea>
</fieldset> </fieldset>
""" """
template = _.template(""" template = _.template("""
<div class="add-member-wrapper"> <div class="add-member-wrapper">
<fieldset> <fieldset>
<input type="email" placeholder="Type an Email" <% if(required) { %> data-required="true" <% } %> data-type="email" /> <input type="email" placeholder="{{'LIGHTBOX.CREATE_MEMBER.PLACEHOLDER_TYPE_EMAIL' | translate}}"
<% if(required) { %> data-required="true" <% } %> data-type="email" />
</fieldset> </fieldset>
<fieldset> <fieldset>
<select <% if(required) { %> data-required="true" <% } %> data-required="true"> <select <% if(required) { %> data-required="true" <% } %> data-required="true">
@ -53,19 +53,19 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, $loading ,lightboxService)
<a class="icon icon-plus add-fieldset" href=""></a> <a class="icon icon-plus add-fieldset" href=""></a>
</fieldset> </fieldset>
</div> </div>
""") # i18n """)
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
createFieldSet = (required = true)-> createFieldSet = (required = true)->
ctx = {roleList: $scope.roles, required: required} ctx = {roleList: $scope.roles, required: required}
return template(ctx) return $compile(template(ctx))($scope)
resetForm = -> resetForm = ->
$el.find("form textarea").remove("") $el.find("form textarea").remove()
$el.find("form .add-member-wrapper").remove() $el.find("form .add-member-wrapper").remove()
invitations = $el.find(".add-member-forms") invitations = $el.find(".add-member-forms")
invitations.html(extraTextTemplate) invitations.html($compile(extraTextTemplate)($scope))
fieldSet = createFieldSet() fieldSet = createFieldSet()
invitations.prepend(fieldSet) invitations.prepend(fieldSet)
@ -84,7 +84,7 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, $loading ,lightboxService)
fieldSet.remove() fieldSet.remove()
lastActionButton = $el.find("fieldset:last > a") lastActionButton = $el.find(".add-member-wrapper fieldset:last > a")
if lastActionButton.hasClass("icon-delete delete-fieldset") if lastActionButton.hasClass("icon-delete delete-fieldset")
lastActionButton.removeClass("icon-delete delete-fieldset") lastActionButton.removeClass("icon-delete delete-fieldset")
.addClass("icon-plus add-fieldset") .addClass("icon-plus add-fieldset")
@ -98,9 +98,10 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, $loading ,lightboxService)
.addClass("icon-delete delete-fieldset") .addClass("icon-delete delete-fieldset")
newFieldSet = createFieldSet(false) newFieldSet = createFieldSet(false)
fieldSet.after(newFieldSet) fieldSet.after(newFieldSet)
$scope.$digest() # To compile newFieldSet and translate text
if $el.find(".add-member-wrapper").length == MAX_MEMBERSHIP_FIELDSETS if $el.find(".add-member-wrapper").length == MAX_MEMBERSHIP_FIELDSETS
$el.find(".add-member-wrapper fieldset:last > a").removeClass("icon-plus add-fieldset") $el.find(".add-member-wrapper fieldset:last > a").removeClass("icon-plus add-fieldset")
.addClass("icon-delete delete-fieldset") .addClass("icon-delete delete-fieldset")
@ -147,7 +148,9 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, $loading ,lightboxService)
if invitations.length if invitations.length
invitation_extra_text = $el.find("form textarea").val() invitation_extra_text = $el.find("form textarea").val()
$rs.memberships.bulkCreateMemberships($scope.project.id, invitations, invitation_extra_text).then(onSuccess, onError) promise = $rs.memberships.bulkCreateMemberships($scope.project.id,
invitations, invitation_extra_text)
promise.then(onSuccess, onError)
submitButton = $el.find(".submit-button") submitButton = $el.find(".submit-button")
@ -155,5 +158,5 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, $loading ,lightboxService)
return {link: link} return {link: link}
module.directive("tgLbCreateMembers", ["$tgResources", "$rootScope", "$tgConfirm", "$tgLoading", "lightboxService", module.directive("tgLbCreateMembers", ["$tgResources", "$rootScope", "$tgConfirm", "$tgLoading",
CreateMembersDirective]) "lightboxService", "$compile", CreateMembersDirective])

View File

@ -50,7 +50,6 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai
@location, @navUrls, @analytics, @appTitle) -> @location, @navUrls, @analytics, @appTitle) ->
bindMethods(@) bindMethods(@)
@scope.sectionName = "Manage Members" #i18n
@scope.project = {} @scope.project = {}
@scope.filters = {} @scope.filters = {}
@ -236,7 +235,7 @@ module.directive("tgMembershipsRowAvatar", ["$log", "$tgTemplate", MembershipsRo
## Member IsAdminCheckbox Directive ## Member IsAdminCheckbox Directive
############################################################################# #############################################################################
MembershipsRowAdminCheckboxDirective = ($log, $repo, $confirm, $template) -> MembershipsRowAdminCheckboxDirective = ($log, $repo, $confirm, $template, $compile) ->
template = $template.get("admin/admin-memberships-row-checkbox.html", true) template = $template.get("admin/admin-memberships-row-checkbox.html", true)
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
@ -244,6 +243,8 @@ MembershipsRowAdminCheckboxDirective = ($log, $repo, $confirm, $template) ->
ctx = {inputId: "is-admin-#{member.id}"} ctx = {inputId: "is-admin-#{member.id}"}
html = template(ctx) html = template(ctx)
html = $compile(html)($scope)
$el.html(html) $el.html(html)
if not $attrs.tgMembershipsRowAdminCheckbox? if not $attrs.tgMembershipsRowAdminCheckbox?
@ -274,8 +275,8 @@ MembershipsRowAdminCheckboxDirective = ($log, $repo, $confirm, $template) ->
return {link: link} return {link: link}
module.directive("tgMembershipsRowAdminCheckbox", ["$log", "$tgRepo", "$tgConfirm", "$tgTemplate", module.directive("tgMembershipsRowAdminCheckbox", ["$log", "$tgRepo", "$tgConfirm",
MembershipsRowAdminCheckboxDirective]) "$tgTemplate", "$compile", MembershipsRowAdminCheckboxDirective])
############################################################################# #############################################################################
@ -338,32 +339,31 @@ module.directive("tgMembershipsRowRoleSelector", ["$log", "$tgRepo", "$tgConfirm
## Member Actions Directive ## Member Actions Directive
############################################################################# #############################################################################
MembershipsRowActionsDirective = ($log, $repo, $rs, $confirm) -> MembershipsRowActionsDirective = ($log, $repo, $rs, $confirm, $compile, $translate) ->
activedTemplate = _.template(""" activedTemplate = """
<div class="active"> <div class="active", translate="ADMIN.MEMBERSHIP.STATUS_ACTIVE">
Active
</div> </div>
<a class="delete" href=""> <a class="delete" href="">
<span class="icon icon-delete"></span> <span class="icon icon-delete"></span>
</a> </a>
""") # TODO: i18n """
pendingTemplate = _.template(""" pendingTemplate = """
<a class="pending" href=""> <a class="pending" href="">
Pending {{'ADMIN.MEMBERSHIP.STATUS_PENDING' | translate}}
<span class="icon icon-reload"></span> <span class="icon icon-reload"></span>
</a> </a>
<a class="delete" href="" title="Delete"> <a class="delete" href="">
<span class="icon icon-delete"></span> <span class="icon icon-delete"></span>
</a> </a>
""") # TODO: i18n """
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
render = (member) -> render = (member) ->
if member.user if member.user
html = activedTemplate() html = $compile(activedTemplate)($scope)
else else
html = pendingTemplate() html = $compile(pendingTemplate)($scope)
$el.html(html) $el.html(html)
@ -377,29 +377,34 @@ MembershipsRowActionsDirective = ($log, $repo, $rs, $confirm) ->
$el.on "click", ".pending", (event) -> $el.on "click", ".pending", (event) ->
event.preventDefault() event.preventDefault()
onSuccess = -> onSuccess = ->
# TODO: i18n text = $translate.instant("ADMIN.MEMBERSHIP.SUCCESS_SEND_INVITATION", {email: $scope.member.email})
$confirm.notify("success", "We've sent the invitationi again to '#{$scope.member.email}'.") $confirm.notify("success", text)
onError = -> onError = ->
$confirm.notify("error", "We haven't sent the invitation.") # TODO: i18n text = $translate.instant("ADMIM.MEMBERSHIP.ERROR_SEND_INVITATION")
$confirm.notify("error", text)
$rs.memberships.resendInvitation($scope.member.id).then(onSuccess, onError) $rs.memberships.resendInvitation($scope.member.id).then(onSuccess, onError)
$el.on "click", ".delete", (event) -> $el.on "click", ".delete", (event) ->
event.preventDefault() event.preventDefault()
title = "Delete member" # TODO: i18n title = $translate.instant("ADMIN.MEMBERSHIP.DELETE_MEMBER")
message = if member.user then member.full_name else "the invitation to #{member.email}" # TODO: i18n defaultMsg = $translate.instant("ADMIN.MEMBERSHIP.DEFAULT_DELETE_MESSAGE")
message = if member.user then member.full_name else defaultMsg
$confirm.askOnDelete(title, message).then (finish) -> $confirm.askOnDelete(title, message).then (finish) ->
onSuccess = -> onSuccess = ->
finish() finish()
$ctrl.loadMembers() $ctrl.loadMembers()
$confirm.notify("success", null, "We've deleted #{message}.") # TODO: i18n
text = $translate.instant("ADMIN.MEMBERSHIP.SUCCESS_DELETE")
$confirm.notify("success", null, text)
onError = -> onError = ->
finish(false) finish(false)
# TODO: i18in
$confirm.notify("error", null, "We have not been able to delete #{message}.") text = $translate.instant("ADMIN.MEMBERSHIP.ERROR_DELETE", {message: message})
$confirm.notify("error", null, text)
$repo.remove(member).then(onSuccess, onError) $repo.remove(member).then(onSuccess, onError)
@ -409,5 +414,4 @@ MembershipsRowActionsDirective = ($log, $repo, $rs, $confirm) ->
return {link: link} return {link: link}
module.directive("tgMembershipsRowActions", ["$log", "$tgRepo", "$tgResources", "$tgConfirm", module.directive("tgMembershipsRowActions", ["$log", "$tgRepo", "$tgResources", "$tgConfirm", "$compile", "$translate", MembershipsRowActionsDirective])
MembershipsRowActionsDirective])

View File

@ -47,21 +47,28 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin)
"$q", "$q",
"$tgLocation", "$tgLocation",
"$tgNavUrls", "$tgNavUrls",
"$appTitle" "$appTitle",
"$translate"
] ]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @appTitle) -> constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @appTitle, @translate) ->
@scope.project = {} @scope.project = {}
promise = @.loadInitialData() promise = @.loadInitialData()
promise.then => promise.then =>
@appTitle.set("Project profile - " + @scope.sectionName + " - " + @scope.project.name) sectionName = @translate.instant( @scope.sectionName)
appTitle = @translate.instant("ADMIN.PROJECT_PROFILE.PAGE_TITLE", {
sectionName: sectionName, projectName: @scope.project.name})
@appTitle.set(appTitle)
promise.then null, @.onInitialDataError.bind(@) promise.then null, @.onInitialDataError.bind(@)
@scope.$on "project:loaded", => @scope.$on "project:loaded", =>
@appTitle.set("Project profile - " + @scope.sectionName + " - " + @scope.project.name) sectionName = @translate.instant(@scope.sectionName)
appTitle = @translate.instant("ADMIN.PROJECT_PROFILE.PAGE_TITLE", {
sectionName: sectionName, projectName: @scope.project.name})
@appTitle.set(appTitle)
loadProject: -> loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) => return @rs.projects.get(@scope.projectId).then (project) =>
@ -79,12 +86,22 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin)
@scope.$emit('project:loaded', project) @scope.$emit('project:loaded', project)
return 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: -> loadInitialData: ->
promise = @repo.resolve({pslug: @params.pslug}).then (data) => promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project @scope.projectId = data.project
return data return data
return promise.then(=> @.loadProject()) return promise.then(=> @.loadProjectProfile())
openDeleteLightbox: -> openDeleteLightbox: ->
@rootscope.$broadcast("deletelightbox:new", @scope.project) @rootscope.$broadcast("deletelightbox:new", @scope.project)
@ -115,7 +132,7 @@ ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location) ->
$scope.$emit("project:loaded", $scope.project) $scope.$emit("project:loaded", $scope.project)
promise.then null, (data) -> promise.then null, (data) ->
$loading.finish(target) $loading.finish(submitButton)
form.setErrors(data) form.setErrors(data)
if data._error_message if data._error_message
$confirm.notify("error", data._error_message) $confirm.notify("error", data._error_message)
@ -126,7 +143,8 @@ ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location) ->
return {link:link} return {link:link}
module.directive("tgProjectProfile", ["$tgRepo", "$tgConfirm", "$tgLoading", "$tgNavUrls", "$tgLocation", ProjectProfileDirective]) module.directive("tgProjectProfile", ["$tgRepo", "$tgConfirm", "$tgLoading", "$tgNavUrls", "$tgLocation",
ProjectProfileDirective])
############################################################################# #############################################################################
## Project Default Values Directive ## Project Default Values Directive
@ -162,7 +180,8 @@ ProjectDefaultValuesDirective = ($repo, $confirm, $loading) ->
return {link:link} return {link:link}
module.directive("tgProjectDefaultValues", ["$tgRepo", "$tgConfirm", "$tgLoading", ProjectDefaultValuesDirective]) module.directive("tgProjectDefaultValues", ["$tgRepo", "$tgConfirm", "$tgLoading",
ProjectDefaultValuesDirective])
############################################################################# #############################################################################
## Project Modules Directive ## Project Modules Directive
@ -217,7 +236,7 @@ module.directive("tgProjectModules", ["$tgRepo", "$tgConfirm", "$tgLoading", Pro
## Project Export Directive ## Project Export Directive
############################################################################# #############################################################################
ProjectExportDirective = ($window, $rs, $confirm) -> ProjectExportDirective = ($window, $rs, $confirm, $translate) ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
buttonsEl = $el.find(".admin-project-export-buttons") buttonsEl = $el.find(".admin-project-export-buttons")
showButtons = -> buttonsEl.removeClass("hidden") showButtons = -> buttonsEl.removeClass("hidden")
@ -232,16 +251,23 @@ ProjectExportDirective = ($window, $rs, $confirm) ->
hideSpinner = -> spinnerEl.addClass("hidden") hideSpinner = -> spinnerEl.addClass("hidden")
resultTitleEl = $el.find(".result-title") resultTitleEl = $el.find(".result-title")
setLoadingTitle = -> resultTitleEl.html("We are generating your dump file") # TODO: i18n
setAsyncTitle = -> resultTitleEl.html("We are generating your dump file") # TODO: i18n
setSyncTitle = -> resultTitleEl.html("Your dump file is ready!") # TODO: i18n loading_title = $translate.instant("ADMIN.PROJECT_EXPORT.LOADING_TITLE")
loading_msg = $translate.instant("ADMIN.PROJECT_EXPORT.LOADING_MESSAGE")
dump_ready_text = -> resultTitleEl.html($translate.instant("ADMIN.PROJECT_EXPORT.DUMP_READY"))
asyn_message = -> resultTitleEl.html($translate.instant("ADMIN.PROJECT_EXPORT.ASYNC_MESSAGE"))
syn_message = (url) -> resultTitleEl.html($translate.instant("ADMIN.PROJECT_EXPORT.SYNC_MESSAGE", {
url: url}))
setLoadingTitle = -> resultTitleEl.html(loading_title)
setAsyncTitle = -> resultTitleEl.html(loading_msg)
setSyncTitle = -> resultTitleEl.html(dump_ready_text)
resultMessageEl = $el.find(".result-message ") resultMessageEl = $el.find(".result-message ")
setLoadingMessage = -> resultMessageEl.html("Please don't close this page.") # TODO: i18n setLoadingMessage = -> resultMessageEl.html(loading_msg)
setAsyncMessage = -> resultMessageEl.html("We will send you an email when ready.") # TODO: i18n setAsyncMessage = -> resultMessageEl.html(asyn_message)
setSyncMessage = (url) -> resultMessageEl.html("If the download doesn't start automatically click setSyncMessage = (url) -> resultMessageEl.html(syn_message(url))
<a href='#{url}' download title='Download
the dump file'>here.") # TODO: i18n
showLoadingMode = -> showLoadingMode = ->
showSpinner() showSpinner()
@ -279,15 +305,13 @@ ProjectExportDirective = ($window, $rs, $confirm) ->
onError = (result) => onError = (result) =>
showErrorMode() showErrorMode()
errorMsg = "Our oompa loompas have some problems generasting your dump. errorMsg = $translate.instant("ADMIN.PROJECT_EXPORT.ERROR")
Please try again. " # TODO: i18n
if result.status == 429 # TOO MANY REQUESTS if result.status == 429 # TOO MANY REQUESTS
errorMsg = "Sorry, our oompa loompas are very busy right now. errorMsg = $translate.instant("ADMIN.PROJECT_EXPORT.ERROR_BUSY")
Please try again in a few minutes. " # TODO: i18n
else if result.data?._error_message else if result.data?._error_message
errorMsg = "Our oompa loompas have some problems generasting your dump: errorMsg = $translate.instant("ADMIN.PROJECT_EXPORT.ERROR_BUSY", {
#{result.data._error_message}" # TODO: i18n message: result.data._error_message})
$confirm.notify("error", errorMsg) $confirm.notify("error", errorMsg)
@ -296,7 +320,8 @@ ProjectExportDirective = ($window, $rs, $confirm) ->
return {link:link} return {link:link}
module.directive("tgProjectExport", ["$window", "$tgResources", "$tgConfirm", ProjectExportDirective]) module.directive("tgProjectExport", ["$window", "$tgResources", "$tgConfirm", "$translate",
ProjectExportDirective])
############################################################################# #############################################################################
@ -310,9 +335,10 @@ class CsvExporterController extends taiga.Controller
"$tgUrls", "$tgUrls",
"$tgConfirm", "$tgConfirm",
"$tgResources", "$tgResources",
"$translate"
] ]
constructor: (@scope, @rootscope, @urls, @confirm, @rs) -> constructor: (@scope, @rootscope, @urls, @confirm, @rs, @translate) ->
@rootscope.$on("project:loaded", @.setCsvUuid) @rootscope.$on("project:loaded", @.setCsvUuid)
@scope.$watch "csvUuid", (value) => @scope.$watch "csvUuid", (value) =>
if value if value
@ -337,10 +363,10 @@ class CsvExporterController extends taiga.Controller
return promise return promise
regenerateUuid: -> regenerateUuid: ->
#TODO: i18n
if @scope.csvUuid if @scope.csvUuid
title = "Change URL" title = @translate.instant("ADMIN.REPORTS.REGENERATE_TITLE")
subtitle = "You going to change the CSV data access url. The previous url will be disabled. Are you sure?" subtitle = @translate.instant("ADMIN.REPORTS.REGENERATE_SUBTITLE")
@confirm.ask(title, subtitle).then @._generateUuid @confirm.ask(title, subtitle).then @._generateUuid
else else
@._generateUuid(_.identity) @._generateUuid(_.identity)
@ -361,3 +387,52 @@ class CsvExporterIssuesController extends CsvExporterController
module.controller("CsvExporterUserstoriesController", CsvExporterUserstoriesController) module.controller("CsvExporterUserstoriesController", CsvExporterUserstoriesController)
module.controller("CsvExporterTasksController", CsvExporterTasksController) module.controller("CsvExporterTasksController", CsvExporterTasksController)
module.controller("CsvExporterIssuesController", CsvExporterIssuesController) module.controller("CsvExporterIssuesController", CsvExporterIssuesController)
#############################################################################
## CSV Directive
#############################################################################
CsvUsDirective = ($translate) ->
link = ($scope) ->
$scope.sectionTitle = "ADMIN.CSV.SECTION_TITLE_US"
return {
controller: "CsvExporterUserstoriesController",
controllerAs: "ctrl",
templateUrl: "admin/project-csv.html",
link: link,
scope: true
}
module.directive("tgCsvUs", ["$translate", CsvUsDirective])
CsvTaskDirective = ($translate) ->
link = ($scope) ->
$scope.sectionTitle = "ADMIN.CSV.SECTION_TITLE_TASK"
return {
controller: "CsvExporterTasksController",
controllerAs: "ctrl",
templateUrl: "admin/project-csv.html",
link: link,
scope: true
}
module.directive("tgCsvTask", ["$translate", CsvTaskDirective])
CsvIssueDirective = ($translate) ->
link = ($scope) ->
$scope.sectionTitle = "ADMIN.CSV.SECTION_TITLE_ISSUE"
return {
controller: "CsvExporterIssuesController",
controllerAs: "ctrl",
templateUrl: "admin/project-csv.html",
link: link,
scope: true
}
module.directive("tgCsvIssue", ["$translate", CsvIssueDirective])

View File

@ -46,16 +46,24 @@ class ProjectValuesSectionController extends mixOf(taiga.Controller, taiga.PageM
"$q", "$q",
"$tgLocation", "$tgLocation",
"$tgNavUrls", "$tgNavUrls",
"$appTitle" "$appTitle",
"$translate"
] ]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @appTitle) -> constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @appTitle, @translate) ->
@scope.project = {} @scope.project = {}
promise = @.loadInitialData() promise = @.loadInitialData()
promise.then () => promise.then () =>
@appTitle.set("Project values - " + @scope.sectionName + " - " + @scope.project.name) sectionName = @translate.instant(@scope.sectionName)
title = @translate.instant("ADMIN.PROJECT_VALUES.APP_TITLE", {
"sectionName": sectionName,
"projectName": @scope.project.name
})
@appTitle.set(title)
promise.then null, @.onInitialDataError.bind(@) promise.then null, @.onInitialDataError.bind(@)
@ -118,7 +126,7 @@ module.controller("ProjectValuesController", ProjectValuesController)
## Project values directive ## Project values directive
############################################################################# #############################################################################
ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame) -> ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame, @translate, $rootscope) ->
## Drag & Drop Link ## Drag & Drop Link
linkDragAndDrop = ($scope, $el, $attrs) -> linkDragAndDrop = ($scope, $el, $attrs) ->
@ -149,6 +157,7 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame) ->
linkValue = ($scope, $el, $attrs) -> linkValue = ($scope, $el, $attrs) ->
$ctrl = $el.controller() $ctrl = $el.controller()
valueType = $attrs.type valueType = $attrs.type
objName = $attrs.objname
initializeNewValue = -> initializeNewValue = ->
$scope.newValue = { $scope.newValue = {
@ -157,7 +166,14 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame) ->
"is_archived": false "is_archived": false
} }
initializeTextTranslations = ->
$scope.addNewElementText = @translate.instant("ADMIN.PROJECT_VALUES_#{objName.toUpperCase()}.ACTION_ADD")
initializeNewValue() initializeNewValue()
initializeTextTranslations()
$rootscope.$on "$translateChangeEnd", ->
$scope.$evalAsync(initializeTextTranslations)
goToBottomList = (focus = false) => goToBottomList = (focus = false) =>
table = $el.find(".table-main") table = $el.find(".table-main")
@ -275,14 +291,12 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame) ->
if value.id != option.id if value.id != option.id
choices[option.id] = option.name choices[option.id] = option.name
#TODO: i18n
title = "Delete value"
subtitle = value.name subtitle = value.name
replacement = "All items with this value will be changed to"
if _.keys(choices).length == 0
return $confirm.error("You can't delete all values.")
return $confirm.askChoice(title, subtitle, choices, replacement).then (response) -> 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) ->
onSucces = -> onSucces = ->
$ctrl.loadValues().finally -> $ctrl.loadValues().finally ->
response.finish() response.finish()
@ -299,8 +313,7 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame) ->
return {link:link} return {link:link}
module.directive("tgProjectValues", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", "animationFrame", module.directive("tgProjectValues", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", "animationFrame", "$translate", "$rootScope", ProjectValuesDirective])
ProjectValuesDirective])
############################################################################# #############################################################################
@ -601,11 +614,9 @@ ProjectCustomAttributesDirective = ($log, $confirm, animationFrame) ->
deleteCustomAttribute = (formEl) -> deleteCustomAttribute = (formEl) ->
attr = formEl.scope().attr attr = formEl.scope().attr
title = "Delete custom attribute" # i18n
subtitle = "Remeber that all values in this custom field will be deleted.</br> Are you sure you want to continue?"
message = attr.name message = attr.name
$confirm.ask(title, subtitle, message).then (finish) ->
$confirm.ask("COMMON.CUSTOM_ATTRIBUTES.DELETE", "COMMON.CUSTOM_ATTRIBUTES.CONFIRM_DELETE", message).then (finish) ->
onSucces = -> onSucces = ->
$ctrl.loadCustomAttributes().finally -> $ctrl.loadCustomAttributes().finally ->
finish() finish()

View File

@ -44,20 +44,23 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil
"$q", "$q",
"$tgLocation", "$tgLocation",
"$tgNavUrls", "$tgNavUrls",
"$appTitle" "$appTitle",
"$translate"
] ]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @appTitle) -> constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @appTitle,
@translate) ->
bindMethods(@) bindMethods(@)
@scope.sectionName = "Permissions" #i18n @scope.sectionName = "ADMIN.MENU.PERMISSIONS"
@scope.project = {} @scope.project = {}
@scope.anyComputableRole = true @scope.anyComputableRole = true
promise = @.loadInitialData() promise = @.loadInitialData()
promise.then () => promise.then () =>
@appTitle.set("Roles - " + @scope.project.name) title = @translate.instant("ADMIN.ROLES.SECTION_NAME", {projectName: @scope.project.name})
@appTitle.set(title)
promise.then null, @.onInitialDataError.bind(@) promise.then null, @.onInitialDataError.bind(@)
@ -65,7 +68,7 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil
return @rs.projects.get(@scope.projectId).then (project) => return @rs.projects.get(@scope.projectId).then (project) =>
if not project.i_am_owner if not project.i_am_owner
@location.path(@navUrls.resolve("permission-denied")) @location.path(@navUrls.resolve("permission-denied"))
@scope.project = project @scope.project = project
@scope.$emit('project:loaded', project) @scope.$emit('project:loaded', project)
@ -80,7 +83,7 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil
return role return role
public_permission = { public_permission = {
"name": "External User", "name": @translate.instant("ADMIN.ROLES.EXTERNAL_USER"),
"permissions": @scope.project.public_permissions, "permissions": @scope.project.public_permissions,
"external_user": true "external_user": true
} }
@ -112,29 +115,28 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil
@scope.$broadcast("role:changed", @scope.role) @scope.$broadcast("role:changed", @scope.role)
delete: -> delete: ->
# TODO: i18n
title = "Delete Role" # TODO: i18n
subtitle = @scope.role.name
replacement = "All the users with this role will be moved to" # TODO: i18n
warning = "<strong>Be careful, all role estimations will be removed</strong>" # TODO: i18n
choices = {} choices = {}
for role in @scope.roles for role in @scope.roles
if role.id != @scope.role.id if role.id != @scope.role.id
choices[role.id] = role.name choices[role.id] = role.name
if _.keys(choices).length == 0 if _.keys(choices).length == 0
return @confirm.error("You can't delete all values.") # TODO: i18n return @confirm.error(@translate.instant("ADMIN.ROLES.ERROR_DELETE_ALL"))
title = @translate.instant("ADMIN.ROLES.TITLE_DELETE_ROLE")
subtitle = @scope.role.name
replacement = @translate.instant("ADMIN.ROLES.REPLACEMENT_ROLE")
warning = @translate.instant("ADMIN.ROLES.WARNING_DELETE_ROLE")
return @confirm.askChoice(title, subtitle, choices, replacement, warning).then (response) => return @confirm.askChoice(title, subtitle, choices, replacement, warning).then (response) =>
promise = @repo.remove(@scope.role, {moveTo: response.selected}) onSuccess = =>
promise.then =>
@.loadProject() @.loadProject()
@.loadRoles().finally -> @.loadRoles().finally =>
response.finish() response.finish()
promise.then null, => onError = =>
@confirm.notify('error') @confirm.notify('error')
return @repo.remove(@scope.role, {moveTo: response.selected}).then onSuccess, onError
setComputable: debounce 2000, -> setComputable: debounce 2000, ->
onSuccess = => onSuccess = =>
@confirm.notify("success") @confirm.notify("success")
@ -256,14 +258,14 @@ module.directive("tgNewRole", ["$tgRepo", "$tgConfirm", NewRoleDirective])
# Use category-config.scss styles # Use category-config.scss styles
RolePermissionsDirective = ($rootscope, $repo, $confirm) -> RolePermissionsDirective = ($rootscope, $repo, $confirm, $compile) ->
resumeTemplate = _.template(""" resumeTemplate = _.template("""
<div class="resume-title"><%- category.name %></div> <div class="resume-title" translate="<%- category.name %>"></div>
<div class="summary-role"> <div class="summary-role">
<div class="count"><%- category.activePermissions %>/<%- category.permissions.length %></div> <div class="count"><%- category.activePermissions %>/<%- category.permissions.length %></div>
<% _.each(category.permissions, function(permission) { %> <% _.each(category.permissions, function(permission) { %>
<div class="role-summary-single <% if(permission.active) { %>active<% } %>" <div class="role-summary-single <% if(permission.active) { %>active<% } %>"
title="<%- permission.description %>"></div> title="{{ '<%- permission.name %>' | translate }}"></div>
<% }) %> <% }) %>
</div> </div>
<div class="icon icon-arrow-bottom"></div> <div class="icon icon-arrow-bottom"></div>
@ -277,12 +279,14 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm) ->
<div class="items-container"> <div class="items-container">
<% _.each(category.permissions, function(permission) { %> <% _.each(category.permissions, function(permission) { %>
<div class="category-item" data-id="<%- permission.key %>"> <div class="category-item" data-id="<%- permission.key %>">
<span><%- permission.description %></span> <span translate="<%- permission.name %>"></span>
<div class="check"> <div class="check">
<input type="checkbox" <% if(!permission.editable) { %>disabled="disabled"<% } %> <% if(permission.active) { %>checked="checked"<% } %>/> <input type="checkbox"
<% if(!permission.editable) { %> disabled="disabled" <% } %>
<% if(permission.active) { %> checked="checked" <% } %>/>
<div></div> <div></div>
<span class="check-text check-yes">Yes</span> <span class="check-text check-yes" translate="COMMON.YES"></span>
<span class="check-text check-no">No</span> <span class="check-text check-no" translate="COMMON.NO"></span>
</div> </div>
</div> </div>
<% }) %> <% }) %>
@ -325,58 +329,73 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm) ->
categories = [] categories = []
milestonePermissions = [ milestonePermissions = [
{ key: "view_milestones", description: "View sprints" } { key: "view_milestones", name: "COMMON.PERMISIONS_CATEGORIES.SPRINTS.VIEW_SPRINTS"}
{ key: "add_milestone", description: "Add sprint" } { key: "add_milestone", name: "COMMON.PERMISIONS_CATEGORIES.SPRINTS.ADD_SPRINTS"}
{ key: "modify_milestone", description: "Modify sprint" } { key: "modify_milestone", name: "COMMON.PERMISIONS_CATEGORIES.SPRINTS.MODIFY_SPRINTS"}
{ key: "delete_milestone", description: "Delete sprint" } { key: "delete_milestone", name: "COMMON.PERMISIONS_CATEGORIES.SPRINTS.DELETE_SPRINTS"}
] ]
categories.push({ name: "Sprints", permissions: setActivePermissions(milestonePermissions) }) categories.push({
name: "COMMON.PERMISIONS_CATEGORIES.SPRINTS.NAME",
permissions: setActivePermissions(milestonePermissions)
})
userStoryPermissions = [ userStoryPermissions = [
{ key: "view_us", description: "View user story" } { key: "view_us", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.VIEW_USER_STORIES"}
{ key: "add_us", description: "Add user story" } { key: "add_us", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.ADD_USER_STORIES"}
{ key: "modify_us", description: "Modify user story" } { key: "modify_us", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.MODIFY_USER_STORIES"}
{ key: "delete_us", description: "Delete user story" } { key: "delete_us", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.DELETE_USER_STORIES"}
] ]
categories.push({ name: "User Stories", permissions: setActivePermissions(userStoryPermissions) }) categories.push({
name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.NAME",
permissions: setActivePermissions(userStoryPermissions)
})
taskPermissions = [ taskPermissions = [
{ key: "view_tasks", description: "View tasks" } { key: "view_tasks", name: "COMMON.PERMISIONS_CATEGORIES.TASKS.VIEW_TASKS"}
{ key: "add_task", description: "Add task" } { key: "add_task", name: "COMMON.PERMISIONS_CATEGORIES.TASKS.ADD_TASKS"}
{ key: "modify_task", description: "Modify task" } { key: "modify_task", name: "COMMON.PERMISIONS_CATEGORIES.TASKS.MODIFY_TASKS"}
{ key: "delete_task", description: "Delete task" } { key: "delete_task", name: "COMMON.PERMISIONS_CATEGORIES.TASKS.DELETE_TASKS"}
] ]
categories.push({ name: "Tasks", permissions: setActivePermissions(taskPermissions) }) categories.push({
name: "COMMON.PERMISIONS_CATEGORIES.TASKS.NAME" ,
permissions: setActivePermissions(taskPermissions)
})
issuePermissions = [ issuePermissions = [
{ key: "view_issues", description: "View issues" } { key: "view_issues", name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.VIEW_ISSUES"}
{ key: "add_issue", description: "Add issue" } { key: "add_issue", name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.ADD_ISSUES"}
{ key: "modify_issue", description: "Modify issue" } { key: "modify_issue", name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.MODIFY_ISSUES"}
{ key: "delete_issue", description: "Delete issue" } { key: "delete_issue", name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.DELETE_ISSUES"}
] ]
categories.push({ name: "Issues", permissions: setActivePermissions(issuePermissions) }) categories.push({
name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.NAME",
permissions: setActivePermissions(issuePermissions)
})
wikiPermissions = [ wikiPermissions = [
{ key: "view_wiki_pages", description: "View wiki pages" } { key: "view_wiki_pages", name: "COMMON.PERMISIONS_CATEGORIES.WIKI.VIEW_WIKI_PAGES"}
{ key: "add_wiki_page", description: "Add wiki page" } { key: "add_wiki_page", name: "COMMON.PERMISIONS_CATEGORIES.WIKI.ADD_WIKI_PAGES"}
{ key: "modify_wiki_page", description: "Modify wiki page" } { key: "modify_wiki_page", name: "COMMON.PERMISIONS_CATEGORIES.WIKI.MODIFY_WIKI_PAGES"}
{ key: "delete_wiki_page", description: "Delete wiki page" } { key: "delete_wiki_page", name: "COMMON.PERMISIONS_CATEGORIES.WIKI.DELETE_WIKI_PAGES"}
{ key: "view_wiki_links", description: "View wiki links" } { key: "view_wiki_links", name: "COMMON.PERMISIONS_CATEGORIES.WIKI.VIEW_WIKI_LINKS"}
{ key: "add_wiki_link", description: "Add wiki link" } { key: "add_wiki_link", name: "COMMON.PERMISIONS_CATEGORIES.WIKI.ADD_WIKI_LINKS"}
{ key: "delete_wiki_link", description: "Delete wiki link" } { key: "delete_wiki_link", name: "COMMON.PERMISIONS_CATEGORIES.WIKI.DELETE_WIKI_LINKS"}
] ]
categories.push({ name: "Wiki", permissions: setActivePermissions(wikiPermissions) }) categories.push({
name: "COMMON.PERMISIONS_CATEGORIES.WIKI.NAME",
permissions: setActivePermissions(wikiPermissions)
})
return setActivePermissionsPerCategory(categories) return setActivePermissionsPerCategory(categories)
renderResume = (element, category) -> renderResume = (element, category) ->
element.find(".resume").html(resumeTemplate({category: category})) element.find(".resume").html($compile(resumeTemplate({category: category}))($scope))
renderCategory = (category, index) -> renderCategory = (category, index) ->
html = categoryTemplate({category: category, index: index}) html = categoryTemplate({category: category, index: index})
html = angular.element(html) html = angular.element(html)
renderResume(html, category) renderResume(html, category)
return html return $compile(html)($scope)
renderPermissions = () -> renderPermissions = () ->
$el.off() $el.off()
@ -437,4 +456,5 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm) ->
return {link:link} return {link:link}
module.directive("tgRolePermissions", ["$rootScope", "$tgRepo", "$tgConfirm", RolePermissionsDirective]) module.directive("tgRolePermissions", ["$rootScope", "$tgRepo", "$tgConfirm", "$compile",
RolePermissionsDirective])

View File

@ -40,19 +40,21 @@ class WebhooksController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.
"$routeParams", "$routeParams",
"$tgLocation", "$tgLocation",
"$tgNavUrls", "$tgNavUrls",
"$appTitle" "$appTitle",
"$translate"
] ]
constructor: (@scope, @repo, @rs, @params, @location, @navUrls, @appTitle) -> constructor: (@scope, @repo, @rs, @params, @location, @navUrls, @appTitle, @translate) ->
bindMethods(@) bindMethods(@)
@scope.sectionName = "Webhooks" #i18n @scope.sectionName = "ADMIN.WEBHOOKS.SECTION_NAME"
@scope.project = {} @scope.project = {}
promise = @.loadInitialData() promise = @.loadInitialData()
promise.then () => promise.then () =>
@appTitle.set("Webhooks - " + @scope.project.name) text = @translate.instant("ADMIN.WEBHOOKS.APP_TITLE", {"projectName": @scope.project.name})
@appTitle.set(text)
promise.then null, @.onInitialDataError.bind(@) promise.then null, @.onInitialDataError.bind(@)
@ -85,17 +87,19 @@ module.controller("WebhooksController", WebhooksController)
## Webhook Directive ## Webhook Directive
############################################################################# #############################################################################
WebhookDirective = ($rs, $repo, $confirm, $loading) -> WebhookDirective = ($rs, $repo, $confirm, $loading, $translate) ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
webhook = $scope.$eval($attrs.tgWebhook) webhook = $scope.$eval($attrs.tgWebhook)
updateLogs = () -> updateLogs = () ->
prettyDate = $translate.instant("ADMIN.WEBHOOKS.DATE")
$rs.webhooklogs.list(webhook.id).then (webhooklogs) => $rs.webhooklogs.list(webhook.id).then (webhooklogs) =>
for log in webhooklogs for log in webhooklogs
log.validStatus = 200 <= log.status < 300 log.validStatus = 200 <= log.status < 300
log.prettySentHeaders = _.map(_.pairs(log.request_headers), ([header, value]) -> "#{header}: #{value}").join("\n") log.prettySentHeaders = _.map(_.pairs(log.request_headers), ([header, value]) -> "#{header}: #{value}").join("\n")
log.prettySentData = JSON.stringify(log.request_data) log.prettySentData = JSON.stringify(log.request_data)
log.prettyDate = moment(log.created).format("DD MMM YYYY [at] hh:mm:ss") # TODO: i18n log.prettyDate = moment(log.created).format(prettyDate)
webhook.logs_counter = webhooklogs.length webhook.logs_counter = webhooklogs.length
webhook.logs = webhooklogs webhook.logs = webhooklogs
@ -104,10 +108,16 @@ WebhookDirective = ($rs, $repo, $confirm, $loading) ->
updateShowHideHistoryText = () -> updateShowHideHistoryText = () ->
textElement = $el.find(".toggle-history") textElement = $el.find(".toggle-history")
historyElement = textElement.parents(".single-webhook-wrapper").find(".webhooks-history") historyElement = textElement.parents(".single-webhook-wrapper").find(".webhooks-history")
if historyElement.hasClass("open") if historyElement.hasClass("open")
textElement.text("(Hide history)") # TODO: i18n text = $translate.instant("ADMIN.WEBHOOKS.ACTION_HIDE_HISTORY")
title = $translate.instant("ADMIN.WEBHOOKS.ACTION_HIDE_HISTORY_TITLE")
else else
textElement.text("(Show history)") # TODO: i18n text = $translate.instant("ADMIN.WEBHOOKS.ACTION_SHOW_HISTORY")
title = $translate.instant("ADMIN.WEBHOOKS.ACTION_SHOW_HISTORY_TITLE")
textElement.text(text)
textElement.prop("title", title)
showVisualizationMode = () -> showVisualizationMode = () ->
$el.find(".edition-mode").addClass("hidden") $el.find(".edition-mode").addClass("hidden")
@ -161,8 +171,8 @@ WebhookDirective = ($rs, $repo, $confirm, $loading) ->
cancel(target) cancel(target)
$el.on "click", ".delete-webhook", () -> $el.on "click", ".delete-webhook", () ->
title = "Delete webhook" #TODO: i18n title = $translate.instant("ADMIN.WEBHOOKS.DELETE")
message = "Webhook '#{webhook.name}'" #TODO: i18n message = $translate.instant("ADMIN.WEBHOOKS.WEBHOOK_NAME", {name: webhook.name})
$confirm.askOnDelete(title, message).then (finish) => $confirm.askOnDelete(title, message).then (finish) =>
onSucces = -> onSucces = ->
@ -203,7 +213,7 @@ WebhookDirective = ($rs, $repo, $confirm, $loading) ->
return {link:link} return {link:link}
module.directive("tgWebhook", ["$tgResources", "$tgRepo", "$tgConfirm", "$tgLoading", WebhookDirective]) module.directive("tgWebhook", ["$tgResources", "$tgRepo", "$tgConfirm", "$tgLoading", "$translate", WebhookDirective])
############################################################################# #############################################################################
@ -279,19 +289,21 @@ class GithubController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
"$tgRepo", "$tgRepo",
"$tgResources", "$tgResources",
"$routeParams", "$routeParams",
"$appTitle" "$appTitle",
"$translate"
] ]
constructor: (@scope, @repo, @rs, @params, @appTitle) -> constructor: (@scope, @repo, @rs, @params, @appTitle, @translate) ->
bindMethods(@) bindMethods(@)
@scope.sectionName = "Github" #i18n @scope.sectionName = @translate.instant("ADMIN.GITHUB.SECTION_NAME")
@scope.project = {} @scope.project = {}
promise = @.loadInitialData() promise = @.loadInitialData()
promise.then () => promise.then () =>
@appTitle.set("Github - " + @scope.project.name) title = @translate.instant("ADMIN.GITHUB.APP_TITLE", {projectName: @scope.project.name})
@appTitle.set(title)
promise.then null, @.onInitialDataError.bind(@) promise.then null, @.onInitialDataError.bind(@)
@ -327,18 +339,20 @@ class GitlabController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
"$tgRepo", "$tgRepo",
"$tgResources", "$tgResources",
"$routeParams", "$routeParams",
"$appTitle" "$appTitle",
"$translate"
] ]
constructor: (@scope, @repo, @rs, @params, @appTitle) -> constructor: (@scope, @repo, @rs, @params, @appTitle, @translate) ->
bindMethods(@) bindMethods(@)
@scope.sectionName = "Gitlab" #i18n @scope.sectionName = @translate.instant("ADMIN.GITLAB.SECTION_NAME")
@scope.project = {} @scope.project = {}
promise = @.loadInitialData() promise = @.loadInitialData()
promise.then () => promise.then () =>
@appTitle.set("Gitlab - " + @scope.project.name) title = @translate.instant("ADMIN.GITLAB.APP_TITLE", {projectName: @scope.project.name})
@appTitle.set(title)
promise.then null, @.onInitialDataError.bind(@) promise.then null, @.onInitialDataError.bind(@)
@ -377,18 +391,20 @@ class BitbucketController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
"$tgRepo", "$tgRepo",
"$tgResources", "$tgResources",
"$routeParams", "$routeParams",
"$appTitle" "$appTitle",
"$translate"
] ]
constructor: (@scope, @repo, @rs, @params, @appTitle) -> constructor: (@scope, @repo, @rs, @params, @appTitle, @translate) ->
bindMethods(@) bindMethods(@)
@scope.sectionName = "Bitbucket" #i18n @scope.sectionName = @translate.instant("ADMIN.BITBUCKET.SECTION_NAME")
@scope.project = {} @scope.project = {}
promise = @.loadInitialData() promise = @.loadInitialData()
promise.then () => promise.then () =>
@appTitle.set("Bitbucket - " + @scope.project.name) title = @translate.instant("ADMIN.BITBUCKET.APP_TITLE", {projectName: @scope.project.name})
@appTitle.set(title)
promise.then null, @.onInitialDataError.bind(@) promise.then null, @.onInitialDataError.bind(@)

View File

@ -34,11 +34,17 @@ class AuthService extends taiga.Service
"$tgModel", "$tgModel",
"$tgResources", "$tgResources",
"$tgHttp", "$tgHttp",
"$tgUrls"] "$tgUrls",
"$tgConfig",
"$translate"]
constructor: (@rootscope, @storage, @model, @rs, @http, @urls) -> constructor: (@rootscope, @storage, @model, @rs, @http, @urls, @config, @translate) ->
super() super()
_setLocales: ->
lang = @rootscope.user.lang || @config.get("defaultLanguage") || "en"
@translate.use(lang)
getUser: -> getUser: ->
if @rootscope.user if @rootscope.user
return @rootscope.user return @rootscope.user
@ -47,16 +53,18 @@ class AuthService extends taiga.Service
if userData if userData
user = @model.make_model("users", userData) user = @model.make_model("users", userData)
@rootscope.user = user @rootscope.user = user
@._setLocales()
return user return user
return null return null
setUser: (user) -> setUser: (user) ->
@rootscope.auth = user @rootscope.auth = user
@rootscope.$broadcast("i18n:change", user.default_language)
@storage.set("userInfo", user.getAttrs()) @storage.set("userInfo", user.getAttrs())
@rootscope.user = user @rootscope.user = user
@._setLocales()
clear: -> clear: ->
@rootscope.auth = null @rootscope.auth = null
@rootscope.user = null @rootscope.user = null
@ -171,7 +179,7 @@ PublicRegisterMessageDirective = ($config, $navUrls, templates) ->
module.directive("tgPublicRegisterMessage", ["$tgConfig", "$tgNavUrls", "$tgTemplate", PublicRegisterMessageDirective]) module.directive("tgPublicRegisterMessage", ["$tgConfig", "$tgNavUrls", "$tgTemplate", PublicRegisterMessageDirective])
LoginDirective = ($auth, $confirm, $location, $config, $routeParams, $navUrls, $events) -> LoginDirective = ($auth, $confirm, $location, $config, $routeParams, $navUrls, $events, $translate) ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
onSuccess = (response) -> onSuccess = (response) ->
if $routeParams['next'] and $routeParams['next'] != $navUrls.resolve("login") if $routeParams['next'] and $routeParams['next'] != $navUrls.resolve("login")
@ -183,8 +191,8 @@ LoginDirective = ($auth, $confirm, $location, $config, $routeParams, $navUrls, $
$location.path(nextUrl) $location.path(nextUrl)
onError = (response) -> onError = (response) ->
$confirm.notify("light-error", "According to our Oompa Loompas, your username/email $confirm.notify("light-error", $translate.instant("LOGIN_FORM.ERROR_AUTH_INCORRECT"))
or password are incorrect.") #TODO: i18n
submit = debounce 2000, (event) => submit = debounce 2000, (event) =>
event.preventDefault() event.preventDefault()
@ -207,13 +215,13 @@ LoginDirective = ($auth, $confirm, $location, $config, $routeParams, $navUrls, $
return {link:link} return {link:link}
module.directive("tgLogin", ["$tgAuth", "$tgConfirm", "$tgLocation", "$tgConfig", "$routeParams", module.directive("tgLogin", ["$tgAuth", "$tgConfirm", "$tgLocation", "$tgConfig", "$routeParams",
"$tgNavUrls", "$tgEvents", LoginDirective]) "$tgNavUrls", "$tgEvents", "$translate", LoginDirective])
############################################################################# #############################################################################
## Register Directive ## Register Directive
############################################################################# #############################################################################
RegisterDirective = ($auth, $confirm, $location, $navUrls, $config, $analytics) -> RegisterDirective = ($auth, $confirm, $location, $navUrls, $config, $analytics, $translate) ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
if not $config.get("publicRegisterEnabled") if not $config.get("publicRegisterEnabled")
$location.path($navUrls.resolve("not-found")) $location.path($navUrls.resolve("not-found"))
@ -224,12 +232,15 @@ RegisterDirective = ($auth, $confirm, $location, $navUrls, $config, $analytics)
onSuccessSubmit = (response) -> onSuccessSubmit = (response) ->
$analytics.trackEvent("auth", "register", "user registration", 1) $analytics.trackEvent("auth", "register", "user registration", 1)
$confirm.notify("success", "Our Oompa Loompas are happy, welcome to Taiga.") #TODO: i18n
$confirm.notify("success", $translate.instant("LOGIN_FORM.SUCCESS"))
$location.path($navUrls.resolve("home")) $location.path($navUrls.resolve("home"))
onErrorSubmit = (response) -> onErrorSubmit = (response) ->
if response.data._error_message? if response.data._error_message?
$confirm.notify("light-error", "According to our Oompa Loompas there was an error. #{response.data._error_message}") #TODO: i18n text = $translate.instant("LOGIN_FORM.ERROR_GENERIC") + " " + response.data._error_message
$confirm.notify("light-error", text + " " + response.data._error_message)
form.setErrors(response.data) form.setErrors(response.data)
@ -247,25 +258,27 @@ RegisterDirective = ($auth, $confirm, $location, $navUrls, $config, $analytics)
return {link:link} return {link:link}
module.directive("tgRegister", ["$tgAuth", "$tgConfirm", "$tgLocation", "$tgNavUrls", "$tgConfig", module.directive("tgRegister", ["$tgAuth", "$tgConfirm", "$tgLocation", "$tgNavUrls", "$tgConfig",
"$tgAnalytics", RegisterDirective]) "$tgAnalytics", "$translate", RegisterDirective])
############################################################################# #############################################################################
## Forgot Password Directive ## Forgot Password Directive
############################################################################# #############################################################################
ForgotPasswordDirective = ($auth, $confirm, $location, $navUrls) -> ForgotPasswordDirective = ($auth, $confirm, $location, $navUrls, $translate) ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
$scope.data = {} $scope.data = {}
form = $el.find("form").checksley() form = $el.find("form").checksley()
onSuccessSubmit = (response) -> onSuccessSubmit = (response) ->
$location.path($navUrls.resolve("login")) $location.path($navUrls.resolve("login"))
$confirm.success("<strong>Check your inbox!</strong><br />
We have sent you an email with the instructions to set a new password") #TODO: i18n text = $translate.instant("FORGOT_PASSWORD_FORM.SUCCESS")
$confirm.success(text)
onErrorSubmit = (response) -> onErrorSubmit = (response) ->
$confirm.notify("light-error", "According to our Oompa Loompas, text = $translate.instant("FORGOT_PASSWORD_FORM.ERROR")
your are not registered yet.") #TODO: i18n
$confirm.notify("light-error", text)
submit = debounce 2000, (event) => submit = debounce 2000, (event) =>
event.preventDefault() event.preventDefault()
@ -280,14 +293,14 @@ ForgotPasswordDirective = ($auth, $confirm, $location, $navUrls) ->
return {link:link} return {link:link}
module.directive("tgForgotPassword", ["$tgAuth", "$tgConfirm", "$tgLocation", "$tgNavUrls", module.directive("tgForgotPassword", ["$tgAuth", "$tgConfirm", "$tgLocation", "$tgNavUrls", "$translate",
ForgotPasswordDirective]) ForgotPasswordDirective])
############################################################################# #############################################################################
## Change Password from Recovery Directive ## Change Password from Recovery Directive
############################################################################# #############################################################################
ChangePasswordFromRecoveryDirective = ($auth, $confirm, $location, $params, $navUrls) -> ChangePasswordFromRecoveryDirective = ($auth, $confirm, $location, $params, $navUrls, $translate) ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
$scope.data = {} $scope.data = {}
@ -301,12 +314,15 @@ ChangePasswordFromRecoveryDirective = ($auth, $confirm, $location, $params, $nav
onSuccessSubmit = (response) -> onSuccessSubmit = (response) ->
$location.path($navUrls.resolve("login")) $location.path($navUrls.resolve("login"))
$confirm.success("Our Oompa Loompas saved your new password.<br />
Try to <strong>sign in</strong> with it.") #TODO: i18n text = $translate.instant("CHANGE_PASSWORD_RECOVERY_FORM.SUCCESS")
$confirm.success(text)
onErrorSubmit = (response) -> onErrorSubmit = (response) ->
$confirm.notify("light-error", "One of our Oompa Loompas say text = $translate.instant("COMMON.GENERIC_ERROR", {error: response.data._error_message})
'#{response.data._error_message}'.") #TODO: i18n
$confirm.notify("light-error", text)
submit = debounce 2000, (event) => submit = debounce 2000, (event) =>
event.preventDefault() event.preventDefault()
@ -322,13 +338,13 @@ ChangePasswordFromRecoveryDirective = ($auth, $confirm, $location, $params, $nav
return {link:link} return {link:link}
module.directive("tgChangePasswordFromRecovery", ["$tgAuth", "$tgConfirm", "$tgLocation", "$routeParams", module.directive("tgChangePasswordFromRecovery", ["$tgAuth", "$tgConfirm", "$tgLocation", "$routeParams",
"$tgNavUrls", ChangePasswordFromRecoveryDirective]) "$tgNavUrls", "$translate", ChangePasswordFromRecoveryDirective])
############################################################################# #############################################################################
## Invitation ## Invitation
############################################################################# #############################################################################
InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics) -> InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics, $translate) ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
token = $params.token token = $params.token
@ -338,8 +354,9 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics
promise.then null, (response) -> promise.then null, (response) ->
$location.path($navUrls.resolve("login")) $location.path($navUrls.resolve("login"))
$confirm.success("<strong>Ooops, we have a problem</strong><br />
Our Oompa Loompas can't find your invitation.") #TODO: i18n text = $translate.instant("INVITATION_LOGIN_FORM.NOT_FOUND")
$confirm.success(text)
# Login form # Login form
$scope.dataLogin = {token: token} $scope.dataLogin = {token: token}
@ -348,12 +365,14 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics
onSuccessSubmitLogin = (response) -> onSuccessSubmitLogin = (response) ->
$analytics.trackEvent("auth", "invitationAccept", "invitation accept with existing user", 1) $analytics.trackEvent("auth", "invitationAccept", "invitation accept with existing user", 1)
$location.path($navUrls.resolve("project", {project: $scope.invitation.project_slug})) $location.path($navUrls.resolve("project", {project: $scope.invitation.project_slug}))
$confirm.notify("success", "You've successfully joined this project", text = $translate.instant("INVITATION_LOGIN_FORM.SUCCESS", {"project_name": $scope.invitation.project_name})
"Welcome to #{_.escape($scope.invitation.project_name)}")
$confirm.notify("success", text)
onErrorSubmitLogin = (response) -> onErrorSubmitLogin = (response) ->
$confirm.notify("light-error", "According to our Oompa Loompas, your are not registered yet or text = $translate.instant("INVITATION_LOGIN_FORM.ERROR")
typed an invalid password.") #TODO: i18n
$confirm.notify("light-error", text)
submitLogin = debounce 2000, (event) => submitLogin = debounce 2000, (event) =>
event.preventDefault() event.preventDefault()
@ -378,8 +397,9 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics
"Welcome to #{_.escape($scope.invitation.project_name)}") "Welcome to #{_.escape($scope.invitation.project_name)}")
onErrorSubmitRegister = (response) -> onErrorSubmitRegister = (response) ->
$confirm.notify("light-error", "According to our Oompa Loompas, that text = $translate.instant("LOGIN_FORM.ERROR_AUTH_INCORRECT")
username or email is already in use.") #TODO: i18n
$confirm.notify("light-error", text)
submitRegister = debounce 2000, (event) => submitRegister = debounce 2000, (event) =>
event.preventDefault() event.preventDefault()
@ -396,13 +416,13 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics
return {link:link} return {link:link}
module.directive("tgInvitation", ["$tgAuth", "$tgConfirm", "$tgLocation", "$routeParams", module.directive("tgInvitation", ["$tgAuth", "$tgConfirm", "$tgLocation", "$routeParams",
"$tgNavUrls", "$tgAnalytics", InvitationDirective]) "$tgNavUrls", "$tgAnalytics", "$translate", InvitationDirective])
############################################################################# #############################################################################
## Change Email ## Change Email
############################################################################# #############################################################################
ChangeEmailDirective = ($repo, $model, $auth, $confirm, $location, $params, $navUrls) -> ChangeEmailDirective = ($repo, $model, $auth, $confirm, $location, $params, $navUrls, $translate) ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
$scope.data = {} $scope.data = {}
$scope.data.email_token = $params.email_token $scope.data.email_token = $params.email_token
@ -412,11 +432,14 @@ ChangeEmailDirective = ($repo, $model, $auth, $confirm, $location, $params, $nav
$repo.queryOne("users", $auth.getUser().id).then (data) => $repo.queryOne("users", $auth.getUser().id).then (data) =>
$auth.setUser(data) $auth.setUser(data)
$location.path($navUrls.resolve("home")) $location.path($navUrls.resolve("home"))
$confirm.success("Our Oompa Loompas updated your email") #TODO: i18n
text = $translate.instant("CHANGE_EMAIL_FORM.SUCCESS")
$confirm.success(text)
onErrorSubmit = (response) -> onErrorSubmit = (response) ->
$confirm.notify("error", "One of our Oompa Loompas says text = $translate.instant("COMMON.GENERIC_ERROR", {error: response.data._error_message})
'#{response.data._error_message}'.") #TODO: i18n
$confirm.notify("light-error", text)
submit = -> submit = ->
if not form.validate() if not form.validate()
@ -436,7 +459,7 @@ ChangeEmailDirective = ($repo, $model, $auth, $confirm, $location, $params, $nav
return {link:link} return {link:link}
module.directive("tgChangeEmail", ["$tgRepo", "$tgModel", "$tgAuth", "$tgConfirm", "$tgLocation", "$routeParams", module.directive("tgChangeEmail", ["$tgRepo", "$tgModel", "$tgAuth", "$tgConfirm", "$tgLocation", "$routeParams",
"$tgNavUrls", ChangeEmailDirective]) "$tgNavUrls", "$translate", ChangeEmailDirective])
############################################################################# #############################################################################
## Cancel account ## Cancel account
@ -451,11 +474,15 @@ CancelAccountDirective = ($repo, $model, $auth, $confirm, $location, $params, $n
onSuccessSubmit = (response) -> onSuccessSubmit = (response) ->
$auth.logout() $auth.logout()
$location.path($navUrls.resolve("home")) $location.path($navUrls.resolve("home"))
$confirm.success("Our Oompa Loompas removed your account") #TODO: i18n
text = $translate.instant("CANCEL_ACCOUNT.SUCCESS")
$confirm.success(text)
onErrorSubmit = (response) -> onErrorSubmit = (response) ->
$confirm.notify("error", "One of our Oompa Loompas says text = $translate.instant("COMMON.GENERIC_ERROR", {error: response.data._error_message})
'#{response.data._error_message}'.") #TODO: i18n
$confirm.notify("error", text)
submit = debounce 2000, (event) => submit = debounce 2000, (event) =>
event.preventDefault() event.preventDefault()

View File

@ -116,6 +116,9 @@ BacklogFiltersDirective = ($log, $location, $templates) ->
$scope.$on "filters:loaded", (ctx, filters) -> $scope.$on "filters:loaded", (ctx, filters) ->
initializeSelectedFilters(filters) initializeSelectedFilters(filters)
$scope.$on "filters:update", (ctx, filters) ->
renderFilters(filters)
## Dom Event Handlers ## Dom Event Handlers
$el.on "click", ".filters-cats > ul > li > a", (event) -> $el.on "click", ".filters-cats > ul > li > a", (event) ->
event.preventDefault() event.preventDefault()

View File

@ -29,7 +29,7 @@ module = angular.module("taigaBacklog")
## Creare/Edit Sprint Lightbox Directive ## Creare/Edit Sprint Lightbox Directive
############################################################################# #############################################################################
CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading) -> CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading, $translate) ->
link = ($scope, $el, attrs) -> link = ($scope, $el, attrs) ->
hasErrors = false hasErrors = false
createSprint = true createSprint = true
@ -43,8 +43,10 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading)
submit = debounce 2000, (event) => submit = debounce 2000, (event) =>
event.preventDefault() event.preventDefault()
target = angular.element(event.currentTarget) target = angular.element(event.currentTarget)
prettyDate = $translate.instant("COMMON.PICKERDATE.FORMAT")
submitButton = $el.find(".submit-button")
form = $el.find("form").checksley() form = $el.find("form").checksley()
if not form.validate() if not form.validate()
@ -57,13 +59,15 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading)
broadcastEvent = null broadcastEvent = null
if createSprint if createSprint
newSprint.estimated_start = moment(newSprint.estimated_start).format("YYYY-MM-DD") newSprint.estimated_start = moment(newSprint.estimated_start, prettyDate).format("YYYY-MM-DD")
newSprint.estimated_finish = moment(newSprint.estimated_finish).format("YYYY-MM-DD") newSprint.estimated_finish = moment(newSprint.estimated_finish,prettyDate).format("YYYY-MM-DD")
promise = $repo.create("milestones", newSprint) promise = $repo.create("milestones", newSprint)
broadcastEvent = "sprintform:create:success" broadcastEvent = "sprintform:create:success"
else else
newSprint.setAttr("estimated_start", moment(newSprint.estimated_start).format("YYYY-MM-DD")) newSprint.setAttr("estimated_start",
newSprint.setAttr("estimated_finish", moment(newSprint.estimated_finish).format("YYYY-MM-DD")) moment(newSprint.estimated_start, prettyDate).format("YYYY-MM-DD"))
newSprint.setAttr("estimated_finish",
moment(newSprint.estimated_finish, prettyDate).format("YYYY-MM-DD"))
promise = $repo.save(newSprint) promise = $repo.save(newSprint)
broadcastEvent = "sprintform:edit:success" broadcastEvent = "sprintform:edit:success"
@ -86,8 +90,7 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading)
$confirm.notify("light-error", data.__all__[0]) $confirm.notify("light-error", data.__all__[0])
remove = -> remove = ->
#TODO: i18n title = $translate.instant("LIGHTBOX.DELETE_SPRINT.TITLE")
title = "Delete sprint"
message = $scope.sprint.name message = $scope.sprint.name
$confirm.askOnDelete(title, message).then (finish) => $confirm.askOnDelete(title, message).then (finish) =>
@ -107,6 +110,7 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading)
form.reset() form.reset()
createSprint = true createSprint = true
prettyDate = $translate.instant("COMMON.PICKERDATE.FORMAT")
$scope.sprint.project = projectId $scope.sprint.project = projectId
$scope.sprint.name = null $scope.sprint.name = null
$scope.sprint.slug = null $scope.sprint.slug = null
@ -118,36 +122,50 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading)
estimatedStart = moment($scope.sprint.estimated_start) estimatedStart = moment($scope.sprint.estimated_start)
else if lastSprint? else if lastSprint?
estimatedStart = moment(lastSprint.estimated_finish) estimatedStart = moment(lastSprint.estimated_finish)
$scope.sprint.estimated_start = estimatedStart.format("DD MMM YYYY") $scope.sprint.estimated_start = estimatedStart.format(prettyDate)
estimatedFinish = moment().add(2, "weeks") estimatedFinish = moment().add(2, "weeks")
if $scope.sprint.estimated_finish if $scope.sprint.estimated_finish
estimatedFinish = moment($scope.sprint.estimated_finish) estimatedFinish = moment($scope.sprint.estimated_finish)
else if lastSprint? else if lastSprint?
estimatedFinish = moment(lastSprint.estimated_finish).add(2, "weeks") estimatedFinish = moment(lastSprint.estimated_finish).add(2, "weeks")
$scope.sprint.estimated_finish = estimatedFinish.format("DD MMM YYYY") $scope.sprint.estimated_finish = estimatedFinish.format(prettyDate)
lastSprintNameDom = $el.find(".last-sprint-name") lastSprintNameDom = $el.find(".last-sprint-name")
if lastSprint?.name? if lastSprint?.name?
lastSprintNameDom.html(" last sprint is <strong> #{lastSprint.name} ;-) </strong>") text = $translate.instant("LIGHTBOX.ADD_EDIT_SPRINT.LAST_SPRINT_NAME", {
lastSprint: lastSprint.name})
lastSprintNameDom.html(text)
$el.find(".delete-sprint").addClass("hidden") $el.find(".delete-sprint").addClass("hidden")
$el.find(".title").text("New sprint") #TODO i18n
$el.find(".button-green").text("Create") #TODO i18n text = $translate.instant("LIGHTBOX.ADD_EDIT_SPRINT.TITLE")
$el.find(".title").text(text)
text = $translate.instant("COMMON.CREATE")
$el.find(".button-green").text(text)
lightboxService.open($el) lightboxService.open($el)
$el.find(".sprint-name").focus() $el.find(".sprint-name").focus()
$el.find(".last-sprint-name").removeClass("disappear") $el.find(".last-sprint-name").removeClass("disappear")
$scope.$on "sprintform:edit", (ctx, sprint) -> $scope.$on "sprintform:edit", (ctx, sprint) ->
createSprint = false createSprint = false
prettyDate = $translate.instant("COMMON.PICKERDATE.FORMAT")
$scope.$apply -> $scope.$apply ->
$scope.sprint = sprint $scope.sprint = sprint
$scope.sprint.estimated_start = moment($scope.sprint.estimated_start).format("DD MMM YYYY") $scope.sprint.estimated_start = moment($scope.sprint.estimated_start).format(prettyDate)
$scope.sprint.estimated_finish = moment($scope.sprint.estimated_finish).format("DD MMM YYYY") $scope.sprint.estimated_finish = moment($scope.sprint.estimated_finish).format(prettyDate)
$el.find(".delete-sprint").removeClass("hidden") $el.find(".delete-sprint").removeClass("hidden")
$el.find(".title").text("Edit sprint") #TODO i18n
$el.find(".button-green").text("Save") #TODO i18n editSprint = $translate.instant("BACKLOG.EDIT_SPRINT")
$el.find(".title").text(editSprint)
save = $translate.instant("COMMON.SAVE")
$el.find(".button-green").text(save)
lightboxService.open($el) lightboxService.open($el)
$el.find(".sprint-name").focus().select() $el.find(".sprint-name").focus().select()
$el.find(".last-sprint-name").addClass("disappear") $el.find(".last-sprint-name").addClass("disappear")
@ -158,8 +176,6 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading)
else else
$el.find(".last-sprint-name").removeClass("disappear") $el.find(".last-sprint-name").removeClass("disappear")
submitButton = $el.find(".submit-button")
$el.on "submit", "form", submit $el.on "submit", "form", submit
$el.on "click", ".delete-sprint .icon-delete", (event) -> $el.on "click", ".delete-sprint .icon-delete", (event) ->
@ -178,6 +194,7 @@ module.directive("tgLbCreateEditSprint", [
"$tgResources", "$tgResources",
"$rootScope", "$rootScope",
"lightboxService" "lightboxService"
"$tgLoading" "$tgLoading",
"$translate",
CreateEditSprint CreateEditSprint
]) ])

View File

@ -49,14 +49,15 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
"$tgNavUrls", "$tgNavUrls",
"$tgEvents", "$tgEvents",
"$tgAnalytics", "$tgAnalytics",
"tgLoader" "tgLoader",
"$translate"
] ]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q,
@location, @appTitle, @navUrls, @events, @analytics, tgLoader) -> @location, @appTitle, @navUrls, @events, @analytics, tgLoader, @translate) ->
bindMethods(@) bindMethods(@)
@scope.sectionName = "Backlog" @scope.sectionName = @translate.instant("BACKLOG.SECTION_NAME")
@showTags = false @showTags = false
@activeFilters = false @activeFilters = false
@ -439,12 +440,16 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
return _.pick(@location.search(), "statuses", "tags", "q") return _.pick(@location.search(), "statuses", "tags", "q")
generateFilters: -> generateFilters: ->
urlfilters = @.getUrlFilters()
@scope.filters = {} @scope.filters = {}
#tags #tags
plainTags = _.flatten(_.filter(_.map(@scope.visibleUserstories, "tags"))) plainTags = _.flatten(_.filter(_.map(@scope.visibleUserstories, "tags")))
plainTags.sort() plainTags.sort()
if plainTags.length == 0 and urlfilters["tags"]
plainTags.push(urlfilters["tags"])
@scope.filters.tags = _.map _.countBy(plainTags), (v, k) => @scope.filters.tags = _.map _.countBy(plainTags), (v, k) =>
obj = { obj = {
id: k, id: k,
@ -466,6 +471,9 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
if status if status
return status return status
if plainStatuses.length == 0 and urlfilters["statuses"]
plainStatuses.push(urlfilters["statuses"])
@scope.filters.statuses = _.map _.countBy(plainStatuses), (v, k) => @scope.filters.statuses = _.map _.countBy(plainStatuses), (v, k) =>
obj = { obj = {
id: k, id: k,
@ -491,12 +499,19 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
## Template actions ## Template actions
updateUserStoryStatus: () ->
@.setSearchDataFilters()
@.filterVisibleUserstories()
@.generateFilters()
@rootscope.$broadcast("filters:update", @scope.filters['statuses'])
@.loadProjectStats()
editUserStory: (us) -> editUserStory: (us) ->
@rootscope.$broadcast("usform:edit", us) @rootscope.$broadcast("usform:edit", us)
deleteUserStory: (us) -> deleteUserStory: (us) ->
#TODO: i18n title = @translate.instant("US.TITLE_DELETE_ACTION")
title = "Delete User Story"
message = us.subject message = us.subject
@confirm.askOnDelete(title, message).then (finish) => @confirm.askOnDelete(title, message).then (finish) =>
@ -527,12 +542,11 @@ module.controller("BacklogController", BacklogController)
## Backlog Directive ## Backlog Directive
############################################################################# #############################################################################
BacklogDirective = ($repo, $rootscope) -> BacklogDirective = ($repo, $rootscope, $translate) ->
## Doom line Link ## Doom line Link
doomLineTemplate = _.template(""" doomLineTemplate = _.template("""
<div class="doom-line"><span>Project Scope [Doomline]</span></div> <div class="doom-line"><span><%- text %></span></div>
""") """)
# TODO: i18n
linkDoomLine = ($scope, $el, $attrs, $ctrl) -> linkDoomLine = ($scope, $el, $attrs, $ctrl) ->
reloadDoomLine = -> reloadDoomLine = ->
@ -559,7 +573,8 @@ BacklogDirective = ($repo, $rootscope) ->
$el.find(".doom-line").remove() $el.find(".doom-line").remove()
addDoomLineDom = (element) -> addDoomLineDom = (element) ->
$(element).before(doomLineTemplate({})) text = $translate.instant("BACKLOG.DOOMLINE")
$(element).before(doomLineTemplate({"text": text}))
getUsItems = -> getUsItems = ->
rowElements = $el.find('.backlog-table-body .us-item-row') rowElements = $el.find('.backlog-table-body .us-item-row')
@ -629,10 +644,14 @@ BacklogDirective = ($repo, $rootscope) ->
if $ctrl.showTags if $ctrl.showTags
elm.addClass("active") elm.addClass("active")
elm.find(".text").text("Hide Tags") # TODO: i18n
text = $translate.instant("BACKLOG.TAGS.HIDE")
elm.find(".text").text(text)
else else
elm.removeClass("active") elm.removeClass("active")
elm.find(".text").text("Show Tags") # TODO: i18n
text = $translate.instant("BACKLOG.TAGS.SHOW")
elm.find(".text").text(text)
showHideFilter = ($scope, $el, $ctrl) -> showHideFilter = ($scope, $el, $ctrl) ->
sidebar = $el.find("sidebar.filters-bar") sidebar = $el.find("sidebar.filters-bar")
@ -646,7 +665,10 @@ BacklogDirective = ($repo, $rootscope) ->
sidebar.toggleClass("active") sidebar.toggleClass("active")
target.toggleClass("active") target.toggleClass("active")
toggleText(target.find(".text"), ["Remove Filters", "Show Filters"]) # TODO: i18n hideText = $translate.instant("BACKLOG.FILTERS.HIDE")
showText = $translate.instant("BACKLOG.FILTERS.SHOW")
toggleText(target.find(".text"), [hideText, showText])
if !sidebar.hasClass("active") if !sidebar.hasClass("active")
$ctrl.resetFilters() $ctrl.resetFilters()
@ -687,14 +709,13 @@ BacklogDirective = ($repo, $rootscope) ->
return {link: link} return {link: link}
module.directive("tgBacklog", ["$tgRepo", "$rootScope", BacklogDirective]) module.directive("tgBacklog", ["$tgRepo", "$rootScope", "$translate", BacklogDirective])
############################################################################# #############################################################################
## User story points directive ## User story points directive
############################################################################# #############################################################################
UsRolePointsSelectorDirective = ($rootscope, $template) -> UsRolePointsSelectorDirective = ($rootscope, $template, $compile, $translate) ->
#TODO: i18n
selectionTemplate = $template.get("backlog/us-role-points-popover.html", true) selectionTemplate = $template.get("backlog/us-role-points-popover.html", true)
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
@ -704,7 +725,7 @@ UsRolePointsSelectorDirective = ($rootscope, $template) ->
numberOfRoles = _.size(roles) numberOfRoles = _.size(roles)
if numberOfRoles > 1 if numberOfRoles > 1
$el.append(selectionTemplate({"roles":roles})) $el.append($compile(selectionTemplate({"roles": roles}))($scope))
else else
$el.find(".icon-arrow-bottom").remove() $el.find(".icon-arrow-bottom").remove()
$el.find(".header-points").addClass("not-clickable") $el.find(".header-points").addClass("not-clickable")
@ -715,7 +736,9 @@ UsRolePointsSelectorDirective = ($rootscope, $template) ->
$scope.$on "uspoints:clear-selection", (ctx, roleId) -> $scope.$on "uspoints:clear-selection", (ctx, roleId) ->
$el.find(".popover").popover().close() $el.find(".popover").popover().close()
$el.find(".header-points").text("Points") #TODO: i18n
text = $translate.instant("COMMON.FIELDS.POINTS")
$el.find(".header-points").text(text)
# Dom Event Handlers # Dom Event Handlers
$el.on "click", (event) -> $el.on "click", (event) ->
@ -743,7 +766,7 @@ UsRolePointsSelectorDirective = ($rootscope, $template) ->
return {link: link} return {link: link}
module.directive("tgUsRolePointsSelector", ["$rootScope", "$tgTemplate", UsRolePointsSelectorDirective]) module.directive("tgUsRolePointsSelector", ["$rootScope", "$tgTemplate", "$compile", UsRolePointsSelectorDirective])
UsPointsDirective = ($tgEstimationsService, $repo, $tgTemplate) -> UsPointsDirective = ($tgEstimationsService, $repo, $tgTemplate) ->
@ -848,11 +871,12 @@ UsPointsDirective = ($tgEstimationsService, $repo, $tgTemplate) ->
module.directive("tgBacklogUsPoints", ["$tgEstimationsService", "$tgRepo", "$tgTemplate", UsPointsDirective]) module.directive("tgBacklogUsPoints", ["$tgEstimationsService", "$tgRepo", "$tgTemplate", UsPointsDirective])
############################################################################# #############################################################################
## Burndown graph directive ## Burndown graph directive
############################################################################# #############################################################################
tgBacklogGraphDirective = -> BurndownBacklogGraphDirective = ($translate) ->
redrawChart = (element, dataToDraw) -> redrawChart = (element, dataToDraw) ->
width = element.width() width = element.width()
element.height(width/6) element.height(width/6)
@ -908,13 +932,20 @@ tgBacklogGraphDirective = ->
} }
xaxis: { xaxis: {
ticks: dataToDraw.milestones.length ticks: dataToDraw.milestones.length
axisLabel: "Sprints" axisLabel: $translate.instant("BACKLOG.CHART.XAXIS_LABEL"),
axisLabelUseCanvas: true axisLabelUseCanvas: true
axisLabelFontSizePixels: 14 axisLabelFontSizePixels: 12
axisLabelFontFamily: "Verdana, Arial, Helvetica, Tahoma, sans-serif" axisLabelFontFamily: "Verdana, Arial, Helvetica, Tahoma, sans-serif"
axisLabelPadding: 15 axisLabelPadding: 5
tickFormatter: (val, axis) -> "" tickFormatter: (val, axis) -> ""
} }
yaxis: {
axisLabel: $translate.instant("BACKLOG.CHART.YAXIS_LABEL"),
axisLabelUseCanvas: true
axisLabelFontSizePixels: 12
axisLabelFontFamily: "Verdana, Arial, Helvetica, Tahoma, sans-serif"
axisLabelPadding: 5
}
series: { series: {
shadowSize: 0 shadowSize: 0
lines: { lines: {
@ -932,18 +963,18 @@ tgBacklogGraphDirective = ->
tooltip: true tooltip: true
tooltipOpts: { tooltipOpts: {
content: (label, xval, yval, flotItem) -> content: (label, xval, yval, flotItem) ->
#TODO: i18n
if flotItem.seriesIndex == 1 if flotItem.seriesIndex == 1
return "Optimal pending points for sprint #{xval} should be #{yval}" ctx = {xval: xval, yval: yval}
return $translate.instant("BACKLOG.CHART.OPTIMAL", ctx)
else if flotItem.seriesIndex == 2 else if flotItem.seriesIndex == 2
return "Real pending points for sprint #{xval} is #{yval}" ctx = {xval: xval, yval: yval}
return $translate.instant("BACKLOG.CHART.REAL", ctx)
else if flotItem.seriesIndex == 3 else if flotItem.seriesIndex == 3
return "Incremented points by team requirements for sprint #{xval} is #{Math.abs(yval)}" ctx = {xval: xval, yval: Math.abs(yval)}
return $translate.instant("BACKLOG.CHART.INCREMENT_TEAM", ctx)
else else
return "Incremented points by client requirements for sprint #{xval} is #{Math.abs(yval)}" ctx = {xval: xval, yval: Math.abs(yval)}
return $translate.instant("BACKLOG.CHART.INCREMENT_CLIENT", ctx)
} }
} }
@ -965,8 +996,7 @@ tgBacklogGraphDirective = ->
return {link: link} return {link: link}
module.directive("tgBurndownBacklogGraph", ["$translate", BurndownBacklogGraphDirective])
module.directive("tgGmBacklogGraph", tgBacklogGraphDirective)
############################################################################# #############################################################################

View File

@ -39,7 +39,7 @@ deleteElement = (el) ->
el.off() el.off()
el.remove() el.remove()
BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm) -> BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm, $translate) ->
# Notes about jquery bug: # Notes about jquery bug:
# http://stackoverflow.com/questions/5791886/jquery-draggable-shows- # http://stackoverflow.com/questions/5791886/jquery-draggable-shows-
# helper-in-wrong-place-when-scrolled-down-page # helper-in-wrong-place-when-scrolled-down-page
@ -54,7 +54,7 @@ BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm) ->
return return
filterError = -> filterError = ->
text = "You can't drop on backlog when filters are open" text = $translate.instant("BACKLOG.SORTABLE_FILTER_ERROR")
$tgConfirm.notify("error", text) $tgConfirm.notify("error", text)
$el.sortable({ $el.sortable({
@ -215,6 +215,7 @@ module.directive("tgBacklogSortable", [
"$tgResources", "$tgResources",
"$rootScope", "$rootScope",
"$tgConfirm", "$tgConfirm",
"$translate",
BacklogSortableDirective BacklogSortableDirective
]) ])

View File

@ -85,10 +85,12 @@ module.directive("tgBacklogSprint", ["$tgRepo", "$rootScope", BacklogSprintDirec
## Sprint Header Directive ## Sprint Header Directive
############################################################################# #############################################################################
BacklogSprintHeaderDirective = ($navUrls, $template) -> BacklogSprintHeaderDirective = ($navUrls, $template, $compile, $translate) ->
template = $template.get("backlog/sprint-header.html", true) template = $template.get("backlog/sprint-header.html")
link = ($scope, $el, $attrs, $model) -> link = ($scope, $el, $attrs, $model) ->
prettyDate = $translate.instant("BACKLOG.SPRINTS.DATE")
isEditable = -> isEditable = ->
return $scope.project.my_permissions.indexOf("modify_milestone") != -1 return $scope.project.my_permissions.indexOf("modify_milestone") != -1
@ -99,8 +101,8 @@ BacklogSprintHeaderDirective = ($navUrls, $template) ->
taskboardUrl = $navUrls.resolve("project-taskboard", taskboardUrl = $navUrls.resolve("project-taskboard",
{project: $scope.project.slug, sprint: sprint.slug}) {project: $scope.project.slug, sprint: sprint.slug})
start = moment(sprint.estimated_start).format("DD MMM YYYY") start = moment(sprint.estimated_start).format(prettyDate)
finish = moment(sprint.estimated_finish).format("DD MMM YYYY") finish = moment(sprint.estimated_finish).format(prettyDate)
estimatedDateRange = "#{start}-#{finish}" estimatedDateRange = "#{start}-#{finish}"
ctx = { ctx = {
@ -112,8 +114,13 @@ BacklogSprintHeaderDirective = ($navUrls, $template) ->
isVisible: isVisible() isVisible: isVisible()
isEditable: isEditable() isEditable: isEditable()
} }
$el.html(template(ctx))
templateScope = $scope.$new()
_.assign(templateScope, ctx)
compiledTemplate = $compile(template)(templateScope)
$el.html(compiledTemplate)
$scope.$watch $attrs.ngModel, (sprint) -> $scope.$watch $attrs.ngModel, (sprint) ->
render(sprint) render(sprint)
@ -130,13 +137,15 @@ BacklogSprintHeaderDirective = ($navUrls, $template) ->
require: "ngModel" require: "ngModel"
} }
module.directive("tgBacklogSprintHeader", ["$tgNavUrls", "$tgTemplate", BacklogSprintHeaderDirective]) module.directive("tgBacklogSprintHeader", ["$tgNavUrls", "$tgTemplate", "$compile", "$translate"
BacklogSprintHeaderDirective])
############################################################################# #############################################################################
## Toggle Closed Sprints Directive ## Toggle Closed Sprints Directive
############################################################################# #############################################################################
ToggleExcludeClosedSprintsVisualization = ($rootscope, $loading) -> ToggleExcludeClosedSprintsVisualization = ($rootscope, $loading, $translate) ->
excludeClosedSprints = true excludeClosedSprints = true
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
@ -162,14 +171,16 @@ ToggleExcludeClosedSprintsVisualization = ($rootscope, $loading) ->
$scope.$on "closed-sprints:reloaded", (ctx, sprints) => $scope.$on "closed-sprints:reloaded", (ctx, sprints) =>
$loading.finish(loadingElm) $loading.finish(loadingElm)
#TODO: i18n
if sprints.length > 0 if sprints.length > 0
text = "Hide closed sprints" key = "BACKLOG.SPRINTS.ACTION_HIDE_CLOSED_SPRINTS"
else else
text = "Show closed sprints" key = "BACKLOG.SPRINTS.ACTION_SHOW_CLOSED_SPRINTS"
text = $translate.instant(key)
$el.find(".text").text(text) $el.find(".text").text(text)
return {link: link} return {link: link}
module.directive("tgBacklogToggleClosedSprintsVisualization", ["$rootScope", "$tgLoading", ToggleExcludeClosedSprintsVisualization]) module.directive("tgBacklogToggleClosedSprintsVisualization", ["$rootScope", "$tgLoading", "$translate",
ToggleExcludeClosedSprintsVisualization])

View File

@ -23,7 +23,7 @@ taiga = @.taiga
groupBy = @.taiga.groupBy groupBy = @.taiga.groupBy
bindOnce = @.taiga.bindOnce bindOnce = @.taiga.bindOnce
module = angular.module("taigaBase", ["taigaLocales"]) module = angular.module("taigaBase", [])
############################################################################# #############################################################################
## Main Directive ## Main Directive

View File

@ -22,16 +22,25 @@
taiga = @.taiga taiga = @.taiga
class HttpService extends taiga.Service class HttpService extends taiga.Service
@.$inject = ["$http", "$q", "$tgStorage"] @.$inject = ["$http", "$q", "$tgStorage", "$rootScope"]
constructor: (@http, @q, @storage, @rootScope) ->
super()
headers: -> headers: ->
headers = {}
# Authorization
token = @storage.get('token') token = @storage.get('token')
if token if token
return {"Authorization":"Bearer #{token}"} headers["Authorization"] = "Bearer #{token}"
return {}
constructor: (@http, @q, @storage) -> # Accept-Language
super() lang = @rootScope.user?.lang
if lang
headers["Accept-Language"] = lang
return headers
request: (options) -> request: (options) ->
options.headers = _.merge({}, options.headers or {}, @.headers()) options.headers = _.merge({}, options.headers or {}, @.headers())

View File

@ -1,74 +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/base/i18n.coffee
###
taiga = @.taiga
bindOnce = @.taiga.bindOnce
defaults = {
ns: "app"
fallbackLng: "en"
async: false
lng: "en"
}
class I18nService extends taiga.Service
constructor: (@rootscope, localesEn) ->
@.options = _.clone(defaults, true)
@.options.resStore = {
en: { app: localesEn }
}
setLanguage: (language) ->
i18n.setLng(language)
@rootscope.currentLang = language
@rootscope.$broadcast("i18n:changeLang", language)
initialize: ->
i18n.init(@.options)
@rootscope.t = i18n.t
t: (path, opts) ->
return i18n.t(path, opts)
I18nDirective = ($rootscope, $i18n) ->
link = ($scope, $el, $attrs) ->
values = $attrs.tr.split(",")
options = $attrs.trOpts or '{}'
opts = $scope.$eval(options)
for v in values
if v.indexOf(":") == -1
$el.html(_.escape($i18n.t(v, opts)))
else
[ns, v] = v.split(":")
$el.attr(ns, _.escape($i18n.t(v, opts)))
return {
link: link
restrict: "A"
scope: false
}
module = angular.module("taigaBase")
module.service("$tgI18n", ["$rootScope", "localesEn", I18nService])
module.directive("tr", ["$rootScope", "$tgI18n", I18nDirective])

View File

@ -28,9 +28,9 @@ module = angular.module("taigaCommon")
class AttachmentsController extends taiga.Controller class AttachmentsController extends taiga.Controller
@.$inject = ["$scope", "$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", "$q"] @.$inject = ["$scope", "$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", "$q", "$translate"]
constructor: (@scope, @rootscope, @repo, @rs, @confirm, @q) -> constructor: (@scope, @rootscope, @repo, @rs, @confirm, @q, @translate) ->
bindMethods(@) bindMethods(@)
@.type = null @.type = null
@.objectId = null @.objectId = null
@ -75,10 +75,13 @@ class AttachmentsController extends taiga.Controller
promise = promise.then null, (data) => promise = promise.then null, (data) =>
@scope.$emit("attachments:size-error") if data.status == 413 @scope.$emit("attachments:size-error") if data.status == 413
index = @.uploadingAttachments.indexOf(attachment) index = @.uploadingAttachments.indexOf(attachment)
@.uploadingAttachments.splice(index, 1) @.uploadingAttachments.splice(index, 1)
@confirm.notify("error", "We have not been able to upload '#{attachment.name}'.
#{data.data._error_message}") message = @translate.instant("ATTACHMENT.ERROR_UPLOAD_ATTACHMENT", {
fileName: attachment.name, errorMessage: data.data._error_message})
@confirm.notify("error", message)
return @q.reject(data) return @q.reject(data)
return promise return promise
@ -130,8 +133,8 @@ class AttachmentsController extends taiga.Controller
# Remove one concrete attachment. # Remove one concrete attachment.
removeAttachment: (attachment) -> removeAttachment: (attachment) ->
title = "Delete attachment" #TODO: i18in title = @translate.instant("ATTACHMENT.TITLE_LIGHTBOX_DELETE_ATTACHMENT")
message = "the attachment '#{attachment.name}'" #TODO: i18in message = @translate.instant("ATTACHMENT.MSG_LIGHTBOX_DELETE_ATTACHMENT", {fileName: attachment.name})
return @confirm.askOnDelete(title, message).then (finish) => return @confirm.askOnDelete(title, message).then (finish) =>
onSuccess = => onSuccess = =>
@ -143,7 +146,8 @@ class AttachmentsController extends taiga.Controller
onError = => onError = =>
finish(false) finish(false)
@confirm.notify("error", null, "We have not been able to delete #{message}.") message = @translate.instant("ATTACHMENT.ERROR_DELETE_ATTACHMENT", {errorMessage: message})
@confirm.notify("error", null, message)
return @q.reject() return @q.reject()
return @repo.remove(attachment).then(onSuccess, onError) return @repo.remove(attachment).then(onSuccess, onError)
@ -155,7 +159,7 @@ class AttachmentsController extends taiga.Controller
return not item.is_deprecated return not item.is_deprecated
AttachmentsDirective = ($config, $confirm, $templates) -> AttachmentsDirective = ($config, $confirm, $templates, $translate) ->
template = $templates.get("attachment/attachments.html", true) template = $templates.get("attachment/attachments.html", true)
link = ($scope, $el, $attrs, $ctrls) -> link = ($scope, $el, $attrs, $ctrls) ->
@ -220,8 +224,7 @@ AttachmentsDirective = ($config, $confirm, $templates) ->
templateFn = ($el, $attrs) -> templateFn = ($el, $attrs) ->
maxFileSize = $config.get("maxUploadFileSize", null) maxFileSize = $config.get("maxUploadFileSize", null)
maxFileSize = sizeFormat(maxFileSize) if maxFileSize maxFileSize = sizeFormat(maxFileSize) if maxFileSize
maxFileSizeMsg = if maxFileSize then "Maximum upload size is #{maxFileSize}" else "" # TODO: i18n maxFileSizeMsg = if maxFileSize then $translate.instant("ATTACHMENT.MAX_UPLOAD_SIZE", {maxFileSize: maxFileSize}) else ""
ctx = { ctx = {
type: $attrs.type type: $attrs.type
maxFileSize: maxFileSize maxFileSize: maxFileSize
@ -239,10 +242,10 @@ AttachmentsDirective = ($config, $confirm, $templates) ->
template: templateFn template: templateFn
} }
module.directive("tgAttachments", ["$tgConfig", "$tgConfirm", "$tgTemplate", AttachmentsDirective]) module.directive("tgAttachments", ["$tgConfig", "$tgConfirm", "$tgTemplate", "$translate", AttachmentsDirective])
AttachmentDirective = ($template) -> AttachmentDirective = ($template, $compile, $translate) ->
template = $template.get("attachment/attachment.html", true) template = $template.get("attachment/attachment.html", true)
templateEdit = $template.get("attachment/attachment-edit.html", true) templateEdit = $template.get("attachment/attachment-edit.html", true)
@ -254,7 +257,9 @@ AttachmentDirective = ($template) ->
ctx = { ctx = {
id: attachment.id id: attachment.id
name: attachment.name name: attachment.name
created_date: moment(attachment.created_date).format("DD MMM YYYY [at] hh:mm") #TODO: i18n title : $translate.instant("ATTACHMENT.TITLE", {
fileName: attachment.name,
date: moment(attachment.created_date).format($translate.instant("ATTACHMENT.DATE"))})
url: attachment.url url: attachment.url
size: sizeFormat(attachment.size) size: sizeFormat(attachment.size)
description: attachment.description description: attachment.description
@ -263,9 +268,9 @@ AttachmentDirective = ($template) ->
} }
if edit if edit
html = templateEdit(ctx) html = $compile(templateEdit(ctx))($scope)
else else
html = template(ctx) html = $compile(template(ctx))($scope)
$el.html(html) $el.html(html)
@ -322,4 +327,4 @@ AttachmentDirective = ($template) ->
restrict: "AE" restrict: "AE"
} }
module.directive("tgAttachment", ["$tgTemplate", AttachmentDirective]) module.directive("tgAttachment", ["$tgTemplate", "$compile", "$translate", AttachmentDirective])

View File

@ -29,10 +29,11 @@ module = angular.module("taigaCommon")
## Date Range Directive (used mainly for sprint date range) ## Date Range Directive (used mainly for sprint date range)
############################################################################# #############################################################################
DateRangeDirective = -> DateRangeDirective = ($translate) ->
renderRange = ($el, first, second) -> renderRange = ($el, first, second) ->
initDate = moment(first).format("DD MMM YYYY") prettyDate = $translate.instant("BACKLOG.SPRINTS.DATE")
endDate = moment(second).format("DD MMM YYYY") initDate = moment(first).format(prettyDate)
endDate = moment(second).format(prettyDate)
$el.html("#{initDate}-#{endDate}") $el.html("#{initDate}-#{endDate}")
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
@ -44,34 +45,75 @@ DateRangeDirective = ->
return {link:link} return {link:link}
module.directive("tgDateRange", DateRangeDirective) module.directive("tgDateRange", ["$translate", DateRangeDirective])
############################################################################# #############################################################################
## Date Selector Directive (using pikaday) ## Date Selector Directive (using pikaday)
############################################################################# #############################################################################
DateSelectorDirective =-> DateSelectorDirective = ($rootscope, $translate) ->
link = ($scope, $el, $attrs, $model) -> link = ($scope, $el, $attrs, $model) ->
selectedDate = null selectedDate = null
$el.picker = new Pikaday({
field: $el[0] initialize = () ->
format: "DD MMM YYYY" $el.picker = new Pikaday({
onSelect: (date) => field: $el[0]
selectedDate = date onSelect: (date) =>
onOpen: => selectedDate = date
$el.picker.setDate(selectedDate) if selectedDate? onOpen: =>
}) $el.picker.setDate(selectedDate) if selectedDate?
i18n: {
previousMonth: $translate.instant("COMMON.PICKERDATE.PREV_MONTH"),
nextMonth: $translate.instant("COMMON.PICKERDATE.NEXT_MONTH"),
months: [$translate.instant("COMMON.PICKERDATE.MONTHS.JAN"),
$translate.instant("COMMON.PICKERDATE.MONTHS.FEB"),
$translate.instant("COMMON.PICKERDATE.MONTHS.MAR"),
$translate.instant("COMMON.PICKERDATE.MONTHS.APR"),
$translate.instant("COMMON.PICKERDATE.MONTHS.MAY"),
$translate.instant("COMMON.PICKERDATE.MONTHS.JUN"),
$translate.instant("COMMON.PICKERDATE.MONTHS.JUL"),
$translate.instant("COMMON.PICKERDATE.MONTHS.AUG"),
$translate.instant("COMMON.PICKERDATE.MONTHS.SEP"),
$translate.instant("COMMON.PICKERDATE.MONTHS.OCT"),
$translate.instant("COMMON.PICKERDATE.MONTHS.NOV"),
$translate.instant("COMMON.PICKERDATE.MONTHS.DEC")],
weekdays: [$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.SUN"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.MON"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.TUE"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.WED"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.THU"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.FRI"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.SAT")],
weekdaysShort: [$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.SUN"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.MON"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.TUE"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.WED"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.THU"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.FRI"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.SAT")]
},
isRTL: $translate.instant("COMMON.PICKERDATE.IS_RTL") == "true",
firstDay: parseInt($translate.instant("COMMON.PICKERDATE.FIRST_DAY_OF_WEEK"), 10),
format: $translate.instant("COMMON.PICKERDATE.FORMAT")
})
unbind = $rootscope.$on "$translateChangeEnd", (ctx) => initialize()
$scope.$watch $attrs.ngModel, (val) -> $scope.$watch $attrs.ngModel, (val) ->
initialize() if val? and not $el.picker
$el.picker.setDate(val) if val? $el.picker.setDate(val) if val?
$scope.$on "$destroy", ->
$el.off()
unbind()
return { return {
link: link link: link
require: "ngModel" require: "ngModel"
} }
module.directive("tgDateSelector", DateSelectorDirective) module.directive("tgDateSelector", ["$rootScope", "$translate", DateSelectorDirective])
############################################################################# #############################################################################
@ -98,6 +140,9 @@ SprintProgressBarDirective = ->
renderProgress($el, percentage, visual_percentage) renderProgress($el, percentage, visual_percentage)
$scope.$on "$destroy", ->
$el.off()
return {link: link} return {link: link}
module.directive("tgSprintProgressbar", SprintProgressBarDirective) module.directive("tgSprintProgressbar", SprintProgressBarDirective)
@ -107,7 +152,7 @@ module.directive("tgSprintProgressbar", SprintProgressBarDirective)
## Created-by display directive ## Created-by display directive
############################################################################# #############################################################################
CreatedByDisplayDirective = ($template)-> CreatedByDisplayDirective = ($template, $compile, $translate)->
# Display the owner information (full name and photo) and the date of # Display the owner information (full name and photo) and the date of
# creation of an object (like USs, tasks and issues). # creation of an object (like USs, tasks and issues).
# #
@ -119,18 +164,22 @@ CreatedByDisplayDirective = ($template)->
# 'owner'(ng-model) # 'owner'(ng-model)
# - scope.usersById object is required. # - scope.usersById object is required.
template = $template.get("common/components/created-by.html", true) # TODO: i18n template = $template.get("common/components/created-by.html", true)
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
render = (model) -> render = (model) ->
owner = $scope.usersById?[model.owner] or { owner = $scope.usersById?[model.owner] or {
full_name_display: "external user" full_name_display: $translate.instant("COMMON.EXTERNAL_USER")
photo: "/images/unnamed.png" photo: "/images/unnamed.png"
} }
html = template({ html = template({
owner: owner owner: owner
date: moment(model.created_date).format("DD MMM YYYY HH:mm") date: moment(model.created_date).format($translate.instant("COMMON.DATETIME"))
}) })
html = $compile(html)($scope)
$el.html(html) $el.html(html)
bindOnce $scope, $attrs.ngModel, (model) -> bindOnce $scope, $attrs.ngModel, (model) ->
@ -145,18 +194,16 @@ CreatedByDisplayDirective = ($template)->
require: "ngModel" require: "ngModel"
} }
module.directive("tgCreatedByDisplay", ["$tgTemplate", CreatedByDisplayDirective]) module.directive("tgCreatedByDisplay", ["$tgTemplate", "$compile", "$translate", CreatedByDisplayDirective])
############################################################################# #############################################################################
## Watchers directive ## Watchers directive
############################################################################# #############################################################################
WatchersDirective = ($rootscope, $confirm, $repo, $qqueue, $template) -> WatchersDirective = ($rootscope, $confirm, $repo, $qqueue, $template, $compile, $translate) ->
# You have to include a div with the tg-lb-watchers directive in the page # You have to include a div with the tg-lb-watchers directive in the page
# where use this directive # where use this directive
#
# TODO: i18n
template = $template.get("common/components/watchers.html", true) template = $template.get("common/components/watchers.html", true)
link = ($scope, $el, $attrs, $model) -> link = ($scope, $el, $attrs, $model) ->
@ -200,7 +247,7 @@ WatchersDirective = ($rootscope, $confirm, $repo, $qqueue, $template) ->
isEditable: isEditable() isEditable: isEditable()
} }
html = template(ctx) html = $compile(template(ctx))($scope)
$el.html(html) $el.html(html)
if isEditable() and watchers.length == 0 if isEditable() and watchers.length == 0
@ -213,7 +260,7 @@ WatchersDirective = ($rootscope, $confirm, $repo, $qqueue, $template) ->
target = angular.element(event.currentTarget) target = angular.element(event.currentTarget)
watcherId = target.data("watcher-id") watcherId = target.data("watcher-id")
title = "Delete watcher" title = $translate.instant("COMMON.WATCHERS.TITLE_LIGHTBOX_DELETE_WARTCHER")
message = $scope.usersById[watcherId].full_name_display message = $scope.usersById[watcherId].full_name_display
$confirm.askOnDelete(title, message).then (finish) => $confirm.askOnDelete(title, message).then (finish) =>
@ -247,18 +294,17 @@ WatchersDirective = ($rootscope, $confirm, $repo, $qqueue, $template) ->
return {link:link, require:"ngModel"} return {link:link, require:"ngModel"}
module.directive("tgWatchers", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgQqueue", "$tgTemplate", WatchersDirective]) module.directive("tgWatchers", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgQqueue", "$tgTemplate", "$compile",
"$translate", WatchersDirective])
############################################################################# #############################################################################
## Assigned to directive ## Assigned to directive
############################################################################# #############################################################################
AssignedToDirective = ($rootscope, $confirm, $repo, $loading, $qqueue, $template) -> AssignedToDirective = ($rootscope, $confirm, $repo, $loading, $qqueue, $template, $translate, $compile) ->
# You have to include a div with the tg-lb-assignedto directive in the page # You have to include a div with the tg-lb-assignedto directive in the page
# where use this directive # where use this directive
#
# TODO: i18n
template = $template.get("common/components/assigned-to.html", true) template = $template.get("common/components/assigned-to.html", true)
link = ($scope, $el, $attrs, $model) -> link = ($scope, $el, $attrs, $model) ->
@ -291,7 +337,7 @@ AssignedToDirective = ($rootscope, $confirm, $repo, $loading, $qqueue, $template
assignedTo: assignedTo assignedTo: assignedTo
isEditable: isEditable() isEditable: isEditable()
} }
html = template(ctx) html = $compile(template(ctx))($scope)
$el.html(html) $el.html(html)
$el.on "click", ".user-assigned", (event) -> $el.on "click", ".user-assigned", (event) ->
@ -303,7 +349,7 @@ AssignedToDirective = ($rootscope, $confirm, $repo, $loading, $qqueue, $template
$el.on "click", ".icon-delete", (event) -> $el.on "click", ".icon-delete", (event) ->
event.preventDefault() event.preventDefault()
return if not isEditable() return if not isEditable()
title = "Are you sure you want to leave it unassigned?" # TODO: i18n title = $translate.instant("COMMON.ASSIGNED_TO.CONFIRM_UNASSIGNED")
$confirm.ask(title).then (finish) => $confirm.ask(title).then (finish) =>
finish() finish()
@ -326,7 +372,8 @@ AssignedToDirective = ($rootscope, $confirm, $repo, $loading, $qqueue, $template
require:"ngModel" require:"ngModel"
} }
module.directive("tgAssignedTo", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgLoading", "$tgQqueue", "$tgTemplate", AssignedToDirective]) module.directive("tgAssignedTo", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgLoading", "$tgQqueue", "$tgTemplate", "$translate", "$compile",
AssignedToDirective])
############################################################################# #############################################################################
@ -392,7 +439,7 @@ DeleteButtonDirective = ($log, $repo, $confirm, $location, $template) ->
return $log.error "DeleteButtonDirective requires on-delete-title set in scope." return $log.error "DeleteButtonDirective requires on-delete-title set in scope."
$el.on "click", ".button", (event) -> $el.on "click", ".button", (event) ->
title = $scope.$eval($attrs.onDeleteTitle) title = $attrs.onDeleteTitle
subtitle = $model.$modelValue.subject subtitle = $model.$modelValue.subject
$confirm.askOnDelete(title, subtitle).then (finish) => $confirm.askOnDelete(title, subtitle).then (finish) =>
@ -471,7 +518,6 @@ EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $qqueue, $tem
$el.find('div.edit-subject').hide() $el.find('div.edit-subject').hide()
$el.find('div.view-subject span.edit').hide() $el.find('div.view-subject span.edit').hide()
$scope.$watch $attrs.ngModel, (value) -> $scope.$watch $attrs.ngModel, (value) ->
return if not value return if not value
$scope.item = value $scope.item = value
@ -490,7 +536,8 @@ EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $qqueue, $tem
template: template template: template
} }
module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", "$tgTemplate", EditableSubjectDirective]) module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue",
"$tgTemplate", EditableSubjectDirective])
############################################################################# #############################################################################
@ -498,9 +545,9 @@ module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$
############################################################################# #############################################################################
EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading, $selectedText, $qqueue, $template) -> EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading, $selectedText, $qqueue, $template) ->
template = $template.get("common/components/editable-description.html") # TODO: i18n template = $template.get("common/components/editable-description.html")
noDescriptionMegEditMode = $template.get("common/components/editable-description-msg-edit-mode.html") # TODO: i18n noDescriptionMegEditMode = $template.get("common/components/editable-description-msg-edit-mode.html")
noDescriptionMegReadMode = $template.get("common/components/editable-description-msg-read-mode.html") # TODO: i18n noDescriptionMegReadMode = $template.get("common/components/editable-description-msg-read-mode.html")
link = ($scope, $el, $attrs, $model) -> link = ($scope, $el, $attrs, $model) ->
$el.find('.edit-description').hide() $el.find('.edit-description').hide()
@ -555,9 +602,9 @@ EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading,
if isEditable() if isEditable()
$el.find('.view-description .edit').show() $el.find('.view-description .edit').show()
$el.find('.view-description .us-content').addClass('editable') $el.find('.view-description .us-content').addClass('editable')
$scope.noDescriptionMsg = noDescriptionMegEditMode $scope.noDescriptionMsg = $compile(noDescriptionMegEditMode)($scope)
else else
$scope.noDescriptionMsg = noDescriptionMegReadMode $scope.noDescriptionMsg = $compile(noDescriptionMegReadMode)($scope)
$scope.$on "$destroy", -> $scope.$on "$destroy", ->
$el.off() $el.off()
@ -569,8 +616,8 @@ EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading,
template: template template: template
} }
module.directive("tgEditableDescription", ["$rootScope", "$tgRepo", "$tgConfirm", module.directive("tgEditableDescription", ["$rootScope", "$tgRepo", "$tgConfirm", "$compile", "$tgLoading",
"$compile", "$tgLoading", "$selectedText", "$tgQqueue", "$tgTemplate", EditableDescriptionDirective]) "$selectedText", "$tgQqueue", "$tgTemplate", EditableDescriptionDirective])
############################################################################# #############################################################################
@ -580,14 +627,16 @@ module.directive("tgEditableDescription", ["$rootScope", "$tgRepo", "$tgConfirm"
## completely bindonce, they only serves for visualization of data. ## completely bindonce, they only serves for visualization of data.
############################################################################# #############################################################################
ListItemIssueStatusDirective = -> ListItemUsStatusDirective = ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
issue = $scope.$eval($attrs.tgListitemIssueStatus) us = $scope.$eval($attrs.tgListitemUsStatus)
bindOnce $scope, "issueStatusById", (issueStatusById) -> bindOnce $scope, "usStatusById", (usStatusById) ->
$el.html(issueStatusById[issue.status].name) $el.html(usStatusById[us.status].name)
return {link:link} return {link:link}
module.directive("tgListitemUsStatus", ListItemUsStatusDirective)
ListItemTaskStatusDirective = -> ListItemTaskStatusDirective = ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
@ -597,14 +646,7 @@ ListItemTaskStatusDirective = ->
return {link:link} return {link:link}
module.directive("tgListitemTaskStatus", ListItemTaskStatusDirective)
ListItemUsStatusDirective = ->
link = ($scope, $el, $attrs) ->
us = $scope.$eval($attrs.tgListitemUsStatus)
bindOnce $scope, "usStatusById", (usStatusById) ->
$el.html(usStatusById[us.status].name)
return {link:link}
ListItemAssignedtoDirective = ($template) -> ListItemAssignedtoDirective = ($template) ->
@ -626,6 +668,41 @@ ListItemAssignedtoDirective = ($template) ->
module.directive("tgListitemAssignedto", ["$tgTemplate", ListItemAssignedtoDirective]) module.directive("tgListitemAssignedto", ["$tgTemplate", ListItemAssignedtoDirective])
ListItemIssueStatusDirective = ->
link = ($scope, $el, $attrs) ->
issue = $scope.$eval($attrs.tgListitemIssueStatus)
bindOnce $scope, "issueStatusById", (issueStatusById) ->
$el.html(issueStatusById[issue.status].name)
return {link:link}
module.directive("tgListitemIssueStatus", ListItemIssueStatusDirective)
ListItemTypeDirective = ->
link = ($scope, $el, $attrs) ->
render = (issueTypeById, issue) ->
type = issueTypeById[issue.type]
domNode = $el.find(".level")
domNode.css("background-color", type.color)
domNode.attr("title", type.name)
bindOnce $scope, "issueTypeById", (issueTypeById) ->
issue = $scope.$eval($attrs.tgListitemType)
render(issueTypeById, issue)
$scope.$watch $attrs.tgListitemType, (issue) ->
render($scope.issueTypeById, issue)
return {
link: link
templateUrl: "common/components/level.html"
}
module.directive("tgListitemType", ListItemTypeDirective)
ListItemPriorityDirective = -> ListItemPriorityDirective = ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
render = (priorityById, issue) -> render = (priorityById, issue) ->
@ -648,6 +725,7 @@ ListItemPriorityDirective = ->
module.directive("tgListitemPriority", ListItemPriorityDirective) module.directive("tgListitemPriority", ListItemPriorityDirective)
ListItemSeverityDirective = -> ListItemSeverityDirective = ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
render = (severityById, issue) -> render = (severityById, issue) ->
@ -668,26 +746,7 @@ ListItemSeverityDirective = ->
templateUrl: "common/components/level.html" templateUrl: "common/components/level.html"
} }
module.directive("tgListitemSeverity", ListItemSeverityDirective)
ListItemTypeDirective = ->
link = ($scope, $el, $attrs) ->
render = (issueTypeById, issue) ->
type = issueTypeById[issue.type]
domNode = $el.find(".level")
domNode.css("background-color", type.color)
domNode.attr("title", type.name)
bindOnce $scope, "issueTypeById", (issueTypeById) ->
issue = $scope.$eval($attrs.tgListitemType)
render(issueTypeById, issue)
$scope.$watch $attrs.tgListitemType, (issue) ->
render($scope.issueTypeById, issue)
return {
link: link
templateUrl: "common/components/level.html"
}
############################################################################# #############################################################################
@ -715,35 +774,27 @@ TgProgressBarDirective = ($template) ->
module.directive("tgProgressBar", ["$tgTemplate", TgProgressBarDirective]) module.directive("tgProgressBar", ["$tgTemplate", TgProgressBarDirective])
############################################################################# #############################################################################
## Main title directive ## Main title directive
############################################################################# #############################################################################
TgMainTitleDirective = ($template) -> TgMainTitleDirective = ($translate) ->
template = $template.get("common/components/main-title.html", true)
render = (el, projectName, sectionName) ->
el.html(template({
projectName: projectName
sectionName: sectionName
}))
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
element = angular.element($el) $attrs.$observe "i18nSectionName", (i18nSectionName) ->
$scope.$watch "project", (project) -> trans = $translate(i18nSectionName)
render($el, project.name, $scope.sectionName) if project trans.then (sectionName) -> $scope.sectionName = sectionName
trans.catch (sectionName) -> $scope.sectionName = sectionName
$scope.$on "project:loaded", (ctx, project) =>
render($el, project.name, $scope.sectionName)
$scope.$on "$destroy", -> $scope.$on "$destroy", ->
$el.off() $el.off()
return {link: link} return {
link: link
templateUrl: "common/components/main-title.html"
scope: {
projectName : "=projectName"
}
}
module.directive("tgMainTitle", ["$tgTemplate", TgMainTitleDirective]) module.directive("tgMainTitle", ["$translate", TgMainTitleDirective])
module.directive("tgListitemType", ListItemTypeDirective)
module.directive("tgListitemIssueStatus", ListItemIssueStatusDirective)
module.directive("tgListitemSeverity", ListItemSeverityDirective)
module.directive("tgListitemTaskStatus", ListItemTaskStatusDirective)
module.directive("tgListitemUsStatus", ListItemUsStatusDirective)

View File

@ -27,21 +27,21 @@ bindMethods = @.taiga.bindMethods
NOTIFICATION_MSG = { NOTIFICATION_MSG = {
"success": "success":
title: "Everything is ok" title: "NOTIFICATION.OK"
message: "Our Oompa Loompas saved all your changes!" message: "NOTIFICATION.SAVED"
"error": "error":
title: "Oops, something happened..." title: "NOTIFICATION.WARNING"
message: "Our Oompa Loompas are sad, your changes were not saved!" message: "NOTIFICATION.WARNING_TEXT"
"light-error": "light-error":
title: "Oops, something happened..." title: "NOTIFICATION.WARNING"
message: "Our Oompa Loompas are sad, your changes were not saved!" message: "NOTIFICATION.WARNING_TEXT"
} }
class ConfirmService extends taiga.Service class ConfirmService extends taiga.Service
@.$inject = ["$q", "lightboxService", "$tgLoading"] @.$inject = ["$q", "lightboxService", "$tgLoading", "$translate"]
constructor: (@q, @lightboxService, @loading) -> constructor: (@q, @lightboxService, @loading, @translate) ->
bindMethods(@) bindMethods(@)
hide: (el)-> hide: (el)->
@ -51,6 +51,8 @@ class ConfirmService extends taiga.Service
el.off(".confirm-dialog") el.off(".confirm-dialog")
ask: (title, subtitle, message, lightboxSelector=".lightbox-generic-ask") -> ask: (title, subtitle, message, lightboxSelector=".lightbox-generic-ask") ->
defered = @q.defer()
el = angular.element(lightboxSelector) el = angular.element(lightboxSelector)
# Render content # Render content
@ -58,8 +60,6 @@ class ConfirmService extends taiga.Service
el.find("span.subtitle").html(subtitle) el.find("span.subtitle").html(subtitle)
el.find("span.message").html(message) el.find("span.message").html(message)
defered = @q.defer()
# Assign event handlers # Assign event handlers
el.on "click.confirm-dialog", "a.button-green", debounce 2000, (event) => el.on "click.confirm-dialog", "a.button-green", debounce 2000, (event) =>
event.preventDefault() event.preventDefault()
@ -80,9 +80,11 @@ class ConfirmService extends taiga.Service
return defered.promise return defered.promise
askOnDelete: (title, message) -> askOnDelete: (title, message) ->
return @.ask(title, "Are you sure you want to delete?", message) #TODO: i18n return @.ask(title, @translate.instant("NOTIFICATION.ASK_DELETE"), message)
askChoice: (title, subtitle, choices, replacement, warning, lightboxSelector=".lightbox-ask-choice") -> askChoice: (title, subtitle, choices, replacement, warning, lightboxSelector=".lightbox-ask-choice") ->
defered = @q.defer()
el = angular.element(lightboxSelector) el = angular.element(lightboxSelector)
# Render content # Render content
@ -103,7 +105,6 @@ class ConfirmService extends taiga.Service
choicesField.html('') choicesField.html('')
_.each choices, (value, key) -> _.each choices, (value, key) ->
choicesField.append(angular.element("<option value='#{key}'>#{value}</option>")) choicesField.append(angular.element("<option value='#{key}'>#{value}</option>"))
defered = @q.defer()
# Assign event handlers # Assign event handlers
el.on "click.confirm-dialog", "a.button-green", debounce 2000, (event) => el.on "click.confirm-dialog", "a.button-green", debounce 2000, (event) =>
@ -127,11 +128,12 @@ class ConfirmService extends taiga.Service
return defered.promise return defered.promise
error: (message) -> error: (message) ->
defered = @q.defer()
el = angular.element(".lightbox-generic-error") el = angular.element(".lightbox-generic-error")
# Render content # Render content
el.find("h2.title").html(message) el.find("h2.title").html(message)
defered = @q.defer()
# Assign event handlers # Assign event handlers
el.on "click.confirm-dialog", "a.button-green", (event) => el.on "click.confirm-dialog", "a.button-green", (event) =>
@ -149,12 +151,13 @@ class ConfirmService extends taiga.Service
return defered.promise return defered.promise
success: (title, message) -> success: (title, message) ->
defered = @q.defer()
el = angular.element(".lightbox-generic-success") el = angular.element(".lightbox-generic-success")
# Render content # Render content
el.find("h2.title").html(title) if title el.find("h2.title").html(title) if title
el.find("p.message").html(message) if message el.find("p.message").html(message) if message
defered = @q.defer()
# Assign event handlers # Assign event handlers
el.on "click.confirm-dialog", "a.button-green", (event) => el.on "click.confirm-dialog", "a.button-green", (event) =>
@ -208,12 +211,12 @@ class ConfirmService extends taiga.Service
if title if title
el.find("h4").html(title) el.find("h4").html(title)
else else
el.find("h4").html(NOTIFICATION_MSG[type].title) el.find("h4").html(@translate.instant(NOTIFICATION_MSG[type].title))
if message if message
el.find("p").html(message) el.find("p").html(message)
else else
el.find("p").html(NOTIFICATION_MSG[type].message) el.find("p").html(@translate.instant(NOTIFICATION_MSG[type].message))
body = angular.element("body") body = angular.element("body")
body.find(".notification-message .notification-light") body.find(".notification-message .notification-light")

View File

@ -121,7 +121,7 @@ CustomAttributesValuesDirective = ($templates, $storage) ->
module.directive("tgCustomAttributesValues", ["$tgTemplate", "$tgStorage", CustomAttributesValuesDirective]) module.directive("tgCustomAttributesValues", ["$tgTemplate", "$tgStorage", CustomAttributesValuesDirective])
CustomAttributeValueDirective = ($template, $selectedText) -> CustomAttributeValueDirective = ($template, $selectedText, $compile) ->
template = $template.get("custom-attributes/custom-attribute-value.html", true) template = $template.get("custom-attributes/custom-attribute-value.html", true)
templateEdit = $template.get("custom-attributes/custom-attribute-value-edit.html", true) templateEdit = $template.get("custom-attributes/custom-attribute-value-edit.html", true)
@ -139,8 +139,10 @@ CustomAttributeValueDirective = ($template, $selectedText) ->
if editable and (edit or not value) if editable and (edit or not value)
html = templateEdit(ctx) html = templateEdit(ctx)
html = $compile(html)($scope)
else else
html = template(ctx) html = template(ctx)
html = $compile(html)($scope)
$el.html(html) $el.html(html)
@ -168,11 +170,13 @@ CustomAttributeValueDirective = ($template, $selectedText) ->
return if $selectedText.get().length return if $selectedText.get().length
render(attributeValue, true) render(attributeValue, true)
$el.find("input[name='description']").focus().select() $el.find("input[name='description']").focus().select()
$scope.$apply()
$el.on "click", "a.icon-edit", (event) -> $el.on "click", "a.icon-edit", (event) ->
event.preventDefault() event.preventDefault()
render(attributeValue, true) render(attributeValue, true)
$el.find("input[name='description']").focus().select() $el.find("input[name='description']").focus().select()
$scope.$apply()
## Actions (on edit mode) ## Actions (on edit mode)
submit = debounce 2000, (event) => submit = debounce 2000, (event) =>
@ -195,4 +199,4 @@ CustomAttributeValueDirective = ($template, $selectedText) ->
restrict: "AE" restrict: "AE"
} }
module.directive("tgCustomAttributeValue", ["$tgTemplate", "$selectedText", CustomAttributeValueDirective]) module.directive("tgCustomAttributeValue", ["$tgTemplate", "$selectedText", "$compile", CustomAttributeValueDirective])

View File

@ -28,7 +28,7 @@ module = angular.module("taigaCommon")
## User story estimation directive (for Lightboxes) ## User story estimation directive (for Lightboxes)
############################################################################# #############################################################################
LbUsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $confirm, $template) -> LbUsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $confirm, $template, $compile) ->
# Display the points of a US and you can edit it. # Display the points of a US and you can edit it.
# #
# Example: # Example:
@ -56,6 +56,7 @@ LbUsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $confirm, $
mainTemplate = "common/estimation/us-estimation-points-per-role.html" mainTemplate = "common/estimation/us-estimation-points-per-role.html"
template = $template.get(mainTemplate, true) template = $template.get(mainTemplate, true)
html = template(ctx) html = template(ctx)
html = $compile(html)($scope)
@$el.html(html) @$el.html(html)
estimationProcess.render() estimationProcess.render()
@ -68,14 +69,14 @@ LbUsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $confirm, $
require: "ngModel" require: "ngModel"
} }
module.directive("tgLbUsEstimation", ["$tgEstimationsService", "$rootScope", "$tgRepo", "$tgConfirm", "$tgTemplate", LbUsEstimationDirective]) module.directive("tgLbUsEstimation", ["$tgEstimationsService", "$rootScope", "$tgRepo", "$tgConfirm", "$tgTemplate", "$compile", LbUsEstimationDirective])
############################################################################# #############################################################################
## User story estimation directive ## User story estimation directive
############################################################################# #############################################################################
UsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $confirm, $qqueue, $template) -> UsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $confirm, $qqueue, $template, $compile) ->
# Display the points of a US and you can edit it. # Display the points of a US and you can edit it.
# #
# Example: # Example:
@ -102,6 +103,7 @@ UsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $confirm, $qq
mainTemplate = "common/estimation/us-estimation-points-per-role.html" mainTemplate = "common/estimation/us-estimation-points-per-role.html"
template = $template.get(mainTemplate, true) template = $template.get(mainTemplate, true)
html = template(ctx) html = template(ctx)
html = $compile(html)($scope)
@$el.html(html) @$el.html(html)
estimationProcess.render() estimationProcess.render()
@ -115,7 +117,7 @@ UsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $confirm, $qq
require: "ngModel" require: "ngModel"
} }
module.directive("tgUsEstimation", ["$tgEstimationsService", "$rootScope", "$tgRepo", "$tgConfirm", "$tgQqueue", "$tgTemplate", module.directive("tgUsEstimation", ["$tgEstimationsService", "$rootScope", "$tgRepo", "$tgConfirm", "$tgQqueue", "$tgTemplate", "$compile"
UsEstimationDirective]) UsEstimationDirective])

View File

@ -33,15 +33,14 @@ defaultFilter = ->
module.filter("default", defaultFilter) module.filter("default", defaultFilter)
yesNoFilter = -> yesNoFilter = ($translate) ->
#TODO: i18n
return (value) -> return (value) ->
if value if value
return "Yes" return $translate.instant("COMMON.YES")
return "No" return $translate.instant("COMMON.NO")
module.filter("yesNo", yesNoFilter) module.filter("yesNo", ["$translate", yesNoFilter])
unslugify = -> unslugify = ->

View File

@ -68,7 +68,7 @@ class HistoryController extends taiga.Controller
return @rs.history.undeleteComment(type, objectId, activityId).then => @.loadHistory(type, objectId) return @rs.history.undeleteComment(type, objectId, activityId).then => @.loadHistory(type, objectId)
HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) -> HistoryDirective = ($log, $loading, $qqueue, $template, $confirm, $translate, $compile) ->
templateChangeDiff = $template.get("common/history/history-change-diff.html", true) templateChangeDiff = $template.get("common/history/history-change-diff.html", true)
templateChangePoints = $template.get("common/history/history-change-points.html", true) templateChangePoints = $template.get("common/history/history-change-points.html", true)
templateChangeGeneric = $template.get("common/history/history-change-generic.html", true) templateChangeGeneric = $template.get("common/history/history-change-generic.html", true)
@ -87,6 +87,9 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) ->
showAllComments = false showAllComments = false
showAllActivity = false showAllActivity = false
getPrettyDateFormat = ->
return $translate.instant("ACTIVITY.DATETIME")
bindOnce $scope, $attrs.ngModel, (model) -> bindOnce $scope, $attrs.ngModel, (model) ->
type = $attrs.type type = $attrs.type
objectId = model.id objectId = model.id
@ -97,24 +100,40 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) ->
# Helpers # Helpers
getHumanizedFieldName = (field) -> getHumanizedFieldName = (field) ->
humanizedFieldNames = { humanizedFieldNames = {
# US subject : $translate.instant("ACTIVITY.FIELDS.SUBJECT")
assigned_to: "assigned to" name: $translate.instant("ACTIVITY.FIELDS.NAME")
is_closed: "is closed" description : $translate.instant("ACTIVITY.FIELDS.DESCRIPTION")
finish_date: "finish date" content: $translate.instant("ACTIVITY.FIELDS.CONTENT")
client_requirement: "client requirement" status: $translate.instant("ACTIVITY.FIELDS.STATUS")
team_requirement: "team requirement" is_closed : $translate.instant("ACTIVITY.FIELDS.IS_CLOSED")
finish_date : $translate.instant("ACTIVITY.FIELDS.FINISH_DATE")
type: $translate.instant("ACTIVITY.FIELDS.TYPE")
priority: $translate.instant("ACTIVITY.FIELDS.PRIORITY")
severity: $translate.instant("ACTIVITY.FIELDS.SEVERITY")
assigned_to : $translate.instant("ACTIVITY.FIELDS.ASSIGNED_TO")
watchers : $translate.instant("ACTIVITY.FIELDS.WATCHERS")
milestone : $translate.instant("ACTIVITY.FIELDS.MILESTONE")
user_story: $translate.instant("ACTIVITY.FIELDS.USER_STORY")
project: $translate.instant("ACTIVITY.FIELDS.PROJECT")
is_blocked: $translate.instant("ACTIVITY.FIELDS.IS_BLOCKED")
blocked_note: $translate.instant("ACTIVITY.FIELDS.BLOCKED_NOTE")
points: $translate.instant("ACTIVITY.FIELDS.POINTS")
client_requirement : $translate.instant("ACTIVITY.FIELDS.CLIENT_REQUIREMENT")
team_requirement : $translate.instant("ACTIVITY.FIELDS.TEAM_REQUIREMENT")
is_iocaine: $translate.instant("ACTIVITY.FIELDS.IS_IOCAINE")
tags: $translate.instant("ACTIVITY.FIELDS.TAGS")
attachments : $translate.instant("ACTIVITY.FIELDS.ATTACHMENTS")
is_deprecated: $translate.instant("ACTIVITY.FIELDS.IS_DEPRECATED")
blocked_note: $translate.instant("ACTIVITY.FIELDS.BLOCKED_NOTE")
is_blocked: $translate.instant("ACTIVITY.FIELDS.IS_BLOCKED")
order: $translate.instant("ACTIVITY.FIELDS.ORDER")
backlog_order: $translate.instant("ACTIVITY.FIELDS.BACKLOG_ORDER")
sprint_order: $translate.instant("ACTIVITY.FIELDS.SPRINT_ORDER")
kanban_order: $translate.instant("ACTIVITY.FIELDS.KANBAN_ORDER")
taskboard_order: $translate.instant("ACTIVITY.FIELDS.TASKBOARD_ORDER")
us_order: $translate.instant("ACTIVITY.FIELDS.US_ORDER")
}
# Task
milestone: "sprint"
user_story: "user story"
is_iocaine: "is iocaine"
# Attachment
is_deprecated: "is deprecated"
blocked_note: "blocked note"
is_blocked: "is blocked"
} # TODO i18n
return humanizedFieldNames[field] or field return humanizedFieldNames[field] or field
getUserFullName = (userId) -> getUserFullName = (userId) ->
@ -132,17 +151,17 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) ->
formatChange = (change) -> formatChange = (change) ->
if _.isArray(change) if _.isArray(change)
if change.length == 0 if change.length == 0
return "empty" return $translate.instant("ACTIVITY.VALUES.EMPTY")
return change.join(", ") return change.join(", ")
if change == "" if change == ""
return "empty" return $translate.instant("ACTIVITY.VALUES.EMPTY")
if not change? or change == false if not change? or change == false
return "no" return $translate.instant("ACTIVITY.VALUES.NO")
if change == true if change == true
return "yes" return $translate.instant("ACTIVITY.VALUES.YES")
return change return change
@ -152,22 +171,26 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) ->
attachments = _.map value, (changes, type) -> attachments = _.map value, (changes, type) ->
if type == "new" if type == "new"
return _.map changes, (change) -> return _.map changes, (change) ->
# TODO: i18n return templateChangeDiff({
return templateChangeDiff({name: "new attachment", diff: change.filename}) name: $translate.instant("ACTIVITY.NEW_ATTACHMENT"),
diff: change.filename
})
else if type == "deleted" else if type == "deleted"
return _.map changes, (change) -> return _.map changes, (change) ->
# TODO: i18n return templateChangeDiff({
return templateChangeDiff({name: "deleted attachment", diff: change.filename}) name: $translate.instant("ACTIVITY.DELETED_ATTACHMENT"),
diff: change.filename
})
else else
return _.map changes, (change) -> return _.map changes, (change) ->
# TODO: i18n name = $translate.instant("ACTIVITY.UPDATED_ATTACHMENT", {filename: change.filename})
name = "updated attachment #{change.filename}"
diff = _.map change.changes, (values, name) -> diff = _.map change.changes, (values, name) ->
return { return {
name: getHumanizedFieldName(name) name: getHumanizedFieldName(name)
from: formatChange(values[0]) from: formatChange(values[0])
to: formatChange(values[1]) to: formatChange(values[1])
} }
return templateChangeAttachment({name: name, diff: diff}) return templateChangeAttachment({name: name, diff: diff})
@ -177,16 +200,19 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) ->
customAttributes = _.map value, (changes, type) -> customAttributes = _.map value, (changes, type) ->
if type == "new" if type == "new"
return _.map changes, (change) -> return _.map changes, (change) ->
return templateChangeGeneric({ html = templateChangeGeneric({
name: change.name, name: change.name,
from: formatChange(""), from: formatChange(""),
to: formatChange(change.value) to: formatChange(change.value)
}) })
html = $compile(html)($scope)
return html[0].outerHTML
else if type == "deleted" else if type == "deleted"
return _.map changes, (change) -> return _.map changes, (change) ->
# TODO: i18n
return templateChangeDiff({ return templateChangeDiff({
name: "deleted custom attribute", name: $translate.instant("ACTIVITY.DELETED_CUSTOM_ATTRIBUTE")
diff: change.name diff: change.name
}) })
else else
@ -207,7 +233,11 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) ->
else if field == "blocked_note" else if field == "blocked_note"
return templateChangeDiff({name: getHumanizedFieldName("blocked_note"), diff: value[1]}) return templateChangeDiff({name: getHumanizedFieldName("blocked_note"), diff: value[1]})
else if field == "points" else if field == "points"
return templateChangePoints({points: value}) html = templateChangePoints({points: value})
html = $compile(html)($scope)
return html[0].outerHTML
else if field == "attachments" else if field == "attachments"
return renderAttachmentEntry(value) return renderAttachmentEntry(value)
else if field == "custom_attributes" else if field == "custom_attributes"
@ -216,11 +246,15 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) ->
name = getHumanizedFieldName(field) name = getHumanizedFieldName(field)
removed = _.difference(value[0], value[1]) removed = _.difference(value[0], value[1])
added = _.difference(value[1], value[0]) added = _.difference(value[1], value[0])
return templateChangeList({name:name, removed:removed, added: added}) html = templateChangeList({name:name, removed:removed, added: added})
html = $compile(html)($scope)
return html[0].outerHTML
else if field == "assigned_to" else if field == "assigned_to"
name = getHumanizedFieldName(field) name = getHumanizedFieldName(field)
from = formatChange(value[0] or "Unassigned") from = formatChange(value[0] or $translate.instant("ACTIVITY.VALUES.UNASSIGNED"))
to = formatChange(value[1] or "Unassigned") to = formatChange(value[1] or $translate.instant("ACTIVITY.VALUES.UNASSIGNED"))
return templateChangeGeneric({name:name, from:from, to: to}) return templateChangeGeneric({name:name, from:from, to: to})
else else
name = getHumanizedFieldName(field) name = getHumanizedFieldName(field)
@ -233,44 +267,51 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) ->
renderChangesHelperText = (change) -> renderChangesHelperText = (change) ->
size = countChanges(change) size = countChanges(change)
if size == 1 return $translate.instant("ACTIVITY.SIZE_CHANGE", {size: size}, 'messageformat')
return "Made #{size} change" # TODO: i18n
return "Made #{size} changes" # TODO: i18n
renderComment = (comment) -> renderComment = (comment) ->
if (comment.delete_comment_date or comment.delete_comment_user?.name) if (comment.delete_comment_date or comment.delete_comment_user?.name)
return templateDeletedComment({ html = templateDeletedComment({
deleteCommentDate: moment(comment.delete_comment_date).format("DD MMM YYYY HH:mm") if comment.delete_comment_date deleteCommentDate: moment(comment.delete_comment_date).format(getPrettyDateFormat()) if comment.delete_comment_date
deleteCommentUser: comment.delete_comment_user.name deleteCommentUser: comment.delete_comment_user.name
deleteComment: comment.comment_html deleteComment: comment.comment_html
activityId: comment.id activityId: comment.id
canRestoreComment: comment.delete_comment_user.pk == $scope.user.id or $scope.project.my_permissions.indexOf("modify_project") > -1 canRestoreComment: (comment.delete_comment_user.pk == $scope.user.id or
$scope.project.my_permissions.indexOf("modify_project") > -1)
}) })
return templateActivity({ html = $compile(html)($scope)
return html[0].outerHTML
html = templateActivity({
avatar: getUserAvatar(comment.user.pk) avatar: getUserAvatar(comment.user.pk)
userFullName: comment.user.name userFullName: comment.user.name
creationDate: moment(comment.created_at).format("DD MMM YYYY HH:mm") creationDate: moment(comment.created_at).format(getPrettyDateFormat())
comment: comment.comment_html comment: comment.comment_html
changesText: renderChangesHelperText(comment) changesText: renderChangesHelperText(comment)
changes: renderChangeEntries(comment) changes: renderChangeEntries(comment)
mode: "comment" mode: "comment"
deleteCommentDate: moment(comment.delete_comment_date).format("DD MMM YYYY HH:mm") if comment.delete_comment_date deleteCommentDate: moment(comment.delete_comment_date).format(getPrettyDateFormat()) if comment.delete_comment_date
deleteCommentUser: comment.delete_comment_user.name if comment.delete_comment_user?.name deleteCommentUser: comment.delete_comment_user.name if comment.delete_comment_user?.name
activityId: comment.id activityId: comment.id
canDeleteComment: comment.user.pk == $scope.user?.id or $scope.project.my_permissions.indexOf("modify_project") > -1 canDeleteComment: comment.user.pk == $scope.user?.id or $scope.project.my_permissions.indexOf("modify_project") > -1
}) })
html = $compile(html)($scope)
return html[0].outerHTML
renderChange = (change) -> renderChange = (change) ->
return templateActivity({ return templateActivity({
avatar: getUserAvatar(change.user.pk) avatar: getUserAvatar(change.user.pk)
userFullName: change.user.name userFullName: change.user.name
creationDate: moment(change.created_at).format("DD MMM YYYY HH:mm") creationDate: moment(change.created_at).format(getPrettyDateFormat())
comment: change.comment_html comment: change.comment_html
changes: renderChangeEntries(change) changes: renderChangeEntries(change)
changesText: "" changesText: ""
mode: "activity" mode: "activity"
deleteCommentDate: moment(change.delete_comment_date).format("DD MMM YYYY HH:mm") if change.delete_comment_date deleteCommentDate: moment(change.delete_comment_date).format(getPrettyDateFormat()) if change.delete_comment_date
deleteCommentUser: change.delete_comment_user.name if change.delete_comment_user?.name deleteCommentUser: change.delete_comment_user.name if change.delete_comment_user?.name
activityId: change.id activityId: change.id
}) })
@ -281,7 +322,9 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) ->
else else
showMore = totalEntries - entries.length showMore = totalEntries - entries.length
return templateBaseEntries({entries: entries, showMore:showMore}) html = templateBaseEntries({entries: entries, showMore:showMore})
html = $compile(html)($scope)
return html
# Render into DOM (operations with dom mutability) # Render into DOM (operations with dom mutability)
@ -394,7 +437,9 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) ->
$el.off() $el.off()
templateFn = ($el, $attrs) -> templateFn = ($el, $attrs) ->
return templateBase({ngmodel: $attrs.ngModel, type: $attrs.type, mode: $attrs.mode}) html = templateBase({ngmodel: $attrs.ngModel, type: $attrs.type, mode: $attrs.mode})
return html
return { return {
controller: HistoryController controller: HistoryController
@ -405,4 +450,5 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) ->
} }
module.directive("tgHistory", ["$log", "$tgLoading", "$tgQqueue", "$tgTemplate", "$tgConfirm", HistoryDirective]) module.directive("tgHistory", ["$log", "$tgLoading", "$tgQqueue", "$tgTemplate", "$tgConfirm", "$translate",
"$compile", HistoryDirective])

View File

@ -22,7 +22,7 @@
module = angular.module("taigaCommon") module = angular.module("taigaCommon")
ImportProjectButtonDirective = ($rs, $confirm, $location, $navUrls) -> ImportProjectButtonDirective = ($rs, $confirm, $location, $navUrls, $translate) ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
$el.on "click", ".import-project-button", (event) -> $el.on "click", ".import-project-button", (event) ->
event.preventDefault() event.preventDefault()
@ -34,34 +34,29 @@ ImportProjectButtonDirective = ($rs, $confirm, $location, $navUrls) ->
file = event.target.files[0] file = event.target.files[0]
return if not file return if not file
loader = $confirm.loader("Uploading dump file") loader = $confirm.loader($translate.instant("PROJECT.IMPORT.UPLOADING_FILE"))
onSuccess = (result) -> onSuccess = (result) ->
loader.stop() loader.stop()
if result.status == 202 # Async mode if result.status == 202 # Async mode
title = "Our Oompa Loompas are importing your project" # TODO: i18n title = $translate.instant("PROJECT.IMPORT.ASYNC_IN_PROGRESS_TITLE")
message = "This process could take a few minutes <br/> We will send you message = $translate.instant("PROJECT.IMPORT.ASYNC_IN_PROGRESS_MESSAGE")
an email when ready" # TODO: i18n
$confirm.success(title, message) $confirm.success(title, message)
else # result.status == 201 # Sync mode else # result.status == 201 # Sync mode
ctx = {project: result.data.slug} ctx = {project: result.data.slug}
$location.path($navUrls.resolve("project-admin-project-profile-details", ctx)) $location.path($navUrls.resolve("project-admin-project-profile-details", ctx))
$confirm.notify("success", "Your project has been imported successfuly.") # TODO: i18n msg = $translate.instant("PROJECT.IMPORT.SYNC_SUCCESS")
$confirm.notify("success", msg)
onError = (result) -> onError = (result) ->
loader.stop() loader.stop()
console.log "Error", result errorMsg = $translate.instant("PROJECT.IMPORT.ERROR")
errorMsg = "Our oompa loompas have some problems importing your dump data.
Please try again. " # TODO: i18n
if result.status == 429 # TOO MANY REQUESTS if result.status == 429 # TOO MANY REQUESTS
errorMsg = "Sorry, our oompa loompas are very busy right now. errorMsg = $translate.instant("PROJECT.IMPORT.ERROR_TOO_MANY_REQUEST")
Please try again in a few minutes. " # TODO: i18n
else if result.data?._error_message else if result.data?._error_message
errorMsg = "Our oompa loompas have some problems importing your dump data: errorMsg = $translate.instant("PROJECT.IMPORT.ERROR_MESSAGE", {error_message: result.data._error_message})
#{result.data._error_message}" # TODO: i18n
$confirm.notify("error", errorMsg) $confirm.notify("error", errorMsg)
loader.start() loader.start()
@ -69,5 +64,5 @@ ImportProjectButtonDirective = ($rs, $confirm, $location, $navUrls) ->
return {link: link} return {link: link}
module.directive("tgImportProjectButton", ["$tgResources", "$tgConfirm", "$location", "$tgNavUrls", module.directive("tgImportProjectButton", ["$tgResources", "$tgConfirm", "$location", "$tgNavUrls", "$translate",
ImportProjectButtonDirective]) ImportProjectButtonDirective])

View File

@ -86,7 +86,10 @@ class LightboxKeyboardNavigationService extends taiga.Service
# Key: enter # Key: enter
if code == 13 if code == 13
activeElement.trigger("click") if $el.find(".watcher-single").length == 1
$el.find('.watcher-single:first').trigger("click")
else
activeElement.trigger("click")
# Key: down # Key: down
else if code == 40 else if code == 40
@ -143,9 +146,10 @@ module.directive("lightbox", ["lightboxService", LightboxDirective])
# Issue/Userstory blocking message lightbox directive. # Issue/Userstory blocking message lightbox directive.
BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loading, $qqueue) -> BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loading, $qqueue, $translate) ->
link = ($scope, $el, $attrs, $model) -> link = ($scope, $el, $attrs, $model) ->
$el.find("h2.title").text($attrs.title) $translate($attrs.title).then (title) ->
$el.find("h2.title").text(title)
unblock = $qqueue.bindAdd (item, finishCallback) => unblock = $qqueue.bindAdd (item, finishCallback) =>
promise = $tgrepo.save(item) promise = $tgrepo.save(item)
@ -213,14 +217,14 @@ BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loadi
require: "ngModel" require: "ngModel"
} }
module.directive("tgLbBlock", ["$rootScope", "$tgRepo", "$tgConfirm", "lightboxService", "$tgLoading", "$tgQqueue", BlockLightboxDirective]) module.directive("tgLbBlock", ["$rootScope", "$tgRepo", "$tgConfirm", "lightboxService", "$tgLoading", "$tgQqueue", "$translate", BlockLightboxDirective])
############################################################################# #############################################################################
## Generic Lightbox Blocking-Message Input Directive ## Generic Lightbox Blocking-Message Input Directive
############################################################################# #############################################################################
BlockingMessageInputDirective = ($log, $template) -> BlockingMessageInputDirective = ($log, $template, $compile) ->
template = $template.get("common/lightbox/lightbox-blocking-message-input.html", true) template = $template.get("common/lightbox/lightbox-blocking-message-input.html", true)
link = ($scope, $el, $attrs, $model) -> link = ($scope, $el, $attrs, $model) ->
@ -243,14 +247,14 @@ BlockingMessageInputDirective = ($log, $template) ->
restrict: "EA" restrict: "EA"
} }
module.directive("tgBlockingMessageInput", ["$log", "$tgTemplate", BlockingMessageInputDirective]) module.directive("tgBlockingMessageInput", ["$log", "$tgTemplate", "$compile", BlockingMessageInputDirective])
############################################################################# #############################################################################
## Create/Edit Userstory Lightbox Directive ## Create/Edit Userstory Lightbox Directive
############################################################################# #############################################################################
CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService, $loading) -> CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService, $loading, $translate) ->
link = ($scope, $el, attrs) -> link = ($scope, $el, attrs) ->
$scope.isNew = true $scope.isNew = true
@ -267,8 +271,8 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService,
}) })
# Update texts for creation # Update texts for creation
$el.find(".button-green").html("Create") #TODO: i18n $el.find(".button-green").html($translate.instant("COMMON.CREATE"))
$el.find(".title").html("New user story ") #TODO: i18n $el.find(".title").html($translate.instant("LIGHTBOX.CREATE_EDIT_US.NEW_US"))
$el.find(".tag-input").val("") $el.find(".tag-input").val("")
$el.find(".blocked-note").addClass("hidden") $el.find(".blocked-note").addClass("hidden")
@ -283,8 +287,8 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService,
$scope.isNew = false $scope.isNew = false
# Update texts for edition # Update texts for edition
$el.find(".button-green").html("Save") #TODO: i18n $el.find(".button-green").html($translate.instant("COMMON.SAVE"))
$el.find(".title").html("Edit user story ") #TODO: i18n $el.find(".title").html($translate.instant("LIGHTBOX.CREATE_EDIT_US.EDIT_US"))
$el.find(".tag-input").val("") $el.find(".tag-input").val("")
# Update requirement info (team, client or blocked) # Update requirement info (team, client or blocked)
@ -362,6 +366,7 @@ module.directive("tgLbCreateEditUserstory", [
"$rootScope", "$rootScope",
"lightboxService", "lightboxService",
"$tgLoading", "$tgLoading",
"$translate",
CreateEditUserstoryDirective CreateEditUserstoryDirective
]) ])
@ -424,7 +429,7 @@ module.directive("tgLbCreateBulkUserstories", [
## AssignedTo Lightbox Directive ## AssignedTo Lightbox Directive
############################################################################# #############################################################################
AssignedToLightboxDirective = (lightboxService, lightboxKeyboardNavigationService, $template) -> AssignedToLightboxDirective = (lightboxService, lightboxKeyboardNavigationService, $template, $compile) ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
selectedUser = null selectedUser = null
selectedItem = null selectedItem = null
@ -458,6 +463,9 @@ AssignedToLightboxDirective = (lightboxService, lightboxKeyboardNavigationServic
} }
html = usersTemplate(ctx) html = usersTemplate(ctx)
html = $compile(html)($scope)
$el.find("div.watchers").html(html) $el.find("div.watchers").html(html)
lightboxKeyboardNavigationService.init($el) lightboxKeyboardNavigationService.init($el)
@ -517,7 +525,7 @@ AssignedToLightboxDirective = (lightboxService, lightboxKeyboardNavigationServic
} }
module.directive("tgLbAssignedto", ["lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", AssignedToLightboxDirective]) module.directive("tgLbAssignedto", ["lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", "$compile", AssignedToLightboxDirective])
############################################################################# #############################################################################
@ -554,6 +562,7 @@ WatchersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationS
html = usersTemplate(ctx) html = usersTemplate(ctx)
$el.find("div.watchers").html(html) $el.find("div.watchers").html(html)
lightboxKeyboardNavigationService.init($el)
closeLightbox = () -> closeLightbox = () ->
lightboxKeyboardNavigationService.stop() lightboxKeyboardNavigationService.stop()

View File

@ -99,7 +99,7 @@ module.directive("tgColorizeTags", ColorizeTagsDirective)
## TagLine Directive (for Lightboxes) ## TagLine Directive (for Lightboxes)
############################################################################# #############################################################################
LbTagLineDirective = ($rs, $template) -> LbTagLineDirective = ($rs, $template, $compile) ->
ENTER_KEY = 13 ENTER_KEY = 13
COMMA_KEY = 188 COMMA_KEY = 188
@ -116,7 +116,7 @@ LbTagLineDirective = ($rs, $template) ->
if tag.color if tag.color
tag.style = "border-left: 5px solid #{tag.color}" tag.style = "border-left: 5px solid #{tag.color}"
html = templateTags(ctx) html = $compile(templateTags(ctx))($scope)
$el.find("div.tags-container").html(html) $el.find("div.tags-container").html(html)
showSaveButton = -> $el.find(".save").removeClass("hidden") showSaveButton = -> $el.find(".save").removeClass("hidden")
@ -221,14 +221,14 @@ LbTagLineDirective = ($rs, $template) ->
templateUrl: "common/tag/lb-tag-line.html" templateUrl: "common/tag/lb-tag-line.html"
} }
module.directive("tgLbTagLine", ["$tgResources", "$tgTemplate", LbTagLineDirective]) module.directive("tgLbTagLine", ["$tgResources", "$tgTemplate", "$compile", LbTagLineDirective])
############################################################################# #############################################################################
## TagLine Directive (for detail pages) ## TagLine Directive (for detail pages)
############################################################################# #############################################################################
TagLineDirective = ($rootScope, $repo, $rs, $confirm, $qqueue, $template) -> TagLineDirective = ($rootScope, $repo, $rs, $confirm, $qqueue, $template, $compile) ->
ENTER_KEY = 13 ENTER_KEY = 13
ESC_KEY = 27 ESC_KEY = 27
COMMA_KEY = 188 COMMA_KEY = 188
@ -237,7 +237,10 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $qqueue, $template) ->
link = ($scope, $el, $attrs, $model) -> link = ($scope, $el, $attrs, $model) ->
isEditable = -> isEditable = ->
return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1 if $attrs.requiredPerm?
return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1
return true
## Render ## Render
renderTags = (tags, tagsColors) -> renderTags = (tags, tagsColors) ->
@ -245,7 +248,7 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $qqueue, $template) ->
tags: _.map(tags, (t) -> {name: t, color: tagsColors[t]}) tags: _.map(tags, (t) -> {name: t, color: tagsColors[t]})
isEditable: isEditable() isEditable: isEditable()
} }
html = templateTags(ctx) html = $compile(templateTags(ctx))($scope)
$el.find("div.tags-container").html(html) $el.find("div.tags-container").html(html)
renderInReadModeOnly = -> renderInReadModeOnly = ->
@ -362,7 +365,7 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $qqueue, $template) ->
deleteValue(value) deleteValue(value)
bindOnce $scope, "project", (project) -> bindOnce $scope, "project.tags_colors", (tags_colors) ->
if not isEditable() if not isEditable()
renderInReadModeOnly() renderInReadModeOnly()
return return
@ -376,7 +379,7 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $qqueue, $template) ->
menu.css("left", position.left) menu.css("left", position.left)
$el.find("input").autocomplete({ $el.find("input").autocomplete({
source: _.keys(project.tags_colors) source: _.keys(tags_colors)
position: { position: {
my: "left top", my: "left top",
using: positioningFunction using: positioningFunction
@ -406,4 +409,5 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $qqueue, $template) ->
templateUrl: "common/tag/tag-line.html" templateUrl: "common/tag/tag-line.html"
} }
module.directive("tgTagLine", ["$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", "$tgQqueue", "$tgTemplate", TagLineDirective]) module.directive("tgTagLine", ["$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", "$tgQqueue",
"$tgTemplate", "$compile", TagLineDirective])

View File

@ -55,16 +55,13 @@ module = angular.module("taigaCommon")
############################################################################# #############################################################################
## WYSIWYG markitup editor directive ## WYSIWYG markitup editor directive
############################################################################# #############################################################################
tgMarkitupDirective = ($rootscope, $rs, $tr, $selectedText, $template) -> MarkitupDirective = ($rootscope, $rs, $selectedText, $template, $compile, $translate) ->
previewTemplate = $template.get("common/wysiwyg/wysiwyg-markitup-preview.html", true) previewTemplate = $template.get("common/wysiwyg/wysiwyg-markitup-preview.html", true)
link = ($scope, $el, $attrs, $model) -> link = ($scope, $el, $attrs, $model) ->
element = angular.element($el) element = angular.element($el)
previewDomNode = $("<div/>", {class: "preview"}) previewDomNode = $("<div/>", {class: "preview"})
#openHelp = ->
# window.open($rootscope.urls.wikiHelpUrl(), "_blank")
closePreviewMode = -> closePreviewMode = ->
element.parents(".markdown").find(".preview").remove() element.parents(".markdown").find(".preview").remove()
element.parents(".markItUp").show() element.parents(".markItUp").show()
@ -76,7 +73,10 @@ tgMarkitupDirective = ($rootscope, $rs, $tr, $selectedText, $template) ->
markdownDomNode = element.parents(".markdown") markdownDomNode = element.parents(".markdown")
markItUpDomNode = element.parents(".markItUp") markItUpDomNode = element.parents(".markItUp")
$rs.mdrender.render($scope.projectId, $model.$modelValue).then (data) -> $rs.mdrender.render($scope.projectId, $model.$modelValue).then (data) ->
markdownDomNode.append(previewTemplate({data: data.data})) html = previewTemplate({data: data.data})
html = $compile(html)($scope)
markdownDomNode.append(html)
markItUpDomNode.hide() markItUpDomNode.hide()
markdown = element.closest(".markdown") markdown = element.closest(".markdown")
@ -130,168 +130,14 @@ tgMarkitupDirective = ($rootscope, $rs, $tr, $selectedText, $template) ->
else else
return cursorPosition return cursorPosition
markdownSettings =
nameSpace: "markdown"
onShiftEnter: {keepDefault:false, openWith:"\n\n"}
onEnter:
keepDefault: false,
replaceWith: () -> "\n"
afterInsert: (data) ->
lines = data.textarea.value.split("\n")
cursorLine = data.textarea.value[0..(data.caretPosition - 1)].split("\n").length
newLineContent = data.textarea.value[data.caretPosition..].split("\n")[0]
lastLine = lines[cursorLine - 1]
# unordered list -
match = lastLine.match /^(\s*- ).*/
if match
emptyListItem = lastLine.match /^(\s*)\-\s$/
if emptyListItem
markdownCaretPositon = addLine(data.textarea, cursorLine - 1)
else
markdownCaretPositon = addLine(data.textarea, cursorLine, "#{match[1]}")
# unordered list *
match = lastLine.match /^(\s*\* ).*/
if match
emptyListItem = lastLine.match /^(\s*\* )$/
if emptyListItem
markdownCaretPositon = addLine(data.textarea, cursorLine - 1)
else
markdownCaretPositon = addLine(data.textarea, cursorLine, "#{match[1]}")
# ordered list
match = lastLine.match /^(\s*)(\d+)\.\s/
if match
emptyListItem = lastLine.match /^(\s*)(\d+)\.\s$/
if emptyListItem
markdownCaretPositon = addLine(data.textarea, cursorLine - 1)
else
markdownCaretPositon = addLine(data.textarea, cursorLine, "#{match[1] + (parseInt(match[2], 10) + 1)}. ")
setCaretPosition(data.textarea, markdownCaretPositon) if markdownCaretPositon
markupSet: [
{
name: $tr.t("markdown-editor.heading-1")
key: "1"
placeHolder: $tr.t("markdown-editor.placeholder")
closeWith: (markItUp) -> markdownTitle(markItUp, "=")
},
{
name: $tr.t("markdown-editor.heading-2")
key: "2"
placeHolder: $tr.t("markdown-editor.placeholder")
closeWith: (markItUp) -> markdownTitle(markItUp, "-")
},
{
name: $tr.t("markdown-editor.heading-3")
key: "3"
openWith: "### "
placeHolder: $tr.t("markdown-editor.placeholder")
},
{
separator: "---------------"
},
{
name: $tr.t("markdown-editor.bold")
key: "B"
openWith: "**"
closeWith: "**"
},
{
name: $tr.t("markdown-editor.italic")
key: "I"
openWith: "_"
closeWith: "_"
},
{
name: $tr.t("markdown-editor.strike")
key: "S"
openWith: "~~"
closeWith: "~~"
},
{
separator: "---------------"
},
{
name: $tr.t("markdown-editor.bulleted-list")
openWith: "- "
},
{
name: $tr.t("markdown-editor.numeric-list")
openWith: (markItUp) -> markItUp.line+". "
},
{
separator: "---------------"
},
{
name: $tr.t("markdown-editor.picture")
key: "P"
replaceWith: '![[![Alternative text]!]](<<<[![Url:!:http://]!]>>> "[![Title]!]")'
beforeInsert:(markItUp) -> prepareUrlFormatting(markItUp)
afterInsert:(markItUp) -> urlFormatting(markItUp)
},
{
name: $tr.t("markdown-editor.link")
key: "L"
openWith: "["
closeWith: '](<<<[![Url:!:http://]!]>>> "[![Title]!]")'
placeHolder: $tr.t("markdown-editor.link-placeholder")
beforeInsert:(markItUp) -> prepareUrlFormatting(markItUp)
afterInsert:(markItUp) -> urlFormatting(markItUp)
},
{
separator: "---------------"
},
{
name: $tr.t("markdown-editor.quotes")
openWith: "> "
},
{
name: $tr.t("markdown-editor.code-block")
openWith: "```\n"
closeWith: "\n```"
},
{
separator: "---------------"
},
{
name: $tr.t("markdown-editor.preview")
call: preview
className: "preview-icon"
},
# {
# separator: "---------------"
# },
# {
# name: $tr.t("markdown-editor.help")
# call: openHelp
# className: "help"
# }
]
afterInsert: (event) ->
target = angular.element(event.textarea)
$model.$setViewValue(target.val())
prepareUrlFormatting = (markItUp) -> prepareUrlFormatting = (markItUp) ->
console.log(markItUp)
regex = /(<<<|>>>)/gi regex = /(<<<|>>>)/gi
result = 0 result = 0
indices = [] indices = []
(indices.push(result.index)) while ( (result = regex.exec(markItUp.textarea.value)) ) (indices.push(result.index)) while ( (result = regex.exec(markItUp.textarea.value)) )
markItUp.donotparse = indices markItUp.donotparse = indices
console.log(indices)
urlFormatting = (markItUp) -> urlFormatting = (markItUp) ->
console.log(markItUp.donotparse)
regex = /<<</gi regex = /<<</gi
result = 0 result = 0
startIndex = 0 startIndex = 0
@ -329,13 +175,186 @@ tgMarkitupDirective = ($rootscope, $rs, $tr, $selectedText, $template) ->
return "\n"+heading+"\n" return "\n"+heading+"\n"
element.markItUp(markdownSettings) renderMarkItUp = () ->
markdownSettings =
nameSpace: "markdown"
onShiftEnter: {keepDefault:false, openWith:"\n\n"}
onEnter:
keepDefault: false,
replaceWith: () -> "\n"
afterInsert: (data) ->
lines = data.textarea.value.split("\n")
cursorLine = data.textarea.value[0..(data.caretPosition - 1)].split("\n").length
newLineContent = data.textarea.value[data.caretPosition..].split("\n")[0]
lastLine = lines[cursorLine - 1]
# unordered list -
match = lastLine.match /^(\s*- ).*/
if match
emptyListItem = lastLine.match /^(\s*)\-\s$/
if emptyListItem
nline = cursorLine - 1
replace = null
else
nline = cursorLine
replace = "#{match[1]}"
markdownCaretPositon = addLine(data.textarea, nline, replace)
# unordered list *
match = lastLine.match /^(\s*\* ).*/
if match
emptyListItem = lastLine.match /^(\s*\* )$/
if emptyListItem
nline = cursorLine - 1
replace = null
else
nline = cursorLine
replace = "#{match[1]}"
markdownCaretPositon = addLine(data.textarea, nline, replace)
# ordered list
match = lastLine.match /^(\s*)(\d+)\.\s/
if match
emptyListItem = lastLine.match /^(\s*)(\d+)\.\s$/
if emptyListItem
nline = cursorLine - 1
replace = null
else
nline = cursorLine
replace = "#{match[1] + (parseInt(match[2], 10) + 1)}. "
markdownCaretPositon = addLine(data.textarea, nline, replace)
setCaretPosition(data.textarea, markdownCaretPositon) if markdownCaretPositon
markupSet: [
{
name: $translate.instant("COMMON.WYSIWYG.H1_BUTTON")
key: "1"
placeHolder: $translate.instant("COMMON.WYSIWYG.H1_SAMPLE_TEXT")
closeWith: (markItUp) -> markdownTitle(markItUp, "=")
},
{
name: $translate.instant("COMMON.WYSIWYG.H2_BUTTON")
key: "2"
placeHolder: $translate.instant("COMMON.WYSIWYG.H2_SAMPLE_TEXT")
closeWith: (markItUp) -> markdownTitle(markItUp, "-")
},
{
name: $translate.instant("COMMON.WYSIWYG.H3_BUTTON")
key: "3"
openWith: "### "
placeHolder: $translate.instant("COMMON.WYSIWYG.H3_SAMPLE_TEXT")
},
{
separator: "---------------"
},
{
name: $translate.instant("COMMON.WYSIWYG.BOLD_BUTTON")
key: "B"
openWith: "**"
closeWith: "**"
placeHolder: $translate.instant("COMMON.WYSIWYG.BOLD_BUTTON_SAMPLE_TEXT")
},
{
name: $translate.instant("COMMON.WYSIWYG.ITALIC_SAMPLE_TEXT")
key: "I"
openWith: "_"
closeWith: "_"
placeHolder: $translate.instant("COMMON.WYSIWYG.ITALIC_SAMPLE_TEXT")
},
{
name: $translate.instant("COMMON.WYSIWYG.STRIKE_BUTTON")
key: "S"
openWith: "~~"
closeWith: "~~"
placeHolder: $translate.instant("COMMON.WYSIWYG.STRIKE_SAMPLE_TEXT")
},
{
separator: "---------------"
},
{
name: $translate.instant("COMMON.WYSIWYG.BULLETED_LIST_BUTTON")
openWith: "- "
placeHolder: $translate.instant("COMMON.WYSIWYG.BULLETED_LIST_SAMPLE_TEXT")
},
{
name: $translate.instant("COMMON.WYSIWYG.NUMERIC_LIST_BUTTON")
openWith: (markItUp) -> markItUp.line+". "
placeHolder: $translate.instant("COMMON.WYSIWYG.NUMERIC_LIST_SAMPLE_TEXT")
},
{
separator: "---------------"
},
{
name: $translate.instant("COMMON.WYSIWYG.PICTURE_BUTTON")
key: "P"
openWith: "!["
closeWith: '](<<<[![Url:!:http://]!]>>> "[![Title]!]")'
placeHolder: $translate.instant("COMMON.WYSIWYG.PICTURE_SAMPLE_TEXT")
beforeInsert:(markItUp) -> prepareUrlFormatting(markItUp)
afterInsert:(markItUp) -> urlFormatting(markItUp)
},
{
name: $translate.instant("COMMON.WYSIWYG.LINK_BUTTON")
key: "L"
openWith: "["
closeWith: '](<<<[![Url:!:http://]!]>>> "[![Title]!]")'
placeHolder: $translate.instant("COMMON.WYSIWYG.LINK_SAMPLE_TEXT")
beforeInsert:(markItUp) -> prepareUrlFormatting(markItUp)
afterInsert:(markItUp) -> urlFormatting(markItUp)
},
{
separator: "---------------"
},
{
name: $translate.instant("COMMON.WYSIWYG.QUOTE_BLOCK_BUTTON")
openWith: "> "
placeHolder: $translate.instant("COMMON.WYSIWYG.QUOTE_BLOCK_SAMPLE_TEXT")
},
{
name: $translate.instant("COMMON.WYSIWYG.CODE_BLOCK_BUTTON")
openWith: "```\n"
placeHolder: $translate.instant("COMMON.WYSIWYG.CODE_BLOCK_SAMPLE_TEXT")
closeWith: "\n```"
},
{
separator: "---------------"
},
{
name: $translate.instant("COMMON.WYSIWYG.PREVIEW_BUTTON")
call: preview
className: "preview-icon"
},
]
afterInsert: (event) ->
target = angular.element(event.textarea)
$model.$setViewValue(target.val())
element
.markItUpRemove()
.markItUp(markdownSettings)
renderMarkItUp()
unbind = $rootscope.$on "$translateChangeEnd", renderMarkItUp
element.on "keypress", (event) -> element.on "keypress", (event) ->
$scope.$apply() $scope.$apply()
$scope.$on "$destroy", -> $scope.$on "$destroy", ->
$el.off() $el.off()
unbind()
return {link:link, require:"ngModel"} return {link:link, require:"ngModel"}
module.directive("tgMarkitup", ["$rootScope", "$tgResources", "$tgI18n", "$selectedText", "$tgTemplate", tgMarkitupDirective]) module.directive("tgMarkitup", ["$rootScope", "$tgResources", "$selectedText", "$tgTemplate", "$compile",
"$translate", MarkitupDirective])

View File

@ -47,13 +47,14 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
"$appTitle", "$appTitle",
"$tgAnalytics", "$tgAnalytics",
"$tgNavUrls", "$tgNavUrls",
"$translate",
"tgLoader" "tgLoader"
] ]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
@log, @appTitle, @analytics, @navUrls, tgLoader) -> @log, @appTitle, @analytics, @navUrls, @translate, tgLoader) ->
@scope.issueRef = @params.issueref @scope.issueRef = @params.issueref
@scope.sectionName = "Issue Details" @scope.sectionName = @translate.instant("ISSUES.SECTION_NAME")
@.initializeEventHandlers() @.initializeEventHandlers()
promise = @.loadInitialData() promise = @.loadInitialData()
@ -145,7 +146,7 @@ module.controller("IssueDetailController", IssueDetailController)
## Issue status display directive ## Issue status display directive
############################################################################# #############################################################################
IssueStatusDisplayDirective = ($template)-> IssueStatusDisplayDirective = ($template, $compile)->
# Display if a Issue is open or closed and its issueboard status. # Display if a Issue is open or closed and its issueboard status.
# #
# Example: # Example:
@ -165,6 +166,9 @@ IssueStatusDisplayDirective = ($template)->
is_closed: status.is_closed is_closed: status.is_closed
status: status status: status
}) })
html = $compile(html)($scope)
$el.html(html) $el.html(html)
$scope.$watch $attrs.ngModel, (issue) -> $scope.$watch $attrs.ngModel, (issue) ->
@ -179,14 +183,14 @@ IssueStatusDisplayDirective = ($template)->
require: "ngModel" require: "ngModel"
} }
module.directive("tgIssueStatusDisplay", ["$tgTemplate", IssueStatusDisplayDirective]) module.directive("tgIssueStatusDisplay", ["$tgTemplate", "$compile", IssueStatusDisplayDirective])
############################################################################# #############################################################################
## Issue status button directive ## Issue status button directive
############################################################################# #############################################################################
IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $template) -> IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $template, $compile) ->
# Display the status of Issue and you can edit it. # Display the status of Issue and you can edit it.
# #
# Example: # Example:
@ -211,6 +215,9 @@ IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $t
statuses: $scope.statusList statuses: $scope.statusList
editable: isEditable() editable: isEditable()
}) })
html = $compile(html)($scope)
$el.html(html) $el.html(html)
save = $qqueue.bindAdd (statusId) => save = $qqueue.bindAdd (statusId) =>
@ -262,13 +269,13 @@ IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $t
require: "ngModel" require: "ngModel"
} }
module.directive("tgIssueStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", "$tgTemplate", IssueStatusButtonDirective]) module.directive("tgIssueStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", "$tgTemplate", "$compile", IssueStatusButtonDirective])
############################################################################# #############################################################################
## Issue type button directive ## Issue type button directive
############################################################################# #############################################################################
IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $template) -> IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $template, $compile) ->
# Display the type of Issue and you can edit it. # Display the type of Issue and you can edit it.
# #
# Example: # Example:
@ -293,6 +300,9 @@ IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $tem
typees: $scope.typeList typees: $scope.typeList
editable: isEditable() editable: isEditable()
}) })
html = $compile(html)($scope)
$el.html(html) $el.html(html)
save = $qqueue.bindAdd (type) => save = $qqueue.bindAdd (type) =>
@ -343,14 +353,14 @@ IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $tem
require: "ngModel" require: "ngModel"
} }
module.directive("tgIssueTypeButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", "$tgTemplate", IssueTypeButtonDirective]) module.directive("tgIssueTypeButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", "$tgTemplate", "$compile", IssueTypeButtonDirective])
############################################################################# #############################################################################
## Issue severity button directive ## Issue severity button directive
############################################################################# #############################################################################
IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $template) -> IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $template, $compile) ->
# Display the severity of Issue and you can edit it. # Display the severity of Issue and you can edit it.
# #
# Example: # Example:
@ -375,6 +385,9 @@ IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue,
severityes: $scope.severityList severityes: $scope.severityList
editable: isEditable() editable: isEditable()
}) })
html = $compile(html)($scope)
$el.html(html) $el.html(html)
save = $qqueue.bindAdd (severity) => save = $qqueue.bindAdd (severity) =>
@ -427,14 +440,14 @@ IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue,
require: "ngModel" require: "ngModel"
} }
module.directive("tgIssueSeverityButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", "$tgTemplate", IssueSeverityButtonDirective]) module.directive("tgIssueSeverityButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", "$tgTemplate", "$compile", IssueSeverityButtonDirective])
############################################################################# #############################################################################
## Issue priority button directive ## Issue priority button directive
############################################################################# #############################################################################
IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $template) -> IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $template, $compile) ->
# Display the priority of Issue and you can edit it. # Display the priority of Issue and you can edit it.
# #
# Example: # Example:
@ -459,6 +472,9 @@ IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue,
priorityes: $scope.priorityList priorityes: $scope.priorityList
editable: isEditable() editable: isEditable()
}) })
html = $compile(html)($scope)
$el.html(html) $el.html(html)
save = $qqueue.bindAdd (priority) => save = $qqueue.bindAdd (priority) =>
@ -511,14 +527,14 @@ IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue,
require: "ngModel" require: "ngModel"
} }
module.directive("tgIssuePriorityButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", "$tgTemplate", IssuePriorityButtonDirective]) module.directive("tgIssuePriorityButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", "$tgTemplate", "$compile", IssuePriorityButtonDirective])
############################################################################# #############################################################################
## Promote Issue to US button directive ## Promote Issue to US button directive
############################################################################# #############################################################################
PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm, $qqueue) -> PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm, $qqueue, $translate) ->
link = ($scope, $el, $attrs, $model) -> link = ($scope, $el, $attrs, $model) ->
save = $qqueue.bindAdd (issue, finish) => save = $qqueue.bindAdd (issue, finish) =>
@ -548,8 +564,8 @@ PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm, $qqueue) ->
event.preventDefault() event.preventDefault()
issue = $model.$modelValue issue = $model.$modelValue
title = "Promote this issue to a new user story" # TODO: i18n title = $translate.instant("ISSUES.CONFIRM_PROMOTE.TITLE")
message = "Are you sure you want to create a new US from this Issue?" # TODO: i18n message = $translate.instant("ISSUES.CONFIRM_PROMOTE.MESSAGE")
subtitle = issue.subject subtitle = issue.subject
$confirm.ask(title, subtitle, message).then (finish) => $confirm.ask(title, subtitle, message).then (finish) =>
@ -566,5 +582,5 @@ PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm, $qqueue) ->
link: link link: link
} }
module.directive("tgPromoteIssueToUsButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgQqueue", module.directive("tgPromoteIssueToUsButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgQqueue", "$translate"
PromoteIssueToUsButtonDirective]) PromoteIssueToUsButtonDirective])

View File

@ -51,12 +51,14 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
"$tgNavUrls", "$tgNavUrls",
"$tgEvents", "$tgEvents",
"$tgAnalytics", "$tgAnalytics",
"tgLoader" "tgLoader",
"$translate"
] ]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @urls, @params, @q, @location, @appTitle, constructor: (@scope, @rootscope, @repo, @confirm, @rs, @urls, @params, @q, @location, @appTitle,
@navUrls, @events, @analytics, tgLoader) -> @navUrls, @events, @analytics, tgLoader, @translate) ->
@scope.sectionName = "Issues"
@scope.sectionName = @translate.instant("ISSUES.LIST_SECTION_NAME")
@scope.filters = {} @scope.filters = {}
if _.isEmpty(@location.search()) if _.isEmpty(@location.search())
@ -310,7 +312,7 @@ module.controller("IssuesController", IssuesController)
## Issues Directive ## Issues Directive
############################################################################# #############################################################################
IssuesDirective = ($log, $location, $template) -> IssuesDirective = ($log, $location, $template, $compile) ->
## Issues Pagination ## Issues Pagination
template = $template.get("issue/issue-paginator.html", true) template = $template.get("issue/issue-paginator.html", true)
@ -360,7 +362,11 @@ IssuesDirective = ($log, $location, $template) ->
else else
pages.push({classes: "page", num: i, type: "page"}) pages.push({classes: "page", num: i, type: "page"})
$pagEl.html(template(options))
html = template(options)
html = $compile(html)($scope)
$pagEl.html(html)
$scope.$watch "issues", (value) -> $scope.$watch "issues", (value) ->
# Do nothing if value is not logical true # Do nothing if value is not logical true
@ -427,14 +433,14 @@ IssuesDirective = ($log, $location, $template) ->
return {link:link} return {link:link}
module.directive("tgIssues", ["$log", "$tgLocation", "$tgTemplate", IssuesDirective]) module.directive("tgIssues", ["$log", "$tgLocation", "$tgTemplate", "$compile", IssuesDirective])
############################################################################# #############################################################################
## Issues Filters Directive ## Issues Filters Directive
############################################################################# #############################################################################
IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template) -> IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template, $translate, $compile) ->
template = $template.get("issue/issues-filters.html", true) template = $template.get("issue/issues-filters.html", true)
templateSelected = $template.get("issue/issues-filters-selected.html", true) templateSelected = $template.get("issue/issues-filters-selected.html", true)
@ -468,6 +474,7 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template) -
f.style = "border-left: 3px solid #{f.color}" f.style = "border-left: 3px solid #{f.color}"
html = templateSelected({filters:selectedFilters}) html = templateSelected({filters:selectedFilters})
html = $compile(html)($scope)
$el.find(".filters-applied").html(html) $el.find(".filters-applied").html(html)
if selectedFilters.length > 0 if selectedFilters.length > 0
@ -481,6 +488,7 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template) -
f.style = "border-left: 3px solid #{f.color}" f.style = "border-left: 3px solid #{f.color}"
html = template({filters:filters}) html = template({filters:filters})
html = $compile(html)($scope)
$el.find(".filter-list").html(html) $el.find(".filter-list").html(html)
toggleFilterSelection = (type, id) -> toggleFilterSelection = (type, id) ->
@ -533,12 +541,13 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template) -
$scope.$on "filters:issueupdate", (ctx, filters) -> $scope.$on "filters:issueupdate", (ctx, filters) ->
html = template({filters:filters.statuses}) html = template({filters:filters.statuses})
html = $compile(html)($scope)
$el.find(".filter-list").html(html) $el.find(".filter-list").html(html)
selectQFilter = debounceLeading 100, (value) -> selectQFilter = debounceLeading 100, (value) ->
return if value is undefined return if value is undefined
$ctrl.replaceFilter("page", null) $ctrl.replaceFilter("page", null, true)
if value.length == 0 if value.length == 0
$ctrl.replaceFilter("q", null) $ctrl.replaceFilter("q", null)
@ -590,8 +599,8 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template) -
target = angular.element(event.currentTarget) target = angular.element(event.currentTarget)
customFilterName = target.parent().data('id') customFilterName = target.parent().data('id')
title = "Delete custom filter" # TODO: i18n title = $translate.instant("ISSUES.FILTERS.CONFIRM_DELETE.TITLE")
message = "the custom filter '#{customFilterName}'" # TODO: i18n message = $translate.instant("ISSUES.FILTERS.CONFIRM_DELETE.MESSAGE", {customFilterName: customFilterName})
$confirm.askOnDelete(title, message).then (finish) -> $confirm.askOnDelete(title, message).then (finish) ->
promise = $ctrl.deleteMyFilter(customFilterName) promise = $ctrl.deleteMyFilter(customFilterName)
@ -615,6 +624,7 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template) -
$el.find('.save-filters').hide() $el.find('.save-filters').hide()
$el.find('.my-filter-name').removeClass("hidden") $el.find('.my-filter-name').removeClass("hidden")
$el.find('.my-filter-name').focus() $el.find('.my-filter-name').focus()
$scope.$apply()
$el.on "keyup", ".my-filter-name", (event) -> $el.on "keyup", ".my-filter-name", (event) ->
event.preventDefault() event.preventDefault()
@ -652,8 +662,8 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template) -
return {link:link} return {link:link}
module.directive("tgIssuesFilters", ["$log", "$tgLocation", "$tgResources", "$tgConfirm", "$tgLoading", "$tgTemplate", module.directive("tgIssuesFilters", ["$log", "$tgLocation", "$tgResources", "$tgConfirm", "$tgLoading",
IssuesFiltersDirective]) "$tgTemplate", "$translate", "$compile", IssuesFiltersDirective])
############################################################################# #############################################################################
@ -708,7 +718,28 @@ IssueStatusInlineEditionDirective = ($repo, $template, $rootscope) ->
updateIssueStatus($el, issue, $scope.issueStatusById) updateIssueStatus($el, issue, $scope.issueStatusById)
$scope.$apply () -> $scope.$apply () ->
$repo.save(issue).then $repo.save(issue).then ->
for filter in $scope.filters.statuses
if filter.id == issue.status
filter.count++
$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 for filter in $scope.filters.statuses
if filter.id == issue.status if filter.id == issue.status
@ -732,7 +763,8 @@ IssueStatusInlineEditionDirective = ($repo, $template, $rootscope) ->
return {link: link} return {link: link}
module.directive("tgIssueStatusInlineEdition", ["$tgRepo", "$tgTemplate", "$rootScope", IssueStatusInlineEditionDirective]) module.directive("tgIssueStatusInlineEdition", ["$tgRepo", "$tgTemplate", "$rootScope",
IssueStatusInlineEditionDirective])
############################################################################# #############################################################################
@ -783,4 +815,5 @@ IssueAssignedToInlineEditionDirective = ($repo, $rootscope, popoverService) ->
return {link: link} return {link: link}
module.directive("tgIssueAssignedToInlineEdition", ["$tgRepo", "$rootScope", IssueAssignedToInlineEditionDirective]) module.directive("tgIssueAssignedToInlineEdition", ["$tgRepo", "$rootScope",
IssueAssignedToInlineEditionDirective])

View File

@ -62,15 +62,16 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
"$tgNavUrls", "$tgNavUrls",
"$tgEvents", "$tgEvents",
"$tgAnalytics", "$tgAnalytics",
"tgLoader" "tgLoader",
"$translate"
] ]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
@appTitle, @navUrls, @events, @analytics, tgLoader) -> @appTitle, @navUrls, @events, @analytics, tgLoader, @translate) ->
bindMethods(@) bindMethods(@)
@scope.sectionName = "Kanban" @scope.sectionName = @translate.instant("KANBAN.SECTION_NAME")
@scope.statusViewModes = {} @scope.statusViewModes = {}
@.initializeEventHandlers() @.initializeEventHandlers()
@ -307,10 +308,9 @@ module.directive("tgKanban", ["$tgRepo", "$rootScope", KanbanDirective])
## Kanban Archived Status Column Header Control ## Kanban Archived Status Column Header Control
############################################################################# #############################################################################
KanbanArchivedStatusHeaderDirective = ($rootscope) -> KanbanArchivedStatusHeaderDirective = ($rootscope, $translate) ->
#TODO: i18N showArchivedText = $translate.instant("KANBAN.ACTION_SHOW_ARCHIVED")
showArchivedText = "Show archived" hideArchivedText = $translate.instant("KANBAN.ACTION_HIDE_ARCHIVED")
hideArchivedText = "Hide archived"
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
status = $scope.$eval($attrs.tgKanbanArchivedStatusHeader) status = $scope.$eval($attrs.tgKanbanArchivedStatusHeader)
@ -338,19 +338,18 @@ KanbanArchivedStatusHeaderDirective = ($rootscope) ->
return {link:link} return {link:link}
module.directive("tgKanbanArchivedStatusHeader", [ "$rootScope", KanbanArchivedStatusHeaderDirective]) module.directive("tgKanbanArchivedStatusHeader", [ "$rootScope", "$translate", KanbanArchivedStatusHeaderDirective])
############################################################################# #############################################################################
## Kanban Archived Status Column Intro Directive ## Kanban Archived Status Column Intro Directive
############################################################################# #############################################################################
KanbanArchivedStatusIntroDirective = -> KanbanArchivedStatusIntroDirective = ($translate) ->
# TODO: i18n
hiddenUserStoriexText = "The user stories in this status are hidden by default"
userStories = [] userStories = []
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
hiddenUserStoriexText = $translate.instant("KANBAN.HIDDEN_USER_STORIES")
status = $scope.$eval($attrs.tgKanbanArchivedStatusIntro) status = $scope.$eval($attrs.tgKanbanArchivedStatusIntro)
$el.text(hiddenUserStoriexText) $el.text(hiddenUserStoriexText)
@ -397,7 +396,7 @@ KanbanArchivedStatusIntroDirective = ->
return {link:link} return {link:link}
module.directive("tgKanbanArchivedStatusIntro", KanbanArchivedStatusIntroDirective) module.directive("tgKanbanArchivedStatusIntro", ["$translate", KanbanArchivedStatusIntroDirective])
############################################################################# #############################################################################
@ -471,17 +470,20 @@ KanbanWipLimitDirective = ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
$el.disableSelection() $el.disableSelection()
redrawWipLimit = -> status = $scope.$eval($attrs.tgKanbanWipLimit)
redrawWipLimit = =>
$el.find(".kanban-wip-limit").remove() $el.find(".kanban-wip-limit").remove()
timeout 200, -> timeout 200, =>
element = $el.find(".kanban-task")[$scope.$eval($attrs.tgKanbanWipLimit)] element = $el.find(".kanban-task")[status.wip_limit]
if element if element
angular.element(element).before("<div class='kanban-wip-limit'></div>") angular.element(element).before("<div class='kanban-wip-limit'></div>")
$scope.$on "redraw:wip", redrawWipLimit if status and not status.is_archived
$scope.$on "kanban:us:move", redrawWipLimit $scope.$on "redraw:wip", redrawWipLimit
$scope.$on "usform:new:success", redrawWipLimit $scope.$on "kanban:us:move", redrawWipLimit
$scope.$on "usform:bulk:success", redrawWipLimit $scope.$on "usform:new:success", redrawWipLimit
$scope.$on "usform:bulk:success", redrawWipLimit
$scope.$on "$destroy", -> $scope.$on "$destroy", ->
$el.off() $el.off()
@ -495,14 +497,14 @@ module.directive("tgKanbanWipLimit", KanbanWipLimitDirective)
## Kanban User Directive ## Kanban User Directive
############################################################################# #############################################################################
KanbanUserDirective = ($log) -> KanbanUserDirective = ($log, $compile) ->
template = _.template(""" template = _.template("""
<figure class="avatar"> <figure class="avatar">
<a href="#" title="Assign User Story" <% if (!clickable) {%>class="not-clickable"<% } %>> <a href="#" title="{{'US.ASSIGN' | translate}}" <% if (!clickable) {%>class="not-clickable"<% } %>>
<img src="<%- imgurl %>" alt="<%- name %>" class="avatar"> <img src="<%- imgurl %>" alt="<%- name %>" class="avatar">
</a> </a>
</figure> </figure>
""") # TODO: i18n """)
clickable = false clickable = false
@ -527,7 +529,7 @@ KanbanUserDirective = ($log) ->
else else
ctx = {name: user.full_name_display, imgurl: user.photo, clickable: clickable} ctx = {name: user.full_name_display, imgurl: user.photo, clickable: clickable}
html = template(ctx) html = $compile(template(ctx))($scope)
$el.html(html) $el.html(html)
username_label.text(ctx.name) username_label.text(ctx.name)
@ -556,4 +558,4 @@ KanbanUserDirective = ($log) ->
return {link: link, require:"ngModel"} return {link: link, require:"ngModel"}
module.directive("tgKanbanUserAvatar", ["$log", KanbanUserDirective]) module.directive("tgKanbanUserAvatar", ["$log", "$compile", KanbanUserDirective])

View File

@ -249,15 +249,19 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $
container.replaceWith(dom) container.replaceWith(dom)
videoConferenceUrl = (project) -> videoConferenceUrl = (project) ->
urlSeparator = "-"
if project.videoconferences == "appear-in" if project.videoconferences == "appear-in"
baseUrl = "https://appear.in/" baseUrl = "https://appear.in/"
else if project.videoconferences == "talky" else if project.videoconferences == "talky"
baseUrl = "https://talky.io/" baseUrl = "https://talky.io/"
else if project.videoconferences == "jitsi"
urlSeparator = ""
baseUrl = "https://meet.jit.si/"
else else
return "" return ""
if project.videoconferences_salt if project.videoconferences_salt
url = "#{project.slug}-#{project.videoconferences_salt}" url = "#{project.slug}#{urlSeparator}#{project.videoconferences_salt}"
else else
url = "#{project.slug}" url = "#{project.slug}"

View File

@ -26,7 +26,7 @@ debounce = @.taiga.debounce
module = angular.module("taigaProject") module = angular.module("taigaProject")
CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $projectUrl, $loading, lightboxService, $cacheFactory) -> CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $projectUrl, $loading, lightboxService, $cacheFactory, $translate) ->
link = ($scope, $el, attrs) -> link = ($scope, $el, attrs) ->
$scope.data = {} $scope.data = {}
$scope.templates = [] $scope.templates = []
@ -41,7 +41,9 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
$loading.finish(submitButton) $loading.finish(submitButton)
$rootscope.$broadcast("projects:reload") $rootscope.$broadcast("projects:reload")
$confirm.notify("success", "Success") #TODO: i18n
$confirm.notify("success", $translate.instant("COMMON.SAVE"))
$location.url($projectUrl.get(response)) $location.url($projectUrl.get(response))
lightboxService.close($el) lightboxService.close($el)
@ -126,7 +128,7 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
return {link:link} return {link:link}
module.directive("tgLbCreateProject", ["$rootScope", "$tgRepo", "$tgConfirm", "$location", "$tgNavUrls", module.directive("tgLbCreateProject", ["$rootScope", "$tgRepo", "$tgConfirm", "$location", "$tgNavUrls",
"$tgResources", "$projectUrl", "$tgLoading", "lightboxService", "$cacheFactory", CreateProject]) "$tgResources", "$projectUrl", "$tgLoading", "lightboxService", "$cacheFactory", "$translate", CreateProject])
############################################################################# #############################################################################

View File

@ -25,7 +25,7 @@ debounce = @.taiga.debounce
module = angular.module("taigaRelatedTasks", []) module = angular.module("taigaRelatedTasks", [])
RelatedTaskRowDirective = ($repo, $compile, $confirm, $rootscope, $loading, $template) -> RelatedTaskRowDirective = ($repo, $compile, $confirm, $rootscope, $loading, $template, $translate) ->
templateView = $template.get("task/related-task-row.html", true) templateView = $template.get("task/related-task-row.html", true)
templateEdit = $template.get("task/related-task-row-edit.html", true) templateEdit = $template.get("task/related-task-row-edit.html", true)
@ -79,9 +79,8 @@ RelatedTaskRowDirective = ($repo, $compile, $confirm, $rootscope, $loading, $tem
$el.find('input').focus().select() $el.find('input').focus().select()
$el.on "click", ".delete-task", (event) -> $el.on "click", ".delete-task", (event) ->
#TODO: i18n title = $translate.instant("TASK.TITLE_DELETE_ACTION")
task = $model.$modelValue task = $model.$modelValue
title = "Delete Task"
message = task.subject message = task.subject
$confirm.askOnDelete(title, message).then (finish) -> $confirm.askOnDelete(title, message).then (finish) ->
@ -109,7 +108,7 @@ RelatedTaskRowDirective = ($repo, $compile, $confirm, $rootscope, $loading, $tem
return {link:link, require:"ngModel"} return {link:link, require:"ngModel"}
module.directive("tgRelatedTaskRow", ["$tgRepo", "$compile", "$tgConfirm", "$rootScope", "$tgLoading", "$tgTemplate", RelatedTaskRowDirective]) module.directive("tgRelatedTaskRow", ["$tgRepo", "$compile", "$tgConfirm", "$rootScope", "$tgLoading", "$tgTemplate", "$translate", RelatedTaskRowDirective])
RelatedTaskCreateFormDirective = ($repo, $compile, $confirm, $tgmodel, $loading, $analytics, $template) -> RelatedTaskCreateFormDirective = ($repo, $compile, $confirm, $tgmodel, $loading, $analytics, $template) ->
template = $template.get("task/related-task-create-form.html", true) template = $template.get("task/related-task-create-form.html", true)

View File

@ -24,59 +24,42 @@ taiga = @.taiga
class ResourcesService extends taiga.Service class ResourcesService extends taiga.Service
urls = { urls = {
# Auth
"auth": "/auth" "auth": "/auth"
"auth-register": "/auth/register" "auth-register": "/auth/register"
"invitations": "/invitations" "invitations": "/invitations"
"permissions": "/permissions"
"roles": "/roles" # User
"projects": "/projects"
"memberships": "/memberships"
"notify-policies": "/notify-policies"
"bulk-create-memberships": "/memberships/bulk_create"
"milestones": "/milestones"
"userstories": "/userstories"
"bulk-create-us": "/userstories/bulk_create"
"bulk-update-us-backlog-order": "/userstories/bulk_update_backlog_order"
"bulk-update-us-sprint-order": "/userstories/bulk_update_sprint_order"
"bulk-update-us-kanban-order": "/userstories/bulk_update_kanban_order"
"userstories-restore": "/userstories/%s/restore"
"tasks": "/tasks"
"bulk-create-tasks": "/tasks/bulk_create"
"bulk-update-task-taskboard-order": "/tasks/bulk_update_taskboard_order"
"tasks-restore": "/tasks/%s/restore"
"issues": "/issues"
"bulk-create-issues": "/issues/bulk_create"
"issues-restore": "/issues/%s/restore"
"wiki": "/wiki"
"wiki-restore": "/wiki/%s/restore"
"wiki-links": "/wiki-links"
"choices/userstory-statuses": "/userstory-statuses"
"choices/userstory-statuses/bulk-update-order": "/userstory-statuses/bulk_update_order"
"choices/points": "/points"
"choices/points/bulk-update-order": "/points/bulk_update_order"
"choices/task-statuses": "/task-statuses"
"choices/task-statuses/bulk-update-order": "/task-statuses/bulk_update_order"
"choices/issue-statuses": "/issue-statuses"
"choices/issue-statuses/bulk-update-order": "/issue-statuses/bulk_update_order"
"choices/issue-types": "/issue-types"
"choices/issue-types/bulk-update-order": "/issue-types/bulk_update_order"
"choices/priorities": "/priorities"
"choices/priorities/bulk-update-order": "/priorities/bulk_update_order"
"choices/severities": "/severities"
"choices/severities/bulk-update-order": "/severities/bulk_update_order"
"search": "/search"
"sites": "/sites"
"project-templates": "/project-templates"
"site-members": "/site-members"
"site-projects": "/site-projects"
"users": "/users" "users": "/users"
"users-password-recovery": "/users/password_recovery" "users-password-recovery": "/users/password_recovery"
"users-change-password-from-recovery": "/users/change_password_from_recovery" "users-change-password-from-recovery": "/users/change_password_from_recovery"
"users-change-password": "/users/change_password" "users-change-password": "/users/change_password"
"users-change-email": "/users/change_email" "users-change-email": "/users/change_email"
"users-cancel-account": "/users/cancel" "users-cancel-account": "/users/cancel"
# User - Notification
"notify-policies": "/notify-policies"
# User - Storage
"user-storage": "/user-storage" "user-storage": "/user-storage"
# Memberships
"memberships": "/memberships"
"bulk-create-memberships": "/memberships/bulk_create"
# Roles & Permissions
"roles": "/roles"
"permissions": "/permissions"
# Resolver
"resolver": "/resolver" "resolver": "/resolver"
# Project
"projects": "/projects"
"project-templates": "/project-templates"
"project-modules": "/projects/%s/modules"
# Project Values - Choises
"userstory-statuses": "/userstory-statuses" "userstory-statuses": "/userstory-statuses"
"points": "/points" "points": "/points"
"task-statuses": "/task-statuses" "task-statuses": "/task-statuses"
@ -84,11 +67,30 @@ urls = {
"issue-types": "/issue-types" "issue-types": "/issue-types"
"priorities": "/priorities" "priorities": "/priorities"
"severities": "/severities" "severities": "/severities"
"project-modules": "/projects/%s/modules"
"webhooks": "/webhooks" # Milestones/Sprints
"webhooks-test": "/webhooks/%s/test" "milestones": "/milestones"
"webhooklogs": "/webhooklogs"
"webhooklogs-resend": "/webhooklogs/%s/resend" # User stories
"userstories": "/userstories"
"bulk-create-us": "/userstories/bulk_create"
"bulk-update-us-backlog-order": "/userstories/bulk_update_backlog_order"
"bulk-update-us-sprint-order": "/userstories/bulk_update_sprint_order"
"bulk-update-us-kanban-order": "/userstories/bulk_update_kanban_order"
# Tasks
"tasks": "/tasks"
"bulk-create-tasks": "/tasks/bulk_create"
"bulk-update-task-taskboard-order": "/tasks/bulk_update_taskboard_order"
# Issues
"issues": "/issues"
"bulk-create-issues": "/issues/bulk_create"
# Wiki pages
"wiki": "/wiki"
"wiki-restore": "/wiki/%s/restore"
"wiki-links": "/wiki-links"
# History # History
"history/us": "/history/userstory" "history/us": "/history/userstory"
@ -107,22 +109,34 @@ urls = {
"custom-attributes/issue": "/issue-custom-attributes" "custom-attributes/issue": "/issue-custom-attributes"
"custom-attributes/task": "/task-custom-attributes" "custom-attributes/task": "/task-custom-attributes"
# Custom field values # Custom Attributess - Values
"custom-attributes-values/userstory": "/userstories/custom-attributes-values" "custom-attributes-values/userstory": "/userstories/custom-attributes-values"
"custom-attributes-values/issue": "/issues/custom-attributes-values" "custom-attributes-values/issue": "/issues/custom-attributes-values"
"custom-attributes-values/task": "/tasks/custom-attributes-values" "custom-attributes-values/task": "/tasks/custom-attributes-values"
# Feedback # Webhooks
"feedback": "/feedback" "webhooks": "/webhooks"
"webhooks-test": "/webhooks/%s/test"
"webhooklogs": "/webhooklogs"
"webhooklogs-resend": "/webhooklogs/%s/resend"
# Reports - CSV
"userstories-csv": "/userstories/csv?uuid=%s"
"tasks-csv": "/tasks/csv?uuid=%s"
"issues-csv": "/issues/csv?uuid=%s"
# Search
"search": "/search"
# Export/Import # Export/Import
"exporter": "/exporter" "exporter": "/exporter"
"importer": "/importer/load_dump" "importer": "/importer/load_dump"
# CSV # Feedback
"userstories-csv": "/userstories/csv?uuid=%s" "feedback": "/feedback"
"tasks-csv": "/tasks/csv?uuid=%s"
"issues-csv": "/issues/csv?uuid=%s" # locales
"locales": "/locales"
} }
# Initialize api urls service # Initialize api urls service
@ -168,5 +182,6 @@ module.run([
"$tgModulesResourcesProvider", "$tgModulesResourcesProvider",
"$tgWebhooksResourcesProvider", "$tgWebhooksResourcesProvider",
"$tgWebhookLogsResourcesProvider", "$tgWebhookLogsResourcesProvider",
"$tgLocalesResourcesProvider",
initResources initResources
]) ])

View File

@ -0,0 +1,38 @@
###
# Copyright (C) 2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2015 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/locales.coffee
###
taiga = @.taiga
sizeFormat = @.taiga.sizeFormat
resourceProvider = ($repo) ->
service = {
list: -> return $repo.queryMany("locales")
}
return (instance) ->
instance.locales = service
module = angular.module("taigaResources")
module.factory("$tgLocalesResourcesProvider", ["$tgRepo", resourceProvider])

View File

@ -24,7 +24,7 @@ taiga = @.taiga
sizeFormat = @.taiga.sizeFormat sizeFormat = @.taiga.sizeFormat
resourceProvider = ($config, $repo, $http, $urls, $auth, $q, $rootScope) -> resourceProvider = ($config, $repo, $http, $urls, $auth, $q, $translate) ->
service = {} service = {}
service.get = (projectId) -> service.get = (projectId) ->
@ -85,21 +85,31 @@ resourceProvider = ($config, $repo, $http, $urls, $auth, $q, $rootScope) ->
maxFileSize = $config.get("maxUploadFileSize", null) maxFileSize = $config.get("maxUploadFileSize", null)
if maxFileSize and file.size > maxFileSize if maxFileSize and file.size > maxFileSize
errorMsg = $translate.instant("PROJECT.IMPORT.ERROR_MAX_SIZE_EXCEEDED", {
fileName: file.name
fileSize: sizeFormat(file.size)
maxFileSize: sizeFormat(maxFileSize)
})
response = { response = {
status: 413, status: 413,
data: _error_message: "'#{file.name}' (#{sizeFormat(file.size)}) is too heavy for our oompa data: _error_message: errorMsg
loompas, try it with a smaller than (#{sizeFormat(maxFileSize)})"
} }
defered.reject(response) defered.reject(response)
return defered.promise return defered.promise
uploadProgress = (evt) => uploadProgress = (evt) =>
percent = Math.round((evt.loaded / evt.total) * 100) percent = Math.round((evt.loaded / evt.total) * 100)
message = "Uloaded #{sizeFormat(evt.loaded)} of #{sizeFormat(evt.total)}" message = $translate.instant("PROJECT.IMPORT.UPLOAD_IN_PROGRESS_MESSAGE", {
uploadedSize: sizeFormat(evt.loaded)
totalSize: sizeFormat(evt.total)
})
statusUpdater("in-progress", null, message, percent) statusUpdater("in-progress", null, message, percent)
uploadComplete = (evt) => uploadComplete = (evt) =>
statusUpdater("done", "Importing Project", "This process can take a while, please keep the window open.") # i18n statusUpdater("done",
$translate.instant("PROJECT.IMPORT.TITLE"),
$translate.instant("PROJECT.IMPORT.DESCRIPTION"))
uploadFailed = (evt) => uploadFailed = (evt) =>
statusUpdater("error") statusUpdater("error")
@ -141,5 +151,5 @@ resourceProvider = ($config, $repo, $http, $urls, $auth, $q, $rootScope) ->
module = angular.module("taigaResources") module = angular.module("taigaResources")
module.factory("$tgProjectsResourcesProvider", ["$tgConfig", "$tgRepo", "$tgHttp", "$tgUrls", "$tgAuth", "$q", module.factory("$tgProjectsResourcesProvider", ["$tgConfig", "$tgRepo", "$tgHttp", "$tgUrls", "$tgAuth",
resourceProvider]) "$q", "$translate", resourceProvider])

View File

@ -34,7 +34,7 @@ module = angular.module("taigaTaskboard")
## Sprint burndown graph directive ## Sprint burndown graph directive
############################################################################# #############################################################################
SprintGraphDirective = -> SprintGraphDirective = ($translate)->
redrawChart = (element, dataToDraw) -> redrawChart = (element, dataToDraw) ->
width = element.width() width = element.width()
element.height(240) element.height(240)
@ -64,13 +64,18 @@ SprintGraphDirective = ->
max: _.last(days) max: _.last(days)
mode: "time" mode: "time"
daysNames: days daysNames: days
axisLabel: 'Day' axisLabel: $translate.instant("TASKBOARD.CHARTS.XAXIS_LABEL")
axisLabelUseCanvas: true axisLabelUseCanvas: true
axisLabelFontSizePixels: 12 axisLabelFontSizePixels: 12
axisLabelFontFamily: 'Verdana, Arial, Helvetica, Tahoma, sans-serif' axisLabelFontFamily: 'Verdana, Arial, Helvetica, Tahoma, sans-serif'
axisLabelPadding: 5 axisLabelPadding: 5
yaxis: yaxis:
min: 0 min: 0
axisLabel: $translate.instant("TASKBOARD.CHARTS.YAXIS_LABEL")
axisLabelUseCanvas: true
axisLabelFontSizePixels: 12
axisLabelFontFamily: 'Verdana, Arial, Helvetica, Tahoma, sans-serif'
axisLabelPadding: 5
series: series:
shadowSize: 0 shadowSize: 0
lines: lines:
@ -85,14 +90,20 @@ SprintGraphDirective = ->
tooltip: true tooltip: true
tooltipOpts: tooltipOpts:
content: (label, xval, yval, flotItem) -> content: (label, xval, yval, flotItem) ->
#TODO: i18n formattedDate = moment(xval).format($translate.instant("TASKBOARD.CHARTS.DATE"))
formattedDate = moment(xval).format("DD MMM")
roundedValue = Math.round(yval) roundedValue = Math.round(yval)
if flotItem.seriesIndex == 1 if flotItem.seriesIndex == 1
return "Optimal pending points for day #{formattedDate} should be #{roundedValue}" return $translate.instant("TASKBOARD.CHARTS.OPTIMAL", {
formattedDate: formattedDate,
roundedValue: roundedValue
})
else else
return "Real pending points for day #{formattedDate} is #{roundedValue}" return $translate.instant("TASKBOARD.CHARTS.REAL", {
formattedDate: formattedDate,
roundedValue: roundedValue
})
element.empty() element.empty()
element.plot(data, options).data("plot") element.plot(data, options).data("plot")
@ -121,5 +132,4 @@ SprintGraphDirective = ->
return {link: link} return {link: link}
module.directive("tgSprintGraph", ["$translate", SprintGraphDirective])
module.directive("tgSprintGraph", SprintGraphDirective)

View File

@ -23,7 +23,7 @@ taiga = @.taiga
bindOnce = @.taiga.bindOnce bindOnce = @.taiga.bindOnce
debounce = @.taiga.debounce debounce = @.taiga.debounce
CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxService) -> CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxService, $translate) ->
link = ($scope, $el, attrs) -> link = ($scope, $el, attrs) ->
$scope.isNew = true $scope.isNew = true
@ -40,10 +40,13 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxSer
$scope.isNew = true $scope.isNew = true
# Update texts for creation # Update texts for creation
$el.find(".button-green").html("Create") #TODO: i18n create = $translate.instant("COMMON.CREATE")
$el.find(".title").html("New task ") #TODO: i18n $el.find(".button-green").html(create)
$el.find(".tag-input").val("")
newTask = $translate.instant("LIGHTBOX.CREATE_EDIT_TASK.TITLE")
$el.find(".title").html(newTask + " ")
$el.find(".tag-input").val("")
lightboxService.open($el) lightboxService.open($el)
$scope.$on "taskform:edit", (ctx, task) -> $scope.$on "taskform:edit", (ctx, task) ->
@ -51,10 +54,13 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxSer
$scope.isNew = false $scope.isNew = false
# Update texts for edition # Update texts for edition
$el.find(".button-green").html("Save") #TODO: i18n save = $translate.instant("COMMON.SAVE")
$el.find(".title").html("Edit task ") #TODO: i18n edit = $translate.instant("LIGHTBOX.CREATE_EDIT_TASK.ACTION_EDIT")
$el.find(".tag-input").val("")
$el.find(".button-green").html(save)
$el.find(".title").html(edit + " ")
$el.find(".tag-input").val("")
lightboxService.open($el) lightboxService.open($el)
@ -142,6 +148,7 @@ module.directive("tgLbCreateEditTask", [
"$rootScope", "$rootScope",
"$tgLoading", "$tgLoading",
"lightboxService", "lightboxService",
"$translate"
CreateEditTaskDirective CreateEditTaskDirective
]) ])

View File

@ -50,13 +50,14 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
"$tgEvents" "$tgEvents"
"$tgAnalytics", "$tgAnalytics",
"tgLoader" "tgLoader"
"$translate"
] ]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @appTitle, @location, @navUrls, constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @appTitle, @location, @navUrls,
@events, @analytics, tgLoader) -> @events, @analytics, tgLoader, @translate) ->
bindMethods(@) bindMethods(@)
@scope.sectionName = "Taskboard" @scope.sectionName = @translate.instant("TASKBOARD.SECTION_NAME")
@.initializeEventHandlers() @.initializeEventHandlers()
promise = @.loadInitialData() promise = @.loadInitialData()
@ -259,7 +260,6 @@ TaskboardDirective = ($rootscope) ->
event.preventDefault() event.preventDefault()
target = angular.element(event.currentTarget) target = angular.element(event.currentTarget)
target.toggleClass('active'); target.toggleClass('active');
#toggleText(target, ["Hide statistics", "Show statistics"]) # TODO: i18n
$rootscope.$broadcast("taskboard:graph:toggle-visibility") $rootscope.$broadcast("taskboard:graph:toggle-visibility")
tableBodyDom = $el.find(".taskboard-table-body") tableBodyDom = $el.find(".taskboard-table-body")

View File

@ -26,6 +26,7 @@ groupBy = @.taiga.groupBy
module = angular.module("taigaTasks") module = angular.module("taigaTasks")
############################################################################# #############################################################################
## Task Detail Controller ## Task Detail Controller
############################################################################# #############################################################################
@ -44,13 +45,14 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
"$appTitle", "$appTitle",
"$tgNavUrls", "$tgNavUrls",
"$tgAnalytics", "$tgAnalytics",
"$translate",
"tgLoader" "tgLoader"
] ]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
@log, @appTitle, @navUrls, @analytics, tgLoader) -> @log, @appTitle, @navUrls, @analytics, @translate, tgLoader) ->
@scope.taskRef = @params.taskref @scope.taskRef = @params.taskref
@scope.sectionName = "Task Details" @scope.sectionName = @translate.instant("TASK.SECTION_NAME")
@.initializeEventHandlers() @.initializeEventHandlers()
promise = @.loadInitialData() promise = @.loadInitialData()
@ -145,7 +147,7 @@ module.controller("TaskDetailController", TaskDetailController)
## Task status display directive ## Task status display directive
############################################################################# #############################################################################
TaskStatusDisplayDirective = ($template) -> TaskStatusDisplayDirective = ($template, $compile) ->
# Display if a Task is open or closed and its taskboard status. # Display if a Task is open or closed and its taskboard status.
# #
# Example: # Example:
@ -165,6 +167,9 @@ TaskStatusDisplayDirective = ($template) ->
is_closed: status.is_closed is_closed: status.is_closed
status: status status: status
}) })
html = $compile(html)($scope)
$el.html(html) $el.html(html)
$scope.$watch $attrs.ngModel, (task) -> $scope.$watch $attrs.ngModel, (task) ->
@ -179,14 +184,14 @@ TaskStatusDisplayDirective = ($template) ->
require: "ngModel" require: "ngModel"
} }
module.directive("tgTaskStatusDisplay", ["$tgTemplate", TaskStatusDisplayDirective]) module.directive("tgTaskStatusDisplay", ["$tgTemplate", "$compile", TaskStatusDisplayDirective])
############################################################################# #############################################################################
## Task status button directive ## Task status button directive
############################################################################# #############################################################################
TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue) -> TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $compile, $translate) ->
# Display the status of Task and you can edit it. # Display the status of Task and you can edit it.
# #
# Example: # Example:
@ -202,7 +207,7 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue) ->
<span class="level" style="background-color:<%- status.color %>"></span> <span class="level" style="background-color:<%- status.color %>"></span>
<span class="status-status"><%- status.name %></span> <span class="status-status"><%- status.name %></span>
<% if(editable){ %><span class="icon icon-arrow-bottom"></span><% }%> <% if(editable){ %><span class="icon icon-arrow-bottom"></span><% }%>
<span class="level-name">status</span> <span class="level-name" translate="COMMON.FIELDS.STATUS"></span>
<ul class="popover pop-status"> <ul class="popover pop-status">
<% _.each(statuses, function(st) { %> <% _.each(statuses, function(st) { %>
@ -211,7 +216,7 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue) ->
<% }); %> <% }); %>
</ul> </ul>
</div> </div>
""") #TODO: i18n """)
link = ($scope, $el, $attrs, $model) -> link = ($scope, $el, $attrs, $model) ->
isEditable = -> isEditable = ->
@ -220,11 +225,12 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue) ->
render = (task) => render = (task) =>
status = $scope.statusById[task.status] status = $scope.statusById[task.status]
html = template({ html = $compile(template({
status: status status: status
statuses: $scope.statusList statuses: $scope.statusList
editable: isEditable() editable: isEditable()
}) }))($scope)
$el.html(html) $el.html(html)
save = $qqueue.bindAdd (status) => save = $qqueue.bindAdd (status) =>
@ -278,14 +284,15 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue) ->
} }
module.directive("tgTaskStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", module.directive("tgTaskStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue",
TaskStatusButtonDirective]) "$compile", "$translate", TaskStatusButtonDirective])
TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue) -> TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue, $compile) ->
template = _.template(""" template = _.template("""
<fieldset title="Feeling a bit overwhelmed by a task? Make sure others know about it by clicking on Iocaine when editing a task. It's possible to become immune to this (fictional) deadly poison by consuming small amounts over time just as it's possible to get better at what you do by occasionally taking on extra challenges!"> <fieldset title="{{ 'TASK.TITLE_ACTION_IOCAINE' | translate }}">
<label for="is-iocaine" <label for="is-iocaine"
class="button button-gray is-iocaine <% if(isEditable){ %>editable<% }; %> <% if(isIocaine){ %>active<% }; %>"> translate="TASK.ACTION_IOCAINE"
class="button button-gray is-iocaine <% if(isEditable){ %>editable<% }; %> <% if(isIocaine){ %>active<% }; %>">
Iocaine Iocaine
</label> </label>
<input type="checkbox" id="is-iocaine" name="is-iocaine"/> <input type="checkbox" id="is-iocaine" name="is-iocaine"/>
@ -305,7 +312,7 @@ TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue
isIocaine: task.is_iocaine isIocaine: task.is_iocaine
isEditable: isEditable() isEditable: isEditable()
} }
html = template(ctx) html = $compile(template(ctx))($scope)
$el.html(html) $el.html(html)
save = $qqueue.bindAdd (is_iocaine) => save = $qqueue.bindAdd (is_iocaine) =>
@ -347,4 +354,5 @@ TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue
require: "ngModel" require: "ngModel"
} }
module.directive("tgTaskIsIocaineButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", TaskIsIocaineButtonDirective]) module.directive("tgTaskIsIocaineButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue",
"$compile", TaskIsIocaineButtonDirective])

View File

@ -41,18 +41,20 @@ class TeamController extends mixOf(taiga.Controller, taiga.PageMixin)
"$tgNavUrls", "$tgNavUrls",
"$appTitle", "$appTitle",
"$tgAuth", "$tgAuth",
"tgLoader" "tgLoader",
"$translate"
] ]
constructor: (@scope, @rootscope, @repo, @rs, @params, @q, @location, @navUrls, @appTitle, @auth, tgLoader) -> constructor: (@scope, @rootscope, @repo, @rs, @params, @q, @location, @navUrls, @appTitle, @auth, tgLoader,
@scope.sectionName = "Team" @translate) ->
@scope.sectionName = "TEAM.SECTION_NAME"
promise = @.loadInitialData() promise = @.loadInitialData()
# On Success # On Success
promise.then => promise.then =>
#TODO: i18n text = @translate.instant("TEAM.APP_TITLE", {"projectName": @scope.project.name})
@appTitle.set("Team - " + @scope.project.name) @appTitle.set(text)
# On Error # On Error
promise.then null, @.onInitialDataError.bind(@) promise.then null, @.onInitialDataError.bind(@)
@ -137,6 +139,7 @@ class TeamController extends mixOf(taiga.Controller, taiga.PageMixin)
module.controller("TeamController", TeamController) module.controller("TeamController", TeamController)
############################################################################# #############################################################################
## Team Filters Directive ## Team Filters Directive
############################################################################# #############################################################################
@ -148,6 +151,7 @@ TeamFiltersDirective = () ->
module.directive("tgTeamFilters", [TeamFiltersDirective]) module.directive("tgTeamFilters", [TeamFiltersDirective])
############################################################################# #############################################################################
## Team Member Stats Directive ## Team Member Stats Directive
############################################################################# #############################################################################
@ -166,6 +170,7 @@ TeamMemberStatsDirective = () ->
module.directive("tgTeamMemberStats", TeamMemberStatsDirective) module.directive("tgTeamMemberStats", TeamMemberStatsDirective)
############################################################################# #############################################################################
## Team Current User Directive ## Team Current User Directive
############################################################################# #############################################################################
@ -185,6 +190,7 @@ TeamMemberCurrentUserDirective = () ->
module.directive("tgTeamCurrentUser", TeamMemberCurrentUserDirective) module.directive("tgTeamCurrentUser", TeamMemberCurrentUserDirective)
############################################################################# #############################################################################
## Team Members Directive ## Team Members Directive
############################################################################# #############################################################################
@ -207,15 +213,18 @@ TeamMembersDirective = () ->
module.directive("tgTeamMembers", TeamMembersDirective) module.directive("tgTeamMembers", TeamMembersDirective)
############################################################################# #############################################################################
## Leave project Directive ## Leave project Directive
############################################################################# #############################################################################
LeaveProjectDirective = ($repo, $confirm, $location, $rs, $navurls) -> LeaveProjectDirective = ($repo, $confirm, $location, $rs, $navurls, $translate) ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
$scope.leave = () -> $scope.leave = () ->
#TODO: i18n leave_project_text = $translate.instant("TEAM.ACTION_LEAVE_PROJECT")
$confirm.ask("Leave this project", "Are you sure you want to leave the project?").then (finish) => confirm_leave_project_text = $translate.instant("TEAM.CONFIRM_LEAVE_PROJECT")
$confirm.ask(leave_project_text, confirm_leave_project_text).then (finish) =>
promise = $rs.projects.leave($attrs.projectid) promise = $rs.projects.leave($attrs.projectid)
promise.then => promise.then =>
@ -233,10 +242,17 @@ LeaveProjectDirective = ($repo, $confirm, $location, $rs, $navurls) ->
link: link link: link
} }
module.directive("tgLeaveProject", ["$tgRepo", "$tgConfirm", "$tgLocation", "$tgResources", "$tgNavUrls", LeaveProjectDirective]) module.directive("tgLeaveProject", ["$tgRepo", "$tgConfirm", "$tgLocation", "$tgResources", "$tgNavUrls", "$translate",
LeaveProjectDirective])
module.filter 'membersRoleFilter', () ->
(input, filtersRole) -> #############################################################################
if filtersRole? ## Team Filters
return _.filter(input, {role: filtersRole.id}) #############################################################################
return input
membersFilter = ->
return (members, filtersQ, filtersRole) ->
return _.filter members, (m) -> (not filtersRole or m.role == filtersRole.id) and
(not filtersQ or m.full_name.search(new RegExp(filtersQ, "i")) >= 0)
module.filter('membersFilter', membersFilter)

View File

@ -42,11 +42,12 @@ class UserChangePasswordController extends mixOf(taiga.Controller, taiga.PageMix
"$q", "$q",
"$tgLocation", "$tgLocation",
"$tgNavUrls", "$tgNavUrls",
"$tgAuth" "$tgAuth",
"$translate"
] ]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @auth) -> constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @auth, @translate) ->
@scope.sectionName = "Change Password" #i18n @scope.sectionName = @translate.instant("CHANGE_PASSWORD.SECTION_NAME")
@scope.project = {} @scope.project = {}
@scope.user = @auth.getUser() @scope.user = @auth.getUser()
@ -74,13 +75,13 @@ module.controller("UserChangePasswordController", UserChangePasswordController)
## User ChangePassword Directive ## User ChangePassword Directive
############################################################################# #############################################################################
UserChangePasswordDirective = ($rs, $confirm, $loading) -> UserChangePasswordDirective = ($rs, $confirm, $loading, $translate) ->
link = ($scope, $el, $attrs, ctrl) -> link = ($scope, $el, $attrs, ctrl) ->
submit = debounce 2000, (event) => submit = debounce 2000, (event) =>
event.preventDefault() event.preventDefault()
if $scope.newPassword1 != $scope.newPassword2 if $scope.newPassword1 != $scope.newPassword2
$confirm.notify('error', "The passwords dosn't match") $confirm.notify('error', $translate.instant("CHANGE_PASSWORD.ERROR_PASSWORD_MATCH"))
return return
$loading.start(submitButton) $loading.start(submitButton)

View File

@ -41,17 +41,21 @@ class UserSettingsController extends mixOf(taiga.Controller, taiga.PageMixin)
"$q", "$q",
"$tgLocation", "$tgLocation",
"$tgNavUrls", "$tgNavUrls",
"$tgAuth" "$tgAuth",
"$translate"
] ]
constructor: (@scope, @rootscope, @config, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @auth) -> constructor: (@scope, @rootscope, @config, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @auth, @translate) ->
@scope.sectionName = "User Profile" #i18n @scope.sectionName = "USER_SETTINGS.MENU.SECTION_TITLE"
@scope.project = {} @scope.project = {}
@scope.user = @auth.getUser() @scope.user = @auth.getUser()
@scope.lang = @getLan()
maxFileSize = @config.get("maxUploadFileSize", null) maxFileSize = @config.get("maxUploadFileSize", null)
if maxFileSize if maxFileSize
@scope.maxFileSizeMsg = "[Max, size: #{sizeFormat(maxFileSize)}" # TODO: i18n @translate("USER_SETTINGS.AVATAR_MAX_SIZE", {"maxFileSize": sizeFormat(maxFileSize)}).then (text) =>
@scope.maxFileSizeMsg = text
promise = @.loadInitialData() promise = @.loadInitialData()
@ -63,16 +67,26 @@ class UserSettingsController extends mixOf(taiga.Controller, taiga.PageMixin)
@scope.$emit('project:loaded', project) @scope.$emit('project:loaded', project)
return project return project
loadLocales: ->
return @rs.locales.list().then (locales) =>
@scope.locales = locales
return locales
loadInitialData: -> loadInitialData: ->
promise = @repo.resolve({pslug: @params.pslug}).then (data) => promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project @scope.projectId = data.project
return data return data
return promise.then(=> @.loadProject()) return @q.all([promise.then(=> @.loadProject()),
@.loadLocales()])
openDeleteLightbox: -> openDeleteLightbox: ->
@rootscope.$broadcast("deletelightbox:new", @scope.user) @rootscope.$broadcast("deletelightbox:new", @scope.user)
getLan: ->
return @scope.user.lang ||
@translate.preferredLanguage()
module.controller("UserSettingsController", UserSettingsController) module.controller("UserSettingsController", UserSettingsController)
@ -80,7 +94,7 @@ module.controller("UserSettingsController", UserSettingsController)
## User Profile Directive ## User Profile Directive
############################################################################# #############################################################################
UserProfileDirective = ($confirm, $auth, $repo) -> UserProfileDirective = ($confirm, $auth, $repo, $translate) ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
submit = debounce 2000, (event) => submit = debounce 2000, (event) =>
event.preventDefault() event.preventDefault()
@ -89,14 +103,14 @@ UserProfileDirective = ($confirm, $auth, $repo) ->
return if not form.validate() return if not form.validate()
changeEmail = $scope.user.isAttributeModified("email") changeEmail = $scope.user.isAttributeModified("email")
$scope.user.lang = $scope.lang
onSuccess = (data) => onSuccess = (data) =>
$auth.setUser(data) $auth.setUser(data)
if changeEmail if changeEmail
$confirm.success("<strong>Check your inbox!</strong><br /> text = $translate.instant("USER_PROFILE.CHANGE_EMAIL_SUCCESS")
We have sent a mail to your account<br /> $confirm.success(text)
with the instructions to set your new address") #TODO: i18n
else else
$confirm.notify('success') $confirm.notify('success')
@ -113,7 +127,7 @@ UserProfileDirective = ($confirm, $auth, $repo) ->
return {link:link} return {link:link}
module.directive("tgUserProfile", ["$tgConfirm", "$tgAuth", "$tgRepo", UserProfileDirective]) module.directive("tgUserProfile", ["$tgConfirm", "$tgAuth", "$tgRepo", "$translate", UserProfileDirective])
############################################################################# #############################################################################

View File

@ -45,7 +45,7 @@ class UserNotificationsController extends mixOf(taiga.Controller, taiga.PageMixi
] ]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @auth) -> constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @auth) ->
@scope.sectionName = "Email Notifications" #i18n @scope.sectionName = "USER_SETTINGS.NOTIFICATIONS.SECTION_NAME"
@scope.project = {} @scope.project = {}
@scope.user = @auth.getUser() @scope.user = @auth.getUser()
@ -94,7 +94,7 @@ module.directive("tgUserNotifications", UserNotificationsDirective)
## User Notifications List Directive ## User Notifications List Directive
############################################################################# #############################################################################
UserNotificationsListDirective = ($repo, $confirm) -> UserNotificationsListDirective = ($repo, $confirm, $compile) ->
template = _.template(""" template = _.template("""
<% _.each(notifyPolicies, function (notifyPolicy, index) { %> <% _.each(notifyPolicies, function (notifyPolicy, index) { %>
<div class="policy-table-row" data-index="<%- index %>"> <div class="policy-table-row" data-index="<%- index %>">
@ -104,7 +104,8 @@ UserNotificationsListDirective = ($repo, $confirm) ->
<input type="radio" <input type="radio"
name="policy-<%- notifyPolicy.id %>" id="policy-all-<%- notifyPolicy.id %>" name="policy-<%- notifyPolicy.id %>" id="policy-all-<%- notifyPolicy.id %>"
value="2" <% if (notifyPolicy.notify_level == 2) { %>checked="checked"<% } %>/> value="2" <% if (notifyPolicy.notify_level == 2) { %>checked="checked"<% } %>/>
<label for="policy-all-<%- notifyPolicy.id %>">All</label> <label for="policy-all-<%- notifyPolicy.id %>"
translate="USER_SETTINGS.NOTIFICATIONS.OPTION_ALL"></label>
</fieldset> </fieldset>
</div> </div>
<div class="policy-table-involved"> <div class="policy-table-involved">
@ -112,7 +113,8 @@ UserNotificationsListDirective = ($repo, $confirm) ->
<input type="radio" <input type="radio"
name="policy-<%- notifyPolicy.id %>" id="policy-involved-<%- notifyPolicy.id %>" name="policy-<%- notifyPolicy.id %>" id="policy-involved-<%- notifyPolicy.id %>"
value="1" <% if (notifyPolicy.notify_level == 1) { %>checked="checked"<% } %> /> value="1" <% if (notifyPolicy.notify_level == 1) { %>checked="checked"<% } %> />
<label for="policy-involved-<%- notifyPolicy.id %>">Involved</label> <label for="policy-involved-<%- notifyPolicy.id %>"
translate="USER_SETTINGS.NOTIFICATIONS.OPTION_INVOLVED"></label>
</fieldset> </fieldset>
</div> </div>
<div class="policy-table-none"> <div class="policy-table-none">
@ -120,7 +122,8 @@ UserNotificationsListDirective = ($repo, $confirm) ->
<input type="radio" <input type="radio"
name="policy-<%- notifyPolicy.id %>" id="policy-none-<%- notifyPolicy.id %>" name="policy-<%- notifyPolicy.id %>" id="policy-none-<%- notifyPolicy.id %>"
value="3" <% if (notifyPolicy.notify_level == 3) { %>checked="checked"<% } %> /> value="3" <% if (notifyPolicy.notify_level == 3) { %>checked="checked"<% } %> />
<label for="policy-none-<%- notifyPolicy.id %>">None</label> <label for="policy-none-<%- notifyPolicy.id %>"
translate="USER_SETTINGS.NOTIFICATIONS.OPTION_NONE"></label>
</fieldset> </fieldset>
</div> </div>
</div> </div>
@ -130,13 +133,17 @@ UserNotificationsListDirective = ($repo, $confirm) ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
render = -> render = ->
$el.off() $el.off()
$el.html(template({notifyPolicies: $scope.notifyPolicies}))
ctx = {notifyPolicies: $scope.notifyPolicies}
html = template(ctx)
$el.html($compile(html)($scope))
$el.on "change", "input[type=radio]", (event) -> $el.on "change", "input[type=radio]", (event) ->
target = angular.element(event.currentTarget) target = angular.element(event.currentTarget)
policyIndex = target.parents(".policy-table-row").data('index') policyIndex = target.parents(".policy-table-row").data('index')
policy = $scope.notifyPolicies[policyIndex] policy = $scope.notifyPolicies[policyIndex]
prev_level = policy.notify_level prev_level = policy.notify_level
policy.notify_level = parseInt(target.val(), 10) policy.notify_level = parseInt(target.val(), 10)
@ -145,7 +152,9 @@ UserNotificationsListDirective = ($repo, $confirm) ->
onError = -> onError = ->
$confirm.notify("error") $confirm.notify("error")
target.parents(".policy-table-row").find("input[value=#{prev_level}]").prop("checked", true) target.parents(".policy-table-row")
.find("input[value=#{prev_level}]")
.prop("checked", true)
$repo.save(policy).then(onSuccess, onError) $repo.save(policy).then(onSuccess, onError)
@ -156,4 +165,5 @@ UserNotificationsListDirective = ($repo, $confirm) ->
return {link:link} return {link:link}
module.directive("tgUserNotificationsList", ["$tgRepo", "$tgConfirm", UserNotificationsListDirective]) module.directive("tgUserNotificationsList", ["$tgRepo", "$tgConfirm", "$compile",
UserNotificationsListDirective])

View File

@ -45,13 +45,14 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
"$appTitle", "$appTitle",
"$tgNavUrls", "$tgNavUrls",
"$tgAnalytics", "$tgAnalytics",
"$translate",
"tgLoader" "tgLoader"
] ]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
@log, @appTitle, @navUrls, @analytics, tgLoader) -> @log, @appTitle, @navUrls, @analytics, @translate, tgLoader) ->
@scope.usRef = @params.usref @scope.usRef = @params.usref
@scope.sectionName = "User Story Details" @scope.sectionName = @translate.instant("US.SECTION_NAME")
@.initializeEventHandlers() @.initializeEventHandlers()
promise = @.loadInitialData() promise = @.loadInitialData()
@ -178,7 +179,7 @@ module.controller("UserStoryDetailController", UserStoryDetailController)
## User story status display directive ## User story status display directive
############################################################################# #############################################################################
UsStatusDisplayDirective = ($template) -> UsStatusDisplayDirective = ($template, $compile) ->
# Display if a US is open or closed and its kanban status. # Display if a US is open or closed and its kanban status.
# #
# Example: # Example:
@ -192,10 +193,14 @@ UsStatusDisplayDirective = ($template) ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
render = (us) -> render = (us) ->
status = $scope.statusById[us.status]
html = template({ html = template({
is_closed: us.is_closed is_closed: us.is_closed
status: $scope.statusById[us.status] status: status
}) })
html = $compile(html)($scope)
$el.html(html) $el.html(html)
$scope.$watch $attrs.ngModel, (us) -> $scope.$watch $attrs.ngModel, (us) ->
@ -210,14 +215,14 @@ UsStatusDisplayDirective = ($template) ->
require: "ngModel" require: "ngModel"
} }
module.directive("tgUsStatusDisplay", ["$tgTemplate", UsStatusDisplayDirective]) module.directive("tgUsStatusDisplay", ["$tgTemplate", "$compile", UsStatusDisplayDirective])
############################################################################# #############################################################################
## User story related tasts progress splay Directive ## User story related tasts progress splay Directive
############################################################################# #############################################################################
UsTasksProgressDisplayDirective = ($template) -> UsTasksProgressDisplayDirective = ($template, $compile) ->
# Display a progress bar with the stats of completed tasks. # Display a progress bar with the stats of completed tasks.
# #
# Example: # Example:
@ -227,8 +232,6 @@ UsTasksProgressDisplayDirective = ($template) ->
# - Task object list (ng-model) # - Task object list (ng-model)
# - scope.taskStatusById object # - scope.taskStatusById object
template = $template.get("us/us-task-progress.html", true)
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
render = (tasks) -> render = (tasks) ->
totalTasks = tasks.length totalTasks = tasks.length
@ -236,12 +239,14 @@ UsTasksProgressDisplayDirective = ($template) ->
progress = if totalTasks > 0 then 100 * totalClosedTasks / totalTasks else 0 progress = if totalTasks > 0 then 100 * totalClosedTasks / totalTasks else 0
html = template({ _.assign($scope, {
totalTasks: totalTasks totalTasks: totalTasks
totalClosedTasks: totalClosedTasks totalClosedTasks: totalClosedTasks
progress: progress progress: progress,
style: {
width: progress + "%"
}
}) })
$el.html(html)
$scope.$watch $attrs.ngModel, (tasks) -> $scope.$watch $attrs.ngModel, (tasks) ->
render(tasks) if tasks? render(tasks) if tasks?
@ -250,12 +255,14 @@ UsTasksProgressDisplayDirective = ($template) ->
$el.off() $el.off()
return { return {
templateUrl: "us/us-task-progress.html"
link: link link: link
restrict: "EA" restrict: "EA"
require: "ngModel" require: "ngModel"
scope: true
} }
module.directive("tgUsTasksProgressDisplay", ["$tgTemplate", UsTasksProgressDisplayDirective]) module.directive("tgUsTasksProgressDisplay", ["$tgTemplate", "$compile", UsTasksProgressDisplayDirective])
############################################################################# #############################################################################
@ -350,7 +357,7 @@ module.directive("tgUsStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$t
## User story team requirements button directive ## User story team requirements button directive
############################################################################# #############################################################################
UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue, $template) -> UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue, $template, $compile) ->
template = $template.get("us/us-team-requirement-button.html", true) template = $template.get("us/us-team-requirement-button.html", true)
link = ($scope, $el, $attrs, $model) -> link = ($scope, $el, $attrs, $model) ->
@ -367,6 +374,8 @@ UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qq
isRequired: us.team_requirement isRequired: us.team_requirement
} }
html = template(ctx) html = template(ctx)
html = $compile(html)($scope)
$el.html(html) $el.html(html)
save = $qqueue.bindAdd (team_requirement) => save = $qqueue.bindAdd (team_requirement) =>
@ -407,13 +416,13 @@ UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qq
require: "ngModel" require: "ngModel"
} }
module.directive("tgUsTeamRequirementButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", "$tgTemplate", UsTeamRequirementButtonDirective]) module.directive("tgUsTeamRequirementButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", "$tgTemplate", "$compile", UsTeamRequirementButtonDirective])
############################################################################# #############################################################################
## User story client requirements button directive ## User story client requirements button directive
############################################################################# #############################################################################
UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue, $template) -> UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue, $template, $compile) ->
template = $template.get("us/us-client-requirement-button.html", true) template = $template.get("us/us-client-requirement-button.html", true)
link = ($scope, $el, $attrs, $model) -> link = ($scope, $el, $attrs, $model) ->
@ -429,7 +438,7 @@ UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $
canEdit: canEdit() canEdit: canEdit()
isRequired: us.client_requirement isRequired: us.client_requirement
} }
html = template(ctx) html = $compile(template(ctx))($scope)
$el.html(html) $el.html(html)
save = $qqueue.bindAdd (client_requirement) => save = $qqueue.bindAdd (client_requirement) =>
@ -467,5 +476,5 @@ UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $
require: "ngModel" require: "ngModel"
} }
module.directive("tgUsClientRequirementButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", "$tgTemplate", module.directive("tgUsClientRequirementButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", "$tgTemplate", "$compile",
UsClientRequirementButtonDirective]) UsClientRequirementButtonDirective])

View File

@ -49,11 +49,12 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
"$appTitle", "$appTitle",
"$tgNavUrls", "$tgNavUrls",
"$tgAnalytics", "$tgAnalytics",
"tgLoader" "tgLoader",
"$translate"
] ]
constructor: (@scope, @rootscope, @repo, @model, @confirm, @rs, @params, @q, @location, constructor: (@scope, @rootscope, @repo, @model, @confirm, @rs, @params, @q, @location,
@filter, @log, @appTitle, @navUrls, @analytics, tgLoader) -> @filter, @log, @appTitle, @navUrls, @analytics, tgLoader, @translate) ->
@scope.projectSlug = @params.pslug @scope.projectSlug = @params.pslug
@scope.wikiSlug = @params.slug @scope.wikiSlug = @params.slug
@scope.sectionName = "Wiki" @scope.sectionName = "Wiki"
@ -111,8 +112,7 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
@q.all([@.loadWikiLinks(), @.loadWiki()]) @q.all([@.loadWikiLinks(), @.loadWiki()])
delete: -> delete: ->
# TODO: i18n title = @translate.instant("WIKI.DELETE_LIGHTBOX_TITLE")
title = "Delete Wiki Page"
message = unslugify(@scope.wiki.slug) message = unslugify(@scope.wiki.slug)
@confirm.askOnDelete(title, message).then (finish) => @confirm.askOnDelete(title, message).then (finish) =>
@ -135,7 +135,7 @@ module.controller("WikiDetailController", WikiDetailController)
## Wiki Summary Directive ## Wiki Summary Directive
############################################################################# #############################################################################
WikiSummaryDirective = ($log, $template) -> WikiSummaryDirective = ($log, $template, $compile, $translate) ->
template = $template.get("wiki/wiki-summary.html", true) template = $template.get("wiki/wiki-summary.html", true)
link = ($scope, $el, $attrs, $model) -> link = ($scope, $el, $attrs, $model) ->
@ -152,10 +152,11 @@ WikiSummaryDirective = ($log, $template) ->
ctx = { ctx = {
totalEditions: wiki.editions totalEditions: wiki.editions
lastModifiedDate: moment(wiki.modified_date).format("DD MMM YYYY HH:mm") lastModifiedDate: moment(wiki.modified_date).format($translate.instant("WIKI.DATETIME"))
user: user user: user
} }
html = template(ctx) html = template(ctx)
html = $compile(html)($scope)
$el.html(html) $el.html(html)
$scope.$watch $attrs.ngModel, (wikiPage) -> $scope.$watch $attrs.ngModel, (wikiPage) ->
@ -171,7 +172,7 @@ WikiSummaryDirective = ($log, $template) ->
require: "ngModel" require: "ngModel"
} }
module.directive("tgWikiSummary", ["$log", "$tgTemplate", WikiSummaryDirective]) module.directive("tgWikiSummary", ["$log", "$tgTemplate", "$compile", "$translate", WikiSummaryDirective])
############################################################################# #############################################################################
@ -215,7 +216,7 @@ EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $
if not wiki.id? if not wiki.id?
$analytics.trackEvent("wikipage", "create", "create wiki page", 1) $analytics.trackEvent("wikipage", "create", "create wiki page", 1)
$model.$setViewValue wikiPage $model.$setViewValue wikiPage.clone()
$confirm.notify("success") $confirm.notify("success")
switchToReadMode() switchToReadMode()

View File

@ -34,7 +34,7 @@ module = angular.module("taigaWiki")
## Wiki Main Directive ## Wiki Main Directive
############################################################################# #############################################################################
WikiNavDirective = ($tgrepo, $log, $location, $confirm, $navUrls, $analytics, $loading, $template) -> WikiNavDirective = ($tgrepo, $log, $location, $confirm, $navUrls, $analytics, $loading, $template, $compile, $translate) ->
template = $template.get("wiki/wiki-nav.html", true) template = $template.get("wiki/wiki-nav.html", true)
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
$ctrl = $el.controller() $ctrl = $el.controller()
@ -53,6 +53,8 @@ WikiNavDirective = ($tgrepo, $log, $location, $confirm, $navUrls, $analytics, $l
deleteWikiLinkPermission: deleteWikiLinkPermission deleteWikiLinkPermission: deleteWikiLinkPermission
}) })
html = $compile(html)($scope)
$el.off() $el.off()
$el.html(html) $el.html(html)
@ -80,8 +82,7 @@ WikiNavDirective = ($tgrepo, $log, $location, $confirm, $navUrls, $analytics, $l
target = angular.element(event.currentTarget) target = angular.element(event.currentTarget)
linkId = target.parents('.wiki-link').data('id') linkId = target.parents('.wiki-link').data('id')
# TODO: i18n title = $translate.instant("WIKI.DELETE_LIGHTBOX_TITLE")
title = "Delete Wiki Link"
message = $scope.wikiLinks[linkId].title message = $scope.wikiLinks[linkId].title
$confirm.askOnDelete(title, message).then (finish) => $confirm.askOnDelete(title, message).then (finish) =>
@ -143,4 +144,4 @@ WikiNavDirective = ($tgrepo, $log, $location, $confirm, $navUrls, $analytics, $l
return {link:link} return {link:link}
module.directive("tgWikiNav", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm", "$tgNavUrls", module.directive("tgWikiNav", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm", "$tgNavUrls",
"$tgAnalytics", "$tgLoading", "$tgTemplate", WikiNavDirective]) "$tgAnalytics", "$tgLoading", "$tgTemplate", "$compile", "$translate", WikiNavDirective])

11
app/js/jquery.ui.touch-punch.min.js vendored Normal file
View File

@ -0,0 +1,11 @@
/*!
* jQuery UI Touch Punch 0.2.3
*
* Copyright 20112014, Dave Furfero
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Depends:
* jquery.ui.widget.js
* jquery.ui.mouse.js
*/
!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery);

View File

@ -1,63 +0,0 @@
{
"checksley": {
"defaultMessage": "This value seems to be invalid.",
"type-email": "This value should be a valid email.",
"type-url": "This value should be a valid url.",
"type-urlstrict": "This value should be a valid url.",
"type-number": "This value should be a valid number.",
"type-digits": "This value should be digits.",
"type-dateIso": "This value should be a valid date (YYYY-MM-DD).",
"type-alphanum": "This value should be alphanumeric.",
"type-phone": "This value should be a valid phone number.",
"notnull": "This value should not be null.",
"notblank": "This value should not be blank.",
"required": "This value is required.",
"regexp": "This value seems to be invalid.",
"min": "This value should be greater than or equal to %s.",
"max": "This value should be lower than or equal to %s.",
"range": "This value should be between %s and %s.",
"minlength": "This value is too short. It should have %s characters or more.",
"maxlength": "This value is too long. It should have %s characters or less.",
"rangelength": "This value length is invalid. It should be between %s and %s characters long.",
"mincheck": "You must select at least %s choices.",
"maxcheck": "You must select %s choices or less.",
"rangecheck": "You must select between %s and %s choices.",
"equalto": "This value should be the same."
},
"common": {
"subject": "Subject",
"save": "Save",
"blocked": "Blocked",
"cancel": "Cancel",
"status": "Status",
"new-bulk": "New bulk insert",
"one-item-line": "One item per line..."
},
"pagination": {
"next": "Next",
"prev": "Previous"
},
"markdown-editor": {
"heading-1": "First Level Heading",
"heading-2": "Second Level Heading",
"heading-3": "Third Level Heading",
"bold": "Bold",
"italic": "Italic",
"strike": "Strike",
"bulleted-list": "Bulleted List",
"numeric-list": "Numeric List",
"picture": "Picture",
"link": "Link",
"quotes": "Quotes",
"code-block": "Code Block / Code",
"preview": "Preview",
"help": "Help",
"placeholder": "Your title here...",
"link-placeholder": "Your text to link here..."
},
"us": {
"title-new": "New User Story",
"team-requirement": "Team Requirement",
"client-requirement": "Client Requirement"
}
}

1091
app/locales/locale-ca.json Normal file

File diff suppressed because it is too large Load Diff

1093
app/locales/locale-en.json Normal file

File diff suppressed because it is too large Load Diff

1091
app/locales/locale-es.json Normal file

File diff suppressed because it is too large Load Diff

1091
app/locales/locale-fi.json Normal file

File diff suppressed because it is too large Load Diff

1091
app/locales/locale-fr.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,5 @@
### ###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be> # Copyright (C) 2015 Taiga Agile LLC
# 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 # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as # it under the terms of the GNU Affero General Public License as
@ -16,7 +14,7 @@
# You should have received a copy of the GNU Affero General Public License # 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/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
# File: modules/locales.coffee # File: components.module.coffee
### ###
module = angular.module("taigaLocales", []) angular.module("taigaComponents", [])

View File

@ -2,7 +2,7 @@ ul.paginator
<% if (showPrevious) { %> <% if (showPrevious) { %>
li.previous li.previous
a(href="", class="previous next_prev_button", class="disabled") a(href="", class="previous next_prev_button", class="disabled")
span(i18next="pagination.prev") Prev span(translate="PAGINATION.PREVIOUS")
<% } %> <% } %>
<% _.each(pages, function(item) { %> <% _.each(pages, function(item) { %>
@ -19,5 +19,5 @@ ul.paginator
<% if (showNext) { %> <% if (showNext) { %>
li.next li.next
a(href="", class="next next_prev_button", class="disabled") a(href="", class="next next_prev_button", class="disabled")
span(i18next="pagination.next") Next span(translate="PAGINATION.NEXT")
<% } %> <% } %>

View File

@ -1,5 +1,5 @@
.check .check
input(type="checkbox", id!="<%- inputId %>") input(type="checkbox", id!="<%- inputId %>")
div div
span.check-text.check-yes Yes span.check-text.check-yes(translate="COMMON.YES")
span.check-text.check-no No span.check-text.check-no(translate="COMMON.NO")

View File

@ -1,5 +1,7 @@
doctype html
div.wrapper.memberships(ng-controller="MembershipsController as ctrl", div.wrapper.memberships(ng-controller="MembershipsController as ctrl",
ng-init="section='admin'", tg-memberships) ng-init="section='admin'; sectionName='ADMIN.MEMBERSHIPS.TITLE'", tg-memberships)
sidebar.menu-secondary.sidebar(tg-admin-navigation="memberships") sidebar.menu-secondary.sidebar(tg-admin-navigation="memberships")
include ../includes/modules/admin-menu include ../includes/modules/admin-menu
@ -9,8 +11,9 @@ div.wrapper.memberships(ng-controller="MembershipsController as ctrl",
include ../includes/components/mainTitle include ../includes/components/mainTitle
.action-buttons .action-buttons
a.button-green(title="Add new member" href="" ng-click="ctrl.addNewMembers()") a.button-green(href="", title="{{ ADMIN.MEMBERSHIPS.ADD_BUTTON_TITLE | translate }}",
span.text + New member ng-click="ctrl.addNewMembers()")
span.text(translate="ADMIN.MEMBERSHIPS.ADD_BUTTON")
include ../includes/modules/admin/admin-membership-table include ../includes/modules/admin/admin-membership-table

View File

@ -1,5 +1,7 @@
doctype html
div.wrapper(tg-project-default-values, ng-controller="ProjectProfileController as ctrl", div.wrapper(tg-project-default-values, ng-controller="ProjectProfileController as ctrl",
ng-init="section='admin'; sectionName='Default values'") ng-init="section='admin'; sectionName='ADMIN.PROJECT_DEFAULT_VALUES.TITLE'")
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile") sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile")
include ../includes/modules/admin-menu include ../includes/modules/admin-menu
@ -9,7 +11,6 @@ div.wrapper(tg-project-default-values, ng-controller="ProjectProfileController a
section.main.admin-common section.main.admin-common
header header
include ../includes/components/mainTitle include ../includes/components/mainTitle
p.admin-subtitle(translate="ADMIN.PROJECT_DEFAULT_VALUES.SUBTITLE")
p.total Default Values
include ../includes/modules/admin/default-values include ../includes/modules/admin/default-values

View File

@ -1,5 +1,8 @@
doctype html
div.wrapper(ng-controller="ProjectProfileController as ctrl", div.wrapper(ng-controller="ProjectProfileController as ctrl",
ng-init="section='admin'; sectionName='Export'") ng-init="section='admin'; sectionName='ADMIN.PROJECT_EXPORT.TITLE'")
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile") sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile")
include ../includes/modules/admin-menu include ../includes/modules/admin-menu
@ -9,18 +12,18 @@ div.wrapper(ng-controller="ProjectProfileController as ctrl",
section.main.admin-common(tg-project-export) section.main.admin-common(tg-project-export)
header header
include ../includes/components/mainTitle include ../includes/components/mainTitle
p.admin-subtitle Export your project to save a backup or to create a new one based on this. p.admin-subtitle(translate="ADMIN.PROJECT_EXPORT.SUBTITLE")
div.admin-project-export-buttons div.admin-project-export-buttons
a.button-green.button-export(href="", title="Export your project") a.button-green.button-export(href="", title="{{ 'ADMIN.PROJECT_EXPORT.EXPORT_BUTTON_TITLE' | translate }}")
span Export span(translate="ADMIN.PROJECT_EXPORT.EXPORT_BUTTON")
div.admin-project-export-result.hidden div.admin-project-export-result.hidden
div.spin.hidden div.spin.hidden
img(src="/svg/spinner-circle.svg", alt="loading...") img(src="/svg/spinner-circle.svg", alt="{{ 'COMMON.LOADING' | translate }}")
h3.result-title h3.result-title
p.result-message p.result-message
a.help-button(href="https://taiga.io/support/import-export-projects/", target="_blank") a.help-button(href="https://taiga.io/support/import-export-projects/", target="_blank")
span.icon.icon-help span.icon.icon-help
span Do you need help? Check out our support page! span(translate="ADMIN.HELP")

View File

@ -1,5 +1,7 @@
doctype html
div.wrapper(tg-project-modules, ng-controller="ProjectProfileController as ctrl", div.wrapper(tg-project-modules, ng-controller="ProjectProfileController as ctrl",
ng-init="section='admin'; sectionName='Modules'") ng-init="section='admin'; sectionName='ADMIN.MODULES.TITLE'")
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile") sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile")
include ../includes/modules/admin-menu include ../includes/modules/admin-menu
@ -15,76 +17,76 @@ div.wrapper(tg-project-modules, ng-controller="ProjectProfileController as ctrl"
div.icon.icon-backlog div.icon.icon-backlog
div.desc div.desc
p p
span Backlog span.title(translate="ADMIN.MODULES.BACKLOG")
| Manage your user stories to maintain an organized view of upcoming and prioritized work. span(translate="ADMIN.MODULES.BACKLOG_DESCRIPTION")
div.activate div.activate
input.activate-input(type="checkbox", id="functionality-backlog", input.activate-input(type="checkbox", id="functionality-backlog",
ng-model="project.is_backlog_activated") ng-model="project.is_backlog_activated")
label.button.button-gray(ng-switch="project.is_backlog_activated", label.button.button-gray(ng-switch="project.is_backlog_activated",
for="functionality-backlog") for="functionality-backlog")
span(ng-switch-when="true") Disable span(ng-switch-when="true", translate="ADMIN.MODULES.DISABLE")
span(ng-switch-when="false") Enable span(ng-switch-when="false", translate="ADMIN.MODULES.ENABLE")
div.functionality(ng-class="{true:'active', false:''}[project.is_kanban_activated]") div.functionality(ng-class="{true:'active', false:''}[project.is_kanban_activated]")
div.icon.icon-kanban div.icon.icon-kanban
div.desc div.desc
p p
span Kanban span.title(translate="ADMIN.MODULES.KANBAN")
| Organize your project in a lean way with this board. span(translate="ADMIN.MODULES.KANBAN_DESCRIPTION")
div.activate div.activate
input.activate-input(type="checkbox", id="functionality-kanban", input.activate-input(type="checkbox", id="functionality-kanban",
ng-model="project.is_kanban_activated") ng-model="project.is_kanban_activated")
label.button.button-gray(ng-switch="project.is_kanban_activated", label.button.button-gray(ng-switch="project.is_kanban_activated",
for="functionality-kanban") for="functionality-kanban")
span(ng-switch-when="true") Disable span(ng-switch-when="true", translate="ADMIN.MODULES.DISABLE")
span(ng-switch-when="false") Enable span(ng-switch-when="false", translate="ADMIN.MODULES.ENABLE")
div.functionality(ng-class="{true:'active', false:''}[project.is_issues_activated]") div.functionality(ng-class="{true:'active', false:''}[project.is_issues_activated]")
div.icon.icon-issues div.icon.icon-issues
div.desc div.desc
p p
span Issues span.title(translate="ADMIN.MODULES.ISSUES")
| Track the bugs, questions and enhancements related to your project. Don't miss anything! span(translate="ADMIN.MODULES.ISSUES_DESCRIPTION")
div.activate div.activate
input.activate-input(type="checkbox", id="functionality-issues", input.activate-input(type="checkbox", id="functionality-issues",
ng-model="project.is_issues_activated") ng-model="project.is_issues_activated")
label.button.button-gray(ng-switch="project.is_issues_activated", label.button.button-gray(ng-switch="project.is_issues_activated",
for="functionality-issues") for="functionality-issues")
span(ng-switch-when="true") Disable span(ng-switch-when="true", translate="ADMIN.MODULES.DISABLE")
span(ng-switch-when="false") Enable span(ng-switch-when="false", translate="ADMIN.MODULES.ENABLE")
div.functionality(ng-class="{true:'active', false:''}[project.is_wiki_activated]") div.functionality(ng-class="{true:'active', false:''}[project.is_wiki_activated]")
div.icon.icon-wiki div.icon.icon-wiki
div.desc div.desc
p p
span Wiki span.title(translate="ADMIN.MODULES.WIKI")
| Add, modify, or delete content in collaboration with others. This is the right place for your project documentation. span(translate="ADMIN.MODULES.WIKI_DESCRIPTION")
div.activate div.activate
input.activate-input(type="checkbox", id="functionality-wiki", input.activate-input(type="checkbox", id="functionality-wiki",
ng-model="project.is_wiki_activated") ng-model="project.is_wiki_activated")
label.button.button-gray(ng-switch="project.is_wiki_activated", label.button.button-gray(ng-switch="project.is_wiki_activated",
for="functionality-wiki") for="functionality-wiki")
span(ng-switch-when="true") Disable span(ng-switch-when="true", translate="ADMIN.MODULES.DISABLE")
span(ng-switch-when="false") Enable span(ng-switch-when="false", translate="ADMIN.MODULES.ENABLE")
div.functionality(ng-class="{true:'active', false:''}[isVideoconferenceActivated]") div.functionality(ng-class="{true:'active', false:''}[isVideoconferenceActivated]")
div.icon.icon-video div.icon.icon-video
div.desc div.desc
p p
span Meet Up span.title(translate="ADMIN.MODULES.MEETUP")
| Choose your videoconference system. Even developers need face to face contact. span(translate="ADMIN.MODULES.MEETUP_DESCRIPTION")
div.activate div.activate
input.activate-input(type="checkbox", id="functionality-video", input.activate-input(type="checkbox", id="functionality-video",
ng-model="isVideoconferenceActivated") ng-model="isVideoconferenceActivated")
label.button.button-gray(ng-switch="isVideoconferenceActivated", label.button.button-gray(ng-switch="isVideoconferenceActivated",
for="functionality-video") for="functionality-video")
span(ng-switch-when="true") Disable span(ng-switch-when="true", translate="ADMIN.MODULES.DISABLE")
span(ng-switch-when="false") Enable span(ng-switch-when="false", translate="ADMIN.MODULES.ENABLE")
div.videoconference-attributes.hidden div.videoconference-attributes.hidden
select(ng-model="project.videoconferences", select(ng-model="project.videoconferences",
ng-options="e.id as e.name for e in [{'id':'appear-in', 'name':'AppearIn'},{'id':'talky', 'name': 'Talky'}]") ng-options="e.id as e.name for e in [{'id':'appear-in', 'name':'AppearIn'},{'id':'jitsi', 'name': 'Jitsi'},{'id':'talky', 'name': 'Talky'}]")
option(value="") Select a videoconference system option(value="", translate="ADMIN.MODULES.SELECT_VIDEOCONFERENCE")
input(type="text", ng-model="project.videoconferences_salt", input(type="text", ng-model="project.videoconferences_salt",
placeholder="If you want you can append a salt code to the name of the chat room") placeholder="{{'ADMIN.MODULES.SALT_CHAT_ROOM' | translate}}")
button.button-green.submit-button(type="submit", title="Save") Save button.button-green.submit-button(type="submit", title="{{'COMMON.SAVE' | translate}}", translate="COMMON.SAVE")

View File

@ -1,5 +1,7 @@
doctype html
div.wrapper(tg-project-profile, ng-controller="ProjectProfileController as ctrl", div.wrapper(tg-project-profile, ng-controller="ProjectProfileController as ctrl",
ng-init="section='admin'; sectionName='Project details'") ng-init="section='admin'; sectionName='ADMIN.PROJECT_PROFILE.PROJECT_DETAILS'")
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile") sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile")
include ../includes/modules/admin-menu include ../includes/modules/admin-menu
@ -12,29 +14,33 @@ div.wrapper(tg-project-profile, ng-controller="ProjectProfileController as ctrl"
form form
fieldset fieldset
label(for="project-name") Project Name label(for="project-name", translate="ADMIN.PROJECT_PROFILE.PROJECT_NAME")
input(type="text", name="name", placeholder="Project name", id="project-name", input(type="text", name="name", placeholder="{{'ADMIN.PROJECT_PROFILE.PROJECT_NAME' | translate}}", id="project-name",
ng-model="project.name", data-required="true", maxlength="45") ng-model="project.name", data-required="true", maxlength="45")
fieldset fieldset
label(for="project-slug") Project Slug label(for="project-slug", translate="ADMIN.PROJECT_PROFILE.PROJECT_SLUG")
input(type="text", name="slug", placeholder="Slug", id="project-slug", input(type="text", name="slug", placeholder="{{'ADMIN.PROJECT_PROFILE.PROJECT_SLUG' | translate}}", id="project-slug",
ng-model="project.slug", data-required="true") ng-model="project.slug", data-required="true")
fieldset fieldset
label(for="project-sprints") Number of sprints label(for="project-sprints", translate="ADMIN.PROJECT_PROFILE.NUMBER_SPRINTS")
input(type="number", name="total_milestones", min="0", placeholder="Number of sprints", input(type="number", name="total_milestones", min="0", placeholder="{{'ADMIN.PROJECT_PROFILE.NUMBER_SPRINTS' | translate}}",
id="project-sprints", ng-model="project.total_milestones", data-type="digits") id="project-sprints", ng-model="project.total_milestones", data-type="digits")
fieldset fieldset
label(for="total-story-points") Number of US points label(for="total-story-points", translate="ADMIN.PROJECT_PROFILE.NUMBER_US_POINTS")
input(type="number", name="total_story_points", min="0", placeholder="Number of US points", input(type="number", name="total_story_points", min="0", placeholder="{{'ADMIN.PROJECT_PROFILE.NUMBER_US_POINTS' | translate}}",
id="total-story-points", ng-model="project.total_story_points", id="total-story-points", ng-model="project.total_story_points",
data-type="digits", data-required="true") data-type="digits", data-required="true")
fieldset fieldset
label(for="project-description") Description label(for="tags", translate="ADMIN.PROJECT_PROFILE.TAGS")
textarea(name="description", placeholder="Description", id="project-description", div.tags-block(tg-lb-tag-line, ng-model="project.tags")
fieldset
label(for="project-description", translate="ADMIN.PROJECT_PROFILE.DESCRIPTION")
textarea(name="description", ng-attr-placeholder="{{'ADMIN.PROJECT_PROFILE.DESCRIPTION' | translate}}", id="project-description",
ng-model="project.description", data-required="true") ng-model="project.description", data-required="true")
div div
@ -42,14 +48,14 @@ div.wrapper(tg-project-profile, ng-controller="ProjectProfileController as ctrl"
div div
input.privacy-project(type="radio", name="private-project", ng-model="project.is_private", ng-value="false") input.privacy-project(type="radio", name="private-project", ng-model="project.is_private", ng-value="false")
label.trans-button(for="public-project") label.trans-button(for="public-project")
span Public Project span(translate="ADMIN.PROJECT_PROFILE.PUBLIC_PROJECT")
div div
input.privacy-project(type="radio", name="private-project", ng-model="project.is_private", ng-value="true") input.privacy-project(type="radio", name="private-project", ng-model="project.is_private", ng-value="true")
label.trans-button(for="private-project") label.trans-button(for="private-project")
span Private Project span(translate="ADMIN.PROJECT_PROFILE.PRIVATE_PROJECT")
button.button-green.submit-button(type="submit", title="Save") Save button.button-green.submit-button(type="submit", title="{{'COMMON.SAVE' | translate}}", translate="COMMON.SAVE")
a.delete-project(href="", title="Delete this project", ng-click="ctrl.openDeleteLightbox()") Delete this project a.delete-project(href="", title="{{'ADMIN.PROJECT_PROFILE.DELETE' | translate}}", ng-click="ctrl.openDeleteLightbox()", translate="ADMIN.PROJECT_PROFILE.DELETE")
div.lightbox.lightbox-delete-project(tg-lb-delete-project) div.lightbox.lightbox-delete-project(tg-lb-delete-project)
include ../includes/modules/lightbox-delete-project include ../includes/modules/lightbox-delete-project

View File

@ -1,5 +1,7 @@
doctype html
div.wrapper(ng-controller="ProjectProfileController as ctrl", div.wrapper(ng-controller="ProjectProfileController as ctrl",
ng-init="section='admin'; sectionName='Reports'") ng-init="section='admin'; sectionName='ADMIN.REPORTS.TITLE'")
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile") sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile")
include ../includes/modules/admin-menu include ../includes/modules/admin-menu
@ -9,24 +11,15 @@ div.wrapper(ng-controller="ProjectProfileController as ctrl",
section.main.admin-common(tg-project-export) section.main.admin-common(tg-project-export)
header header
include ../includes/components/mainTitle include ../includes/components/mainTitle
p.admin-subtitle Export your project data in CSV format and make your own reports. p.admin-subtitle(translate="ADMIN.REPORTS.SUBTITLE")
p Download a CSV file or copy the generated URL and open it in your favourite text editor or spreadsheet to make your own project data reports. You will be able to visualize and analize all your data easily. p(translate="ADMIN.REPORTS.DESCRIPTION")
- var csvType = "US"; div.admin-attributes-section(tg-csv-us)
- var controller = "CsvExporterUserstoriesController"; div.admin-attributes-section(tg-csv-task)
div.admin-attributes-section div.admin-attributes-section(tg-csv-issue)
include ../includes/modules/admin/project-csv
- var csvType = "Task"; div
- var controller = "CsvExporterTasksController";
div.admin-attributes-section
include ../includes/modules/admin/project-csv
- var csvType = "Issues";
- var controller = "CsvExporterIssuesController";
div.admin-attributes-section
include ../includes/modules/admin/project-csv
a.help-button(href="https://taiga.io/support/csv-reports/", target="_blank") a.help-button(href="https://taiga.io/support/csv-reports/", target="_blank")
span.icon.icon-help span.icon.icon-help
span How to use this on my own spreadsheet? span(translate="ADMIN.REPORTS.HELP")

View File

@ -1,4 +1,7 @@
div.wrapper(ng-controller="ProjectValuesSectionController") doctype html
div.wrapper(ng-controller="ProjectValuesSectionController",
ng-init="sectionName='ADMIN.CUSTOM_FIELDS.TITLE'")
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values")
include ../includes/modules/admin-menu include ../includes/modules/admin-menu
@ -7,25 +10,19 @@ div.wrapper(ng-controller="ProjectValuesSectionController")
section.main.admin-common.admin-attributes section.main.admin-common.admin-attributes
include ../includes/components/mainTitle include ../includes/components/mainTitle
p.admin-subtitle Specify the custom fields for your user stories, tasks and issues p.admin-subtitle(translate="ADMIN.CUSTOM_FIELDS.SUBTITLE")
div.admin-attributes-section(tg-project-custom-attributes, div.admin-attributes-section(tg-project-custom-attributes,
ng-controller="ProjectCustomAttributesController as ctrl", ng-controller="ProjectCustomAttributesController as ctrl",
ng-init="type='userstory';") ng-init="type='userstory'; customFieldSectionTitle='ADMIN.CUSTOM_FIELDS.US_DESCRIPTION'; customFieldButtonTitle='ADMIN.CUSTOM_FIELDS.US_ADD'")
- var customFieldSectionTitle = "User stories custom fields"
- var customFieldButtonTitle = "Add a custom field in user stories"
include ../includes/modules/admin/admin-custom-attributes include ../includes/modules/admin/admin-custom-attributes
div.admin-attributes-section(tg-project-custom-attributes, div.admin-attributes-section(tg-project-custom-attributes,
ng-controller="ProjectCustomAttributesController as ctrl", ng-controller="ProjectCustomAttributesController as ctrl",
ng-init="type='task';") ng-init="type='task'; customFieldSectionTitle='ADMIN.CUSTOM_FIELDS.TASK_DESCRIPTION'; customFieldButtonTitle='ADMIN.CUSTOM_FIELDS.TASK_ADD'")
- var customFieldSectionTitle = "Tasks custom fields"
- var customFieldButtonTitle = "Add a custom field in tasks"
include ../includes/modules/admin/admin-custom-attributes include ../includes/modules/admin/admin-custom-attributes
div.admin-attributes-section(tg-project-custom-attributes, div.admin-attributes-section(tg-project-custom-attributes,
ng-controller="ProjectCustomAttributesController as ctrl", ng-controller="ProjectCustomAttributesController as ctrl",
ng-init="type='issue';") ng-init="type='issue'; customFieldSectionTitle='ADMIN.CUSTOM_FIELDS.ISSUE_DESCRIPTION'; customFieldButtonTitle='ADMIN.CUSTOM_FIELDS.ISSUE_ADD'")
- var customFieldSectionTitle = "Issues custom fields"
- var customFieldButtonTitle = "Add a custom field in issues"
include ../includes/modules/admin/admin-custom-attributes include ../includes/modules/admin/admin-custom-attributes

View File

@ -1,3 +1,5 @@
doctype html
div.wrapper(ng-controller="ProjectValuesSectionController") div.wrapper(ng-controller="ProjectValuesSectionController")
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values")
include ../includes/modules/admin-menu include ../includes/modules/admin-menu
@ -7,9 +9,10 @@ div.wrapper(ng-controller="ProjectValuesSectionController")
section.main.admin-common.admin-attributes section.main.admin-common.admin-attributes
include ../includes/components/mainTitle include ../includes/components/mainTitle
p.admin-subtitle Specify the points your user stories could be estimated to p.admin-subtitle(translate="ADMIN.PROJECT_VALUES_POINTS.SUBTITLE")
div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl", div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl",
ng-init="section='admin'; resource='userstories'; type='points'; sectionName='Us points'", ng-init="section='admin'; resource='userstories'; type='points'; sectionName='ADMIN.PROJECT_VALUES_POINTS.TITLE'",
objName="points",
type="points") type="points")
include ../includes/modules/admin/project-points include ../includes/modules/admin/project-points

View File

@ -1,3 +1,5 @@
doctype html
div.wrapper(ng-controller="ProjectValuesSectionController") div.wrapper(ng-controller="ProjectValuesSectionController")
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values")
include ../includes/modules/admin-menu include ../includes/modules/admin-menu
@ -7,9 +9,10 @@ div.wrapper(ng-controller="ProjectValuesSectionController")
section.main.admin-common.admin-attributes section.main.admin-common.admin-attributes
include ../includes/components/mainTitle include ../includes/components/mainTitle
p.admin-subtitle Specify the priorities your issues will have p.admin-subtitle(translate="ADMIN.PROJECT_VALUES_PRIORITIES.SUBTITLE")
div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl", div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl",
ng-init="section='admin'; resource='issues'; type='priorities'; sectionName='Issue priorities'; objName='priority'", ng-init="section='admin'; resource='issues'; type='priorities'; sectionName='ADMIN.PROJECT_VALUES_PRIORITIES.TITLE';",
objName="priorities",
type="priorities") type="priorities")
include ../includes/modules/admin/project-types include ../includes/modules/admin/project-types

View File

@ -1,3 +1,5 @@
doctype html
div.wrapper(ng-controller="ProjectValuesSectionController") div.wrapper(ng-controller="ProjectValuesSectionController")
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values")
include ../includes/modules/admin-menu include ../includes/modules/admin-menu
@ -7,9 +9,10 @@ div.wrapper(ng-controller="ProjectValuesSectionController")
section.main.admin-common.admin-attributes section.main.admin-common.admin-attributes
include ../includes/components/mainTitle include ../includes/components/mainTitle
p.admin-subtitle Specify the severities your issues will have p.admin-subtitle(translate="ADMIN.PROJECT_VALUES_SEVERITIES.SUBTITLE")
div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl", div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl",
ng-init="section='admin'; resource='issues'; type='severities'; sectionName='Issue severities'; objName='severity'", ng-init="section='admin'; resource='issues'; type='severities'; sectionName='ADMIN.PROJECT_VALUES_SEVERITIES.TITLE';",
objName="severities",
type="severities") type="severities")
include ../includes/modules/admin/project-types include ../includes/modules/admin/project-types

View File

@ -1,4 +1,7 @@
div.wrapper(ng-controller="ProjectValuesSectionController") doctype html
div.wrapper(ng-controller="ProjectValuesSectionController",
ng-init="section='admin'; sectionName='ADMIN.PROJECT_VALUES_STATUS.TITLE'")
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values")
include ../includes/modules/admin-menu include ../includes/modules/admin-menu
@ -7,19 +10,22 @@ div.wrapper(ng-controller="ProjectValuesSectionController")
section.main.admin-common.admin-attributes section.main.admin-common.admin-attributes
include ../includes/components/mainTitle include ../includes/components/mainTitle
p.admin-subtitle Specify the statuses your user stories, tasks and issues will go through p.admin-subtitle(translate="ADMIN.PROJECT_VALUES_STATUS.SUBTITLE")
div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl", div.admin-attributes-section(tg-project-values, type="userstory-statuses",
ng-init="section='admin'; resource='userstories'; type='userstory-statuses'; sectionName='Us Statuses'", ng-controller="ProjectValuesController as ctrl",
type="userstory-statuses") ng-init="section='admin'; resource='userstories'; type='userstory-statuses'; sectionName='ADMIN.PROJECT_VALUES_STATUS.US_TITLE'",
objName="status")
include ../includes/modules/admin/project-us-status include ../includes/modules/admin/project-us-status
div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl", div.admin-attributes-section(tg-project-values, type="task-statuses",
ng-init="section='admin'; resource='tasks'; type='task-statuses'; sectionName='Task Statuses'", ng-controller="ProjectValuesController as ctrl",
type="task-statuses") ng-init="section='admin'; resource='tasks'; type='task-statuses'; sectionName='ADMIN.PROJECT_VALUES_STATUS.TASK_TITLE'"
objName="status")
include ../includes/modules/admin/project-status include ../includes/modules/admin/project-status
div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl", div.admin-attributes-section(tg-project-values, type="issue-statuses",
ng-init="section='admin'; resource='issues'; type='issue-statuses'; sectionName='Issue Statuses'", ng-controller="ProjectValuesController as ctrl",
type="issue-statuses") ng-init="section='admin'; resource='issues'; type='issue-statuses'; sectionName='ADMIN.PROJECT_VALUES_STATUS.ISSUE_TITLE'",
objName="status")
include ../includes/modules/admin/project-status include ../includes/modules/admin/project-status

View File

@ -1,4 +1,7 @@
div.wrapper(ng-controller="ProjectValuesSectionController") doctype html
div.wrapper(ng-controller="ProjectValuesSectionController"
ng-init="sectionName='ADMIN.PROJECT_VALUES_TYPES.TITLE'")
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values")
include ../includes/modules/admin-menu include ../includes/modules/admin-menu
@ -7,9 +10,10 @@ div.wrapper(ng-controller="ProjectValuesSectionController")
section.main.admin-common.admin-attributes section.main.admin-common.admin-attributes
include ../includes/components/mainTitle include ../includes/components/mainTitle
p.admin-subtitle Specify the types your user stories could be estimated to p.admin-subtitle(translate="ADMIN.PROJECT_VALUES_TYPES.SUBTITLE")
div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl", div.admin-attributes-section(tg-project-values, ng-controller="ProjectValuesController as ctrl",
ng-init="section='admin'; resource='issues'; type='issue-types'; sectionName='Issue types'; objName='type'", ng-init="section='admin'; resource='issues'; sectionName='ADMIN.PROJECT_VALUES_TYPES.ISSUE_TITLE'; type='issue-types';",
objName="types",
type="issue-types") type="issue-types")
include ../includes/modules/admin/project-types include ../includes/modules/admin/project-types

View File

@ -1,3 +1,5 @@
doctype html
div.wrapper.roles(ng-controller="RolesController as ctrl", div.wrapper.roles(ng-controller="RolesController as ctrl",
ng-init="section='admin'", tg-roles) ng-init="section='admin'", tg-roles)
sidebar.menu-secondary.sidebar(tg-admin-navigation="roles") sidebar.menu-secondary.sidebar(tg-admin-navigation="roles")
@ -6,32 +8,31 @@ div.wrapper.roles(ng-controller="RolesController as ctrl",
include ../includes/modules/admin-submenu-roles include ../includes/modules/admin-submenu-roles
section.main.admin-roles.admin-common section.main.admin-roles.admin-common
.header-with-actions header.header-with-actions
include ../includes/components/mainTitle include ../includes/components/mainTitle
.action-buttons(ng-if="!role.external_user") .action-buttons(ng-if="!role.external_user")
a.button-red.delete-role(href="", title="Delete", ng-click="ctrl.delete()") a.button-red.delete-role(href="", title="{{'COMMON.DELETE' | translate}}", ng-click="ctrl.delete()")
span Delete span(translate="COMMON.DELETE")
div(ng-if="!role.external_user") div(ng-if="!role.external_user")
div(tg-edit-role) div(tg-edit-role)
.edit-role .edit-role
input(type="text", value="{{ role.name }}") input(type="text", value="{{ role.name }}")
a.save.icon.icon-floppy(href="", title="Save") a.save.icon.icon-floppy(href="", title="{{'COMMON.SAVE' | translate}}")
p.total p.total
span.role-name(title="{{ role.members_count }} members with this role") {{ role.name }} span.role-name(title="{{'ADMIN.ROLES.COUNT_MEMBERS' | translate}}") {{ role.name }}
a.edit-value.icon.icon-edit a.edit-value.icon.icon-edit
div.any-computable-role(ng-hide="anyComputableRole") Be careful, no role in your project will be able to estimate the point value for user stories div.any-computable-role(ng-hide="anyComputableRole", translate="ADMIN.ROLES.WARNING_NO_ROLE")
div.general-category div.general-category(translate="ADMIN.ROLES.HELP_ROLE_ENABLED")
| When enabled, members assigned to this role will be able to estimate the point value for user stories
div.check div.check
input(type="checkbox", ng-model="role.computable", ng-change="ctrl.setComputable()") input(type="checkbox", ng-model="role.computable", ng-change="ctrl.setComputable()")
div div
span.check-text.check-yes Yes span.check-text.check-yes(translate="COMMON.YES")
span.check-text.check-no No span.check-text.check-no(translate="COMMON.NO")
div(ng-if="role.external_user") div(ng-if="role.external_user")
p.total p.total

View File

@ -1,3 +1,5 @@
doctype html
div.wrapper.roles(tg-bitbucket-webhooks, ng-controller="BitbucketController as ctrl", div.wrapper.roles(tg-bitbucket-webhooks, ng-controller="BitbucketController as ctrl",
ng-init="section='admin'") ng-init="section='admin'")
sidebar.menu-secondary.sidebar(tg-admin-navigation="third-parties") sidebar.menu-secondary.sidebar(tg-admin-navigation="third-parties")
@ -10,25 +12,25 @@ div.wrapper.roles(tg-bitbucket-webhooks, ng-controller="BitbucketController as c
form form
fieldset fieldset
label(for="secret-key") Secret key label(for="secret-key", translate="ADMIN.THIRD_PARTIES.SECRET_KEY")
input(type="text", name="secret-key", ng-model="bitbucket.secret", placeholder="Secret key", id="secret-key") input(type="text", name="secret-key", ng-model="bitbucket.secret", placeholder="{{'ADMIN.THIRD_PARTIES.SECRET_KEY' | translate}}", id="secret-key")
fieldset fieldset
.select-input-text(tg-select-input-text) .select-input-text(tg-select-input-text)
div div
label(for="payload-url") Payload URL label(for="payload-url", translate="ADMIN.THIRD_PARTIES.PAYLOAD_URL")
.field-with-option .field-with-option
input(type="text", ng-model="bitbucket.webhooks_url", name="payload-url", readonly="readonly", placeholder="Payload URL", id="payload-url") input(type="text", ng-model="bitbucket.webhooks_url", name="payload-url", readonly="readonly", placeholder="{{'ADMIN.THIRD_PARTIES.PAYLOAD_URL' | translate}}", id="payload-url")
.option-wrapper.select-input-content .option-wrapper.select-input-content
.icon.icon-copy .icon.icon-copy
.help-copy Copy to clipboard: Ctrl+C .help-copy(translate="COMMON.COPY_TO_CLIPBOARD")
fieldset fieldset
label(for="valid-origin-ips") Valid origin ips (separated by ,) label(for="valid-origin-ips", translate="ADMIN.THIRD_PARTIES.VALID_IPS")
input(type="text", name="valid-origin-ips", tg-valid-origin-ips, ng-model="bitbucket.valid_origin_ips", placeholder="Bitbucket requests are not signed so the best way of verifying the origin is by IP. If the field is empty there will be no IP validation.", id="valid-origin-ips") input(type="text", name="valid-origin-ips", tg-valid-origin-ips, ng-model="bitbucket.valid_origin_ips", placeholder="{{'ADMIN.BITBUCKET.INFO_VERIFYING_IP' | translate}}", id="valid-origin-ips")
button.button-green.submit-button(type="submit", title="Save") Save button.button-green.submit-button(type="submit", title="{{'COMMON.SAVE' | translate}}", translate="COMMON.SAVE")
a.help-button(href="https://taiga.io/support/bitbucket-integration/", target="_blank") a.help-button(href="https://taiga.io/support/bitbucket-integration/", target="_blank")
span.icon.icon-help span.icon.icon-help
span Do you need help? Check out our support page! span(translate="ADMIN.HELP")

View File

@ -1,3 +1,5 @@
doctype html
div.wrapper.roles(tg-github-webhooks, ng-controller="GithubController as ctrl", div.wrapper.roles(tg-github-webhooks, ng-controller="GithubController as ctrl",
ng-init="section='admin'") ng-init="section='admin'")
sidebar.menu-secondary.sidebar(tg-admin-navigation="third-parties") sidebar.menu-secondary.sidebar(tg-admin-navigation="third-parties")
@ -10,21 +12,21 @@ div.wrapper.roles(tg-github-webhooks, ng-controller="GithubController as ctrl",
form form
fieldset fieldset
label(for="secret-key") Secret key label(for="secret-key", translate="ADMIN.THIRD_PARTIES.SECRET_KEY")
input(type="text", name="secret-key", ng-model="github.secret", placeholder="Secret key", id="secret-key") input(type="text", name="secret-key", ng-model="github.secret", placeholder="{{'ADMIN.THIRD_PARTIES.SECRET_KEY' | translate}}", id="secret-key")
fieldset fieldset
.select-input-text(tg-select-input-text) .select-input-text(tg-select-input-text)
div div
label(for="payload-url") Payload URL label(for="payload-url", translate="ADMIN.THIRD_PARTIES.PAYLOAD_URL")
.field-with-option .field-with-option
input(type="text", ng-model="github.webhooks_url", name="payload-url", readonly="readonly", placeholder="Payload URL", id="payload-url") input(type="text", ng-model="github.webhooks_url", name="payload-url", readonly="readonly", placeholder="{{'ADMIN.THIRD_PARTIES.PAYLOAD_URL' | translate}}", id="payload-url")
.option-wrapper.select-input-content .option-wrapper.select-input-content
.icon.icon-copy .icon.icon-copy
.help-copy Copy to clipboard: Ctrl+C .help-copy(translate="COMMON.COPY_TO_CLIPBOARD")
button.button-green.submit-button(type="submit", title="Save") Save button.button-green.submit-button(type="submit", title="{{'COMMON.SAVE' | translate}}", translate="COMMON.SAVE")
a.help-button(href="https://taiga.io/support/github-integration/", target="_blank") a.help-button(href="https://taiga.io/support/github-integration/", target="_blank")
span.icon.icon-help span.icon.icon-help
span Do you need help? Check out our support page! span(translate="ADMIN.HELP")

View File

@ -1,38 +1,36 @@
block head doctype html
title Taiga Your agile, free, and open source project management tool
block content div.wrapper.roles(tg-gitlab-webhooks, ng-controller="GitlabController as ctrl",
div.wrapper.roles(tg-gitlab-webhooks, ng-controller="GitlabController as ctrl", ng-init="section='admin'")
ng-init="section='admin'") sidebar.menu-secondary.sidebar(tg-admin-navigation="third-parties")
sidebar.menu-secondary.sidebar(tg-admin-navigation="third-parties") include ../includes/modules/admin-menu
include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="third-parties-gitlab")
sidebar.menu-tertiary.sidebar(tg-admin-navigation="third-parties-gitlab") include ../includes/modules/admin-submenu-third-parties
include ../includes/modules/admin-submenu-third-parties
section.main.admin-common.admin-third-parties section.main.admin-common.admin-third-parties
include ../includes/components/mainTitle include ../includes/components/mainTitle
form form
fieldset fieldset
label(for="secret-key") Secret key label(for="secret-key", translate="ADMIN.THIRD_PARTIES.SECRET_KEY")
input(type="text", name="secret-key", ng-model="gitlab.secret", placeholder="Secret key", id="secret-key") input(type="text", name="secret-key", ng-model="gitlab.secret", placeholder="{{'ADMIN.THIRD_PARTIES.SECRET_KEY' | translate}}", id="secret-key")
fieldset fieldset
.select-input-text(tg-select-input-text) .select-input-text(tg-select-input-text)
div div
label(for="payload-url") Payload URL label(for="payload-url", translate="ADMIN.THIRD_PARTIES.PAYLOAD_URL")
.field-with-option .field-with-option
input(type="text", ng-model="gitlab.webhooks_url", name="payload-url", readonly="readonly", placeholder="Payload URL", id="payload-url") input(type="text", ng-model="gitlab.webhooks_url", name="payload-url", readonly="readonly", placeholder="{{'ADMIN.THIRD_PARTIES.PAYLOAD_URL' | translate}}", id="payload-url")
.option-wrapper.select-input-content .option-wrapper.select-input-content
.icon.icon-copy .icon.icon-copy
.help-copy Copy to clipboard: Ctrl+C .help-copy(translate="COMMON.COPY_TO_CLIPBOARD")
fieldset fieldset
label(for="valid-origin-ips") Valid origin ips (separated by ,) label(for="valid-origin-ips", translate="ADMIN.THIRD_PARTIES.VALID_IPS")
input(type="text", name="valid-origin-ips", tg-valid-origin-ips, ng-model="gitlab.valid_origin_ips", placeholder="Gitlab requests are not signed so the best way of verifying the origin is by IP. If the field is empty there will be no IP validation.", id="valid-origin-ips") input(type="text", name="valid-origin-ips", tg-valid-origin-ips, ng-model="gitlab.valid_origin_ips", placeholder="{{'ADMIN.GITLAB.INFO_VERIFYING_IP' | translate}}", id="valid-origin-ips")
button.button-green.submit-button(type="submit", title="Save") Save button.button-green.submit-button(type="submit", title="{{'COMMON.SAVE' | translate}}", translate="COMMON.SAVE")
a.help-button(href="https://taiga.io/support/gitlab-integration/", target="_blank") a.help-button(href="https://taiga.io/support/gitlab-integration/", target="_blank")
span.icon.icon-help span.icon.icon-help
span Do you need help? Check out our support page! span(translate="ADMIN.HELP")

View File

@ -1,99 +1,97 @@
block head doctype html
title Taiga Your agile, free, and open source project management tool
block content div.wrapper.roles(ng-controller="WebhooksController as ctrl",
div.wrapper.roles(ng-controller="WebhooksController as ctrl", ng-init="section='admin'")
ng-init="section='admin'") sidebar.menu-secondary.sidebar(tg-admin-navigation="third-parties")
sidebar.menu-secondary.sidebar(tg-admin-navigation="Webhooks") include ../includes/modules/admin-menu
include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="third-parties-webhooks")
sidebar.menu-tertiary.sidebar(tg-admin-navigation="third-parties-webhooks") include ../includes/modules/admin-submenu-third-parties
include ../includes/modules/admin-submenu-third-parties
section.main.admin-common.admin-webhooks(tg-new-webhook) section.main.admin-common.admin-webhooks(tg-new-webhook)
include ../includes/components/mainTitle include ../includes/components/mainTitle
p.admin-subtitle Webhooks notify external services about events in Taiga, like comments, user stories.... p.admin-subtitle(translate="ADMIN.WEBHOOKS.SUBTITLE")
div.webhooks-options div.webhooks-options
a.button-green.hidden.add-webhook(href="",title="Add a New Webhook") Add Webhook a.button-green.hidden.add-webhook(href="", title="{{'ADMIN.WEBHOOKS.ADD_NEW' | translate}}", translate="ADMIN.WEBHOOKS.ADD_NEW")
section.webhooks-table.basic-table section.webhooks-table.basic-table
div.table-header div.table-header
div.row div.row
div.webhook-service Name div.webhook-service(translate="COMMON.FIELDS.NAME")
div.webhook-url URL div.webhook-url(translate="COMMON.FIELDS.URL")
div.webhook-options div.webhook-options
div.table-body div.table-body
div.single-webhook-wrapper(tg-webhook="webhook", ng-repeat="webhook in webhooks") div.single-webhook-wrapper(tg-webhook="webhook", ng-repeat="webhook in webhooks")
div.edition-mode.hidden div.edition-mode.hidden
form.row form.row
fieldset.webhook-service fieldset.webhook-service
input(type="text", name="name", placeholder="Type the service name", data-required="true", ng-model="webhook.name") input(type="text", name="name", placeholder="{{'ADMIN.WEBHOOKS.TYPE_NAME' | translate}}", data-required="true", ng-model="webhook.name")
div.webhook-url div.webhook-url
div.webhook-url-inputs div.webhook-url-inputs
fieldset fieldset
input(type="text", name="url", data-type="url", placeholder="Type the service payload url", data-required="true", ng-model="webhook.url") input(type="text", name="url", data-type="url", placeholder="{{'ADMIN.WEBHOOKS.TYPE_PAYLOAD_URL' | translate}}", data-required="true", ng-model="webhook.url")
fieldset fieldset
input(type="text", name="key", placeholder="Type the service secret key", data-required="true", ng-model="webhook.key") input(type="text", name="key", placeholder="{{'ADMIN.WEBHOOKS.TYPE_SERVICE_SECRET' | translate}}", data-required="true", ng-model="webhook.key")
div.webhook-options div.webhook-options
a.edit-existing.icon.icon-floppy(href="", title="Save Webhook") a.edit-existing.icon.icon-floppy(href="", title="{{'ADMIN.WEBHOOKS.SAVE' | translate}}")
a.cancel-existing.icon.icon-delete(href="", title="Cancel Webhook") a.cancel-existing.icon.icon-delete(href="", title="{{'ADMIN.WEBHOOKS.CANCEL' | translate}}")
div.visualization-mode div.visualization-mode
div.row div.row
div.webhook-service div.webhook-service
span(ng-bind="webhook.name") span(ng-bind="webhook.name")
div.webhook-url div.webhook-url
span(ng-bind="webhook.url") span(ng-bind="webhook.url")
a.show-history.toggle-history(href="", title="Toggle history", ng-show="webhook.logs_counter") (Show history) a.show-history.toggle-history(href="", title="{{'ADMIN.WEBHOOKS.SHOW_HISTORY_TITLE' | translate}}", ng-show="webhook.logs_counter", translate="ADMIN.WEBHOOKS.SHOW_HISTORY")
div.webhook-options div.webhook-options
div.webhook-options-wrapper div.webhook-options-wrapper
a.test-webhook.icon.icon-check-square(href="", title="Test Webhook") a.test-webhook.icon.icon-check-square(href="", title="{{'ADMIN.WEBHOOKS.TEST' | translate}}")
a.edit-webhook.icon.icon-edit(href="", title="Edit Webhook") a.edit-webhook.icon.icon-edit(href="", title="{{'ADMIN.WEBHOOKS.EDIT' | translate}}")
a.delete-webhook.icon.icon-delete(href="", title="Delete Webhook") a.delete-webhook.icon.icon-delete(href="", title="{{'ADMIN.WEBHOOKS.DELETE' | translate}}")
div.webhooks-history(ng-show="webhook.logs") div.webhooks-history(ng-show="webhook.logs")
div.history-single-wrapper(ng-repeat="log in webhook.logs") div.history-single-wrapper(ng-repeat="log in webhook.logs")
div.history-single div.history-single
div div
span.history-response-icon(ng-class="log.validStatus ? 'history-success' : 'history-error'", title="{{log.status}}") span.history-response-icon(ng-class="log.validStatus ? 'history-success' : 'history-error'", title="{{log.status}}")
span.history-date(ng-bind="log.prettyDate") span.history-date(ng-bind="log.prettyDate")
span.toggle-log.icon.icon-arrow-bottom span.toggle-log.icon.icon-arrow-bottom
div.history-single-response div.history-single-response
div.history-single-request-header div.history-single-request-header
span Request span(translate="ADMIN.WEBHOOKS.REQUEST")
a.resend-request(href="", title="Resend request", data-log="{{log.id}}") a.resend-request(href="", title="{{'ADMIN.WEBHOOKS.RESEND_REQUEST' | translate}}", data-log="{{log.id}}")
span.icon.icon-reload span.icon.icon-reload
span Resend request span(translate="ADMIN.WEBHOOKS.RESEND_REQUEST")
div.history-single-request-body div.history-single-request-body
div.response-container div.response-container
span.response-title Headers span.response-title(translate="ADMIN.WEBHOOKS.HEADERS")
textarea(name="headers", ng-bind="log.prettySentHeaders") textarea(name="headers", ng-bind="log.prettySentHeaders")
div.response-container div.response-container
span.response-title Payload span.response-title(translate="ADMIN.WEBHOOKS.PAYLOAD")
textarea(name="payload", ng-bind="log.prettySentData") textarea(name="payload", ng-bind="log.prettySentData")
div.history-single-response-header div.history-single-response-header
span Response span(translate="ADMIN.WEBHOOKS.RESPONSE")
div.history-single-response-body div.history-single-response-body
div.response-container div.response-container
textarea(name="response-data", ng-bind="log.response_data") textarea(name="response-data", ng-bind="log.response_data")
form.new-webhook-form.row.hidden form.new-webhook-form.row.hidden
fieldset.webhook-service fieldset.webhook-service
input(type="text", name="name", placeholder="Type the service name", data-required="true", ng-model="newValue.name") input(type="text", name="name", placeholder="{{'ADMIN.WEBHOOKS.TYPE_NAME' | translate}}", data-required="true", ng-model="newValue.name")
div.webhook-url div.webhook-url
div.webhook-url-inputs div.webhook-url-inputs
fieldset fieldset
input(type="text", name="url", data-type="url", placeholder="Type the service payload url", data-required="true", ng-model="newValue.url") input(type="text", name="url", data-type="url", placeholder="{{'ADMIN.WEBHOOKS.TYPE_PAYLOAD_URL' | translate}}", data-required="true", ng-model="newValue.url")
fieldset fieldset
input(type="text", name="key", placeholder="Type the service secret key", data-required="true", ng-model="newValue.key") input(type="text", name="key", placeholder="{{'ADMIN.WEBHOOKS.TYPE_SERVICE_SECRET' | translate}}", data-required="true", ng-model="newValue.key")
div.webhook-options div.webhook-options
a.add-new.icon.icon-floppy(href="", title="Save Webhook") a.add-new.icon.icon-floppy(href="", title="{{'ADMIN.WEBHOOKS.SAVE' | translate}}")
a.cancel-new.icon.icon-delete(href="", title="Cancel Webhook") a.cancel-new.icon.icon-delete(href="", title="{{'ADMIN.WEBHOOKS.CANCEL' | translate}}")
a.help-button(href="https://taiga.io/support/webhooks/", target="_blank") a.help-button(href="https://taiga.io/support/webhooks/", target="_blank")
span.icon.icon-help span.icon.icon-help
span Do you need help? Check out our support page! span(translate="ADMIN.HELP")

View File

@ -0,0 +1,20 @@
section.project-csv(tg-select-input-text)
div.project-values-title
h2(translate="{{::sectionTitle}}")
a.button.button-gray(title="{{'ADMIN.CSV.DOWNLOAD' | translate}}",
ng-href="{{csvUrl}}", ng-show="csvUrl", target="_blank")
span(translate="ADMIN.CSV.DOWNLOAD")
div.csv-regenerate-field
div.field-with-options
input(type="text", placeholder="{{'ADMIN.CSV.URL_FIELD_PLACEHOLDER' | translate}}",
readonly, ng-model="csvUrl")
div.option-wrapper.select-input-content
div.icon.icon-copy
a(href="", title="{{'ADMIN.CSV.TITLE_REGENERATE_URL' | translate}}", ng-click="ctrl.regenerateUuid()")
span.icon.icon-plus(ng-hide="csvUrl")
span(ng-hide="csvUrl", translate="ADMIN.CSV.ACTION_GENERATE_URL")
span.icon.icon-reload(ng-Show="csvUrl")
span(ng-Show="csvUrl", translate="ADMIN.CSV.ACTION_REGENERATE")

View File

@ -1,19 +1,19 @@
.attachment-name .attachment-name
span.icon.icon-document span.icon.icon-document
a(href!="<%- url %>", title!="<%- name %> uploaded on <%- created_date %>", target="_blank") a(href!="<%- url %>", title!="<%- title %>", target="_blank")
| <%- name %> | <%- name %>
.attachment-size .attachment-size
span <%- size %> span <%- size %>
.editable.editable-attachment-comment .editable.editable-attachment-comment
input(type="text", name="description", maxlength="140", input(type="text", name="description", maxlength="140",
value!="<%- description %>", placeholder="Type a short description") value!="<%- description %>", placeholder="{{'ATTACHMENT.DESCRIPTION' | translate}}")
.editable.editable-attachment-deprecated .editable.editable-attachment-deprecated
input(type="checkbox", name="is-deprecated", input(type="checkbox", name="is-deprecated",
id!="attach-<%- id %>-is-deprecated") id!="attach-<%- id %>-is-deprecated")
label(for!="attach-<%- id %>-is-deprecated") Deprecated? label(for!="attach-<%- id %>-is-deprecated", translate="{{'ATTACHMENT.DEPRECATED_FILE' | translate}}")
.attachment-settings .attachment-settings
a.editable-settings.icon.icon-floppy(href="", title="Save") a.editable-settings.icon.icon-floppy(href="", title="{{'COMMON.SAVE' | translate}}")
a.editable-settings.icon.icon-delete(href="", title="Cancel") a.editable-settings.icon.icon-delete(href="", title="{{'COMMON.CANCEL' | translate}}")

View File

@ -1,5 +1,5 @@
.attachment-name .attachment-name
a(href!="<%- url %>", title!="<%- name %> uploaded on <%- created_date %>", target="_blank") a(href!="<%- url %>", title!="<%- title %>", target="_blank")
span.icon.icon-documents span.icon.icon-documents
span <%- name %> span <%- name %>
.attachment-size .attachment-size
@ -7,13 +7,13 @@
.attachment-comments .attachment-comments
<% if (isDeprecated){ %> <% if (isDeprecated){ %>
span.deprecated-file (deprecated) span.deprecated-file {{'ATTACHMENT.DEPRECATED' | translate}}
<% } %> <% } %>
span <%- description %> span <%- description %>
<% if (modifyPermission) {%> <% if (modifyPermission) {%>
.attachment-settings .attachment-settings
a.settings.icon.icon-edit(href="", title="Edit") a.settings.icon.icon-edit(href="", title="{{'COMMON.EDIT' | translate}}")
a.settings.icon.icon-delete(href="", title="Delete") a.settings.icon.icon-delete(href="", title="{{'COMMON.DELETE' | translate}}")
a.settings.icon.icon-drag-v(href="", title="Drag") a.settings.icon.icon-drag-v(href="", title="{{'COMMON.DRAG' | translate}}")
<% } %> <% } %>

View File

@ -2,10 +2,10 @@ section.attachments
.attachments-header .attachments-header
h3.attachments-title h3.attachments-title
span.attachments-num(tg-bind-html="ctrl.attachmentsCount") span.attachments-num(tg-bind-html="ctrl.attachmentsCount")
span.attachments-text attachments span.attachments-text(translate="ATTACHMENT.SECTION_NAME")
.add-attach(tg-check-permission!="modify_<%- type %>", title!="Add new attachment. <%- maxFileSizeMsg %>") .add-attach(tg-check-permission!="modify_<%- type %>", title!="{{'ATTACHMENT.ADD' | translate}}")
<% if (maxFileSize){ %> <% if (maxFileSize){ %>
span.size-info.hidden [Max. size: <%- maxFileSize %>] span.size-info.hidden(translate="ATTACHMENT.MAX_FILE_SIZE", translate-values!="{ 'maxFileSize': '<%- maxFileSize %>'}")
<% }; %> <% }; %>
label(for="add-attach", class="icon icon-plus related-tasks-buttons") label(for="add-attach", class="icon icon-plus related-tasks-buttons")
input(id="add-attach", type="file", multiple="multiple") input(id="add-attach", type="file", multiple="multiple")
@ -22,9 +22,7 @@ section.attachments
span(ng-bind="file.progressMessage") span(ng-bind="file.progressMessage")
.percentage(ng-style="{'width': file.progressPercent}") .percentage(ng-style="{'width': file.progressPercent}")
a.more-attachments(href="", title="show deprecated atachments", ng-if="ctrl.deprecatedAttachmentsCount > 0") a.more-attachments(href="", title="{{'ATTACHMENT.SHOW_DEPRECATED' | translate}}", ng-if="ctrl.deprecatedAttachmentsCount > 0")
span.text(data-type="show") + show deprecated atachments span.text(data-type="show", translate="ATTACHMENT.SHOW_DEPRECATED")
span.text.hidden(data-type="hide") span.text.hidden(data-type="hide", translate="ATTACHMENT.HIDE_DEPRECATED")
| - hide deprecated atachments span.more-attachments-num(translate="ATTACHMENT.COUNT_DEPRECATED", translate-values="{counter: '{{ctrl.deprecatedAttachmentsCount}}'}")
span.more-attachments-num
| ({{ctrl.deprecatedAttachmentsCount }} deprecated)

View File

@ -1,8 +1,10 @@
doctype html
div.wrapper div.wrapper
div.login-main div.login-main
div.login-container div.login-container
h1.logo h1.logo
img.logo-svg(src="/svg/logo.svg", alt="TAIGA") img.logo-svg(src="/svg/logo.svg", alt="TAIGA")
p.tagline Your agile, free, and open source project management tool p.tagline(translate="COMMON.TAG_LINE")
include ../includes/modules/change-password-from-recovery-form include ../includes/modules/change-password-from-recovery-form

View File

@ -1,9 +1,11 @@
doctype html
include ../includes/components/beta include ../includes/components/beta
div.wrapper div.wrapper
div.login-main div.login-main
div.login-container div.login-container
img.logo-svg(src="/svg/logo.svg", alt="TAIGA") img.logo-svg(src="/svg/logo.svg", alt="TAIGA")
h1.logo Taiga h1.logo Taiga
h2.tagline LOVE YOUR PROJECT h2.tagline(translate="COMMON.TAG_LINE_2")
include ../includes/modules/forgot-form include ../includes/modules/forgot-form

View File

@ -1,3 +1,5 @@
doctype html
div.wrapper div.wrapper
div.invitation-main div.invitation-main
div.invitation-container(tg-invitation) div.invitation-container(tg-invitation)
@ -8,7 +10,7 @@ div.wrapper
span.person-name(tg-bo-bind="invitation.invited_by.full_name_display") span.person-name(tg-bo-bind="invitation.invited_by.full_name_display")
span.invitation-text span.invitation-text
p has invited you to join the project p(translate="AUTH.INVITED_YOU")
p.project-name(tg-bo-bind="invitation.project_name") p.project-name(tg-bo-bind="invitation.project_name")
div.invitation-form div.invitation-form

View File

@ -1,3 +1,4 @@
p.login-text p.login-text
span Not registered yet? span(translate="AUTH.NOT_REGISTERED_YET")
a(href!='<%- url %>', tg-nav='register', title='Register') create your free account here | &nbsp;
a(href!='<%- url %>', tg-nav='register', title='{{"AUTH.REGISTER" | translate}}', translate="AUTH.CREATE_ACCOUNT")

View File

@ -1,9 +1,11 @@
doctype html
include ../includes/components/beta include ../includes/components/beta
div.wrapper div.wrapper
div.login-main div.login-main
div.login-container div.login-container
img.logo-svg(src="/svg/logo.svg", alt="TAIGA") img.logo-svg(src="/svg/logo.svg", alt="TAIGA")
h1.logo Taiga h1.logo Taiga
h2.tagline LOVE YOUR PROJECT h2.tagline(translate="COMMON.TAG_LINE_2")
include ../includes/modules/login-form include ../includes/modules/login-form

View File

@ -1,8 +1,10 @@
doctype html
div.wrapper div.wrapper
div.login-main div.login-main
div.login-container div.login-container
img.logo-svg(src="/svg/logo.svg", alt="TAIGA") img.logo-svg(src="/svg/logo.svg", alt="TAIGA")
h1.logo Taiga h1.logo Taiga
h2.tagline LOVE YOUR PROJECT h2.tagline(translate="COMMON.TAG_LINE_2")
include ../includes/modules/register-form include ../includes/modules/register-form

View File

@ -1,32 +1,48 @@
doctype html
div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl", div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl",
ng-init="section='backlog'") ng-init="section='backlog'")
sidebar.menu-secondary.extrabar.filters-bar(tg-backlog-filters) sidebar.menu-secondary.extrabar.filters-bar(tg-backlog-filters)
include ../includes/modules/backlog-filters include ../includes/modules/backlog-filters
section.main.backlog section.main.backlog
include ../includes/components/mainTitle include ../includes/components/mainTitle
include ../includes/components/summary include ../includes/components/summary
div.graphics-container.burndown-container div.graphics-container.burndown-container
div.burndown(tg-gm-backlog-graph) div.burndown(tg-burndown-backlog-graph)
include ../includes/modules/burndown include ../includes/modules/burndown
div.backlog-menu div.backlog-menu
div.backlog-table-options div.backlog-table-options
a.trans-button.move-to-current-sprint(href="", title="Move to Current Sprint", a.trans-button.move-to-current-sprint(href="",
title="{{'BACKLOG.MOVE_US_TO_CURRENT_SPRINT' | translate}}",
id="move-to-current-sprint") id="move-to-current-sprint")
span.icon.icon-move span.icon.icon-move
span.text Move to current Sprint span.text(translate="BACKLOG.MOVE_US_TO_CURRENT_SPRINT")
a.trans-button(href="", title="Show Filters", id="show-filters-button") a.trans-button(href="",
title="{{'BACKLOG.FILTERS.TOGGLE' | translate}}",
id="show-filters-button")
span.icon.icon-filter span.icon.icon-filter
span.text Show Filters span.text(translate="BACKLOG.FILTERS.SHOW")
a.trans-button(href="", title="Show Tags", id="show-tags") a.trans-button(href="",
title="{{'BACKLOG.TAGS.TOGGLE' | translate}}",
id="show-tags")
span.icon.icon-tag span.icon.icon-tag
span.text Show Tags span.text(translate="BACKLOG.TAGS.SHOW")
include ../includes/components/addnewus include ../includes/components/addnewus
section.backlog-table(ng-class="{'hidden': !visibleUserstories.length}") section.backlog-table(ng-class="{'hidden': !visibleUserstories.length}")
include ../includes/modules/backlog-table include ../includes/modules/backlog-table
div.empty.empty-backlog(ng-class="{'hidden': visibleUserstories.length}", tg-backlog-empty-sortable) div.empty.empty-backlog(ng-class="{'hidden': visibleUserstories.length}", tg-backlog-empty-sortable)
span.icon.icon-backlog span.icon.icon-backlog
span.title Your backlog is empty! span.title(translate="BACKLOG.EMPTY")
a(href="", title+"Create a new US", ng-click="ctrl.addNewUs('standard')", tg-check-permission="add_us") You may want to create a new user story a(href="", title="{{'BACKLOG.CREATE_NEW_US' | translate}}",
ng-click="ctrl.addNewUs('standard')",
tg-check-permission="add_us",
translate="BACKLOG.CREATE_NEW_US_EMPTY_HELP")
sidebar.menu-secondary.sidebar sidebar.menu-secondary.sidebar
include ../includes/modules/sprints include ../includes/modules/sprints

View File

@ -1,3 +1,6 @@
.defined-points(title="Excess of points") .defined-points(title="{{'BACKLOG.EXCESS_OF_POINTS' | translate}}")
.project-points-progress(title="Pending Points", style!="width: <%- projectPointsPercentaje %>%")
.closed-points-progress(title="Closed points", style!="width: <%- closedPointsPercentaje %>%") .project-points-progress(title="{{'BACKLOG.PENDING_POINTS' | translate}}",
style!="width: <%- projectPointsPercentaje %>%")
.closed-points-progress(title="{{'BACKLOG.CLOSED_POINTS' | translate}}",
style!="width: <%- closedPointsPercentaje %>%")

View File

@ -1,20 +1,16 @@
.sprint-name .sprint-name
a.icon.icon-arrow-up(href="", title="Compact Sprint") a.icon.icon-arrow-up(href="", title="{{'BACKLOG.COMPACT_SPRINT' | translate}}")
<% if(isVisible){ %> a(ng-if="::isVisible", href="{{::taskboardUrl}}", title="{{'BACKLOG.GO_TO_TASKBOARD' | translate}}")
a(href!="<%- taskboardUrl %>", title!="'Go to the taskboard of '<%- name %>'") span {{::name}}
span <%- name %>
<% } %>
<% if(isEditable){ %> a.icon.icon-edit(ng-if="::isEditable", href="", title="{{'BACKLOG.EDIT_SPRINT' | translate}}")
a.icon.icon-edit(href="", title="Edit Sprint")
<% } %>
.sprint-summary .sprint-summary
.sprint-date <%- estimatedDateRange %> .sprint-date {{::estimatedDateRange}}
ul ul
li li
span.number <%- closedPoints %> span.number {{::closedPoints}}
span.description closed span.description(translate="BACKLOG.CLOSED_POINTS")
li li
span.number <%- totalPoints %> span.number {{::totalPoints}}
span.description total span.description(translate="BACKLOG.TOTAL_POINTS")

View File

@ -1,6 +1,6 @@
ul.popover.pop-role ul.popover.pop-role
li li
a.clear-selection(href="", title="All") All a.clear-selection(href="", title="{{'COMMON.ROLES.ALL' | translate}}", translate="COMMON.ROLES.ALL")
<% _.each(roles, function(role) { %> <% _.each(roles, function(role) { %>
li li
a(href="", class="role", title!="<%- role.name %>", data-role-id!="<%- role.id %>") a(href="", class="role", title!="<%- role.name %>", data-role-id!="<%- role.id %>")

View File

@ -4,18 +4,19 @@
<% } %> <% } %>
.assigned-to .assigned-to
span.assigned-title Assigned to span.assigned-title(translate="COMMON.FIELDS.ASSIGNED_TO")
a(href="" title="edit assignment", class!="user-assigned <% if(isEditable){ %>editable<% }; %>") a(href="" title="{{ 'COMMON.ASSIGNED_TO.TITLE_ACTION_EDIT_ASSIGNMENT'|translate }}",
class!="user-assigned <% if(isEditable){ %>editable<% }; %>")
span.assigned-name span.assigned-name
<% if (assignedTo) { %> <% if (assignedTo) { %>
<%- assignedTo.full_name_display %> <%- assignedTo.full_name_display %>
<% } else { %> <% } else { %>
| Not assigned | {{ 'COMMON.ASSIGNED_TO.NOT_ASSIGNED'|translate }}
<% } %> <% } %>
<% if(isEditable){ %> <% if(isEditable){ %>
span.icon.icon-arrow-bottom span.icon.icon-arrow-bottom
<% }; %> <% }; %>
<% if (assignedTo!==null && isEditable) { %> <% if (assignedTo!==null && isEditable) { %>
a.icon.icon-delete(href="" title="delete assignment") a.icon.icon-delete(href="" title="{{'COMMON.ASSIGNED_TO.DELETE_ASSIGNMENT' | translate}}")
<% } %> <% } %>

View File

@ -1,4 +1,4 @@
a(href="#", class="button button-gray item-block") a(href="#", class="button button-gray item-block")
span Block span(translate="COMMON.BLOCK")
a(href="#", class="button button-red item-unblock") a(href="#", class="button button-red item-unblock")
span Unblock span(translate="COMMON.UNBLOCK")

View File

@ -2,7 +2,6 @@
img(src!="<%- owner.photo %>", alt!="<%- owner.full_name_display %>") img(src!="<%- owner.photo %>", alt!="<%- owner.full_name_display %>")
.created-by .created-by
span.created-title span.created-title(translate="COMMON.CREATED_BY", translate-values!="{ 'fullDisplayName': '<%- owner.full_name_display %>'}")
| Created by <%- owner.full_name_display %>
span.created-date span.created-date
| <%- date %> | <%- date %>

View File

@ -1,2 +1,2 @@
a(href="", class="button button-red") a(href="", class="button button-red")
span Delete span(translate="COMMON.DELETE")

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