taiga-front/app/coffee/app.coffee

639 lines
19 KiB
CoffeeScript

###
# 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: app.coffee
###
@taiga = taiga = {}
@.taigaContribPlugins = @.taigaContribPlugins or []
# Generic function for generate hash from a arbitrary length
# collection of parameters.
taiga.generateHash = (components=[]) ->
components = _.map(components, (x) -> JSON.stringify(x))
return hex_sha1(components.join(":"))
taiga.generateUniqueSessionIdentifier = ->
date = (new Date()).getTime()
randomNumber = Math.floor(Math.random() * 0x9000000)
return taiga.generateHash([date, randomNumber])
taiga.sessionId = taiga.generateUniqueSessionIdentifier()
configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEventsProvider,
$compileProvider, $translateProvider) ->
$routeProvider.when("/",
{
templateUrl: "home/home.html",
access: {
requiresLogin: true
},
title: "HOME.PAGE_TITLE",
description: "HOME.PAGE_DESCRIPTION",
loader: true
}
)
$routeProvider.when("/projects/",
{
templateUrl: "projects/listing/projects-listing.html",
access: {
requiresLogin: true
},
title: "PROJECTS.PAGE_TITLE",
description: "PROJECTS.PAGE_DESCRIPTION",
loader: true,
controller: "ProjectsListing",
controllerAs: "vm"
}
)
$routeProvider.when("/project/:pslug/",
{
templateUrl: "projects/project/project.html",
loader: true,
controller: "Project",
controllerAs: "vm"
section: "project-timeline"
}
)
$routeProvider.when("/project/:pslug/search",
{
templateUrl: "search/search.html",
reloadOnSearch: false,
section: "search"
}
)
$routeProvider.when("/project/:pslug/backlog",
{
templateUrl: "backlog/backlog.html",
loader: true,
section: "backlog"
}
)
$routeProvider.when("/project/:pslug/kanban",
{
templateUrl: "kanban/kanban.html",
loader: true,
section: "kanban"
}
)
# Milestone
$routeProvider.when("/project/:pslug/taskboard/:sslug",
{
templateUrl: "taskboard/taskboard.html",
loader: true,
section: "backlog"
}
)
# User stories
$routeProvider.when("/project/:pslug/us/:usref",
{
templateUrl: "us/us-detail.html",
loader: true,
section: "backlog-kanban"
}
)
# Tasks
$routeProvider.when("/project/:pslug/task/:taskref",
{
templateUrl: "task/task-detail.html",
loader: true,
section: "backlog-kanban"
}
)
# Wiki
$routeProvider.when("/project/:pslug/wiki",
{redirectTo: (params) -> "/project/#{params.pslug}/wiki/home"}, )
$routeProvider.when("/project/:pslug/wiki/:slug",
{
templateUrl: "wiki/wiki.html",
loader: true,
section: "wiki"
}
)
# Team
$routeProvider.when("/project/:pslug/team",
{
templateUrl: "team/team.html",
loader: true,
section: "team"
}
)
# Issues
$routeProvider.when("/project/:pslug/issues",
{
templateUrl: "issue/issues.html",
loader: true,
section: "issues"
}
)
$routeProvider.when("/project/:pslug/issue/:issueref",
{
templateUrl: "issue/issues-detail.html",
loader: true,
section: "issues"
}
)
# Admin - Project Profile
$routeProvider.when("/project/:pslug/admin/project-profile/details",
{
templateUrl: "admin/admin-project-profile.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-profile/default-values",
{
templateUrl: "admin/admin-project-default-values.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-profile/modules",
{
templateUrl: "admin/admin-project-modules.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-profile/export",
{
templateUrl: "admin/admin-project-export.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-profile/reports",
{
templateUrl: "admin/admin-project-reports.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-values/status",
{
templateUrl: "admin/admin-project-values-status.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-values/points",
{
templateUrl: "admin/admin-project-values-points.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-values/priorities",
{
templateUrl: "admin/admin-project-values-priorities.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-values/severities",
{
templateUrl: "admin/admin-project-values-severities.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-values/types",
{
templateUrl: "admin/admin-project-values-types.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/project-values/custom-fields",
{
templateUrl: "admin/admin-project-values-custom-fields.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/memberships",
{
templateUrl: "admin/admin-memberships.html",
section: "admin"
}
)
# Admin - Roles
$routeProvider.when("/project/:pslug/admin/roles",
{
templateUrl: "admin/admin-roles.html",
section: "admin"
}
)
# Admin - Third Parties
$routeProvider.when("/project/:pslug/admin/third-parties/webhooks",
{
templateUrl: "admin/admin-third-parties-webhooks.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/third-parties/github",
{
templateUrl: "admin/admin-third-parties-github.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/third-parties/gitlab",
{
templateUrl: "admin/admin-third-parties-gitlab.html",
section: "admin"
}
)
$routeProvider.when("/project/:pslug/admin/third-parties/bitbucket",
{
templateUrl: "admin/admin-third-parties-bitbucket.html",
section: "admin"
}
)
# Admin - Contrib Plugins
$routeProvider.when("/project/:pslug/admin/contrib/:plugin",
{templateUrl: "contrib/main.html"})
# User settings
$routeProvider.when("/user-settings/user-profile",
{templateUrl: "user/user-profile.html"})
$routeProvider.when("/user-settings/user-change-password",
{templateUrl: "user/user-change-password.html"})
$routeProvider.when("/user-settings/mail-notifications",
{templateUrl: "user/mail-notifications.html"})
$routeProvider.when("/change-email/:email_token",
{templateUrl: "user/change-email.html"})
$routeProvider.when("/cancel-account/:cancel_token",
{templateUrl: "user/cancel-account.html"})
# User profile
$routeProvider.when("/profile",
{
templateUrl: "profile/profile.html",
loader: true,
access: {
requiresLogin: true
},
controller: "Profile",
controllerAs: "vm"
}
)
$routeProvider.when("/profile/:slug",
{
templateUrl: "profile/profile.html",
loader: true,
controller: "Profile",
controllerAs: "vm"
}
)
# Auth
$routeProvider.when("/login",
{
templateUrl: "auth/login.html",
title: "LOGIN.PAGE_TITLE"
description: "LOGIN.PAGE_DESCRIPTION"
}
)
$routeProvider.when("/register",
{
templateUrl: "auth/register.html",
title: "REGISTER.PAGE_TITLE",
description: "REGISTER.PAGE_DESCRIPTION"
}
)
$routeProvider.when("/forgot-password",
{
templateUrl: "auth/forgot-password.html",
title: "FORGOT_PASSWORD.PAGE_TITLE",
description: "FORGOT_PASSWORD.PAGE_DESCRIPTION"
}
)
$routeProvider.when("/change-password",
{
templateUrl: "auth/change-password-from-recovery.html",
title: "CHANGE_PASSWORD.PAGE_TITLE",
description: "CHANGE_PASSWORD.PAGE_TITLE",
}
)
$routeProvider.when("/change-password/:token",
{
templateUrl: "auth/change-password-from-recovery.html",
title: "CHANGE_PASSWORD.PAGE_TITLE",
description: "CHANGE_PASSWORD.PAGE_TITLE",
}
)
$routeProvider.when("/invitation/:token",
{
templateUrl: "auth/invitation.html",
title: "INVITATION.PAGE_TITLE",
description: "INVITATION.PAGE_DESCRIPTION"
}
)
# Errors/Exceptions
$routeProvider.when("/error",
{templateUrl: "error/error.html"})
$routeProvider.when("/not-found",
{templateUrl: "error/not-found.html"})
$routeProvider.when("/permission-denied",
{templateUrl: "error/permission-denied.html"})
$routeProvider.otherwise({redirectTo: "/not-found"})
$locationProvider.html5Mode({enabled: true, requireBase: false})
defaultHeaders = {
"Content-Type": "application/json"
"Accept-Language": window.taigaConfig.defaultLanguage || "en"
"X-Session-Id": taiga.sessionId
}
$httpProvider.defaults.headers.delete = defaultHeaders
$httpProvider.defaults.headers.patch = defaultHeaders
$httpProvider.defaults.headers.post = defaultHeaders
$httpProvider.defaults.headers.put = defaultHeaders
$httpProvider.defaults.headers.get = {
"X-Session-Id": taiga.sessionId
}
$httpProvider.useApplyAsync(true)
$tgEventsProvider.setSessionId(taiga.sessionId)
# Add next param when user try to access to a secction need auth permissions.
authHttpIntercept = ($q, $location, $navUrls, $lightboxService) ->
httpResponseError = (response) ->
if response.status == 0
$lightboxService.closeAll()
$location.path($navUrls.resolve("error"))
$location.replace()
else if response.status == 401
nextPath = $location.path()
$location.url($navUrls.resolve("login")).search("next=#{nextPath}")
return $q.reject(response)
return {
responseError: httpResponseError
}
$provide.factory("authHttpIntercept", ["$q", "$location", "$tgNavUrls", "lightboxService",
authHttpIntercept])
$httpProvider.interceptors.push("authHttpIntercept")
loaderIntercept = ($q, loaderService) ->
return {
request: (config) ->
loaderService.logRequest()
return config
requestError: (rejection) ->
loaderService.logResponse()
return $q.reject(rejection)
responseError: (rejection) ->
loaderService.logResponse()
return $q.reject(rejection)
response: (response) ->
loaderService.logResponse()
return response
}
$provide.factory("loaderIntercept", ["$q", "tgLoader", loaderIntercept])
$httpProvider.interceptors.push("loaderIntercept")
# If there is an error in the version throw a notify error.
# IMPROVEiMENT: Move this version error handler to USs, issues and tasks repository
versionCheckHttpIntercept = ($q) ->
httpResponseError = (response) ->
if response.status == 400 && response.data.version
# HACK: to prevent circular dependencies with [$tgConfirm, $translate]
$injector = angular.element("body").injector()
$injector.invoke(["$tgConfirm", "$translate", ($confirm, $translate) =>
versionErrorMsg = $translate.instant("ERROR.VERSION_ERROR")
$confirm.notify("error", versionErrorMsg, null, 10000)
])
return $q.reject(response)
return {responseError: httpResponseError}
$provide.factory("versionCheckHttpIntercept", ["$q", versionCheckHttpIntercept])
$httpProvider.interceptors.push("versionCheckHttpIntercept")
window.checksley.updateValidators({
linewidth: (val, width) ->
lines = taiga.nl2br(val).split("<br />")
valid = _.every lines, (line) ->
line.length < width
return valid
})
$compileProvider.debugInfoEnabled(window.taigaConfig.debugInfo || false)
if localStorage.userInfo
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, $rootscope, $auth, $events, $analytics, $translate, $location, $navUrls, appMetaService, projectService, loaderService) ->
$log.debug("Initialize application")
# Taiga Plugins
$rootscope.contribPlugins = @.taigaContribPlugins
$rootscope.adminPlugins = _.where(@.taigaContribPlugins, {"type": "admin"})
$rootscope.$on "$translateChangeEnd", (e, ctx) ->
lang = ctx.language
i18nInit(lang, $translate)
# bluebird
Promise.setScheduler (cb) ->
$rootscope.$evalAsync(cb)
# Load user
if $auth.isAuthenticated()
$events.setupConnection()
user = $auth.getUser()
# Analytics
$analytics.initialize()
$rootscope.$on '$routeChangeSuccess', (event, next) ->
if next.access && next.access.requiresLogin
if !$auth.isAuthenticated()
$location.path($navUrls.resolve("login"))
projectService.setSection(next.section)
if next.params.pslug
projectService.setProject(next.params.pslug)
else
projectService.cleanProject()
if next.title or next.description
title = next.title or ""
description = next.description or ""
$translate([title, description]).then (translations) =>
appMetaService.setAll(translations[title], translations[description])
if next.loader
loaderService.startWithAutoClose()
modules = [
# Main Global Modules
"taigaBase",
"taigaCommon",
"taigaResources",
"taigaResources2",
"taigaAuth",
"taigaEvents",
# Specific Modules
"taigaHome",
"taigaNavigationBar",
"taigaProjects",
"taigaRelatedTasks",
"taigaBacklog",
"taigaTaskboard",
"taigaKanban",
"taigaIssues",
"taigaUserStories",
"taigaTasks",
"taigaTeam",
"taigaWiki",
"taigaSearch",
"taigaAdmin",
"taigaProject",
"taigaUserSettings",
"taigaFeedback",
"taigaPlugins",
"taigaIntegrations",
"taigaComponents",
# new modules
"taigaProfile",
"taigaHome",
"taigaUserTimeline",
# template cache
"templates",
# Vendor modules
"ngRoute",
"ngAnimate",
"pascalprecht.translate",
"infinite-scroll",
"tgRepeat"
].concat(_.map(@.taigaContribPlugins, (plugin) -> plugin.module))
# Main module definition
module = angular.module("taiga", modules)
module.config([
"$routeProvider",
"$locationProvider",
"$httpProvider",
"$provide",
"$tgEventsProvider",
"$compileProvider",
"$translateProvider",
configure
])
module.run([
"$log",
"$rootScope",
"$tgAuth",
"$tgEvents",
"$tgAnalytics",
"$translate",
"$tgLocation",
"$tgNavUrls",
"tgAppMetaService",
"tgProjectService",
"tgLoader",
init
])