diff --git a/CHANGELOG.md b/CHANGELOG.md index 159fcbae..31470c2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Ability to create single-line or multi-line custom fields. (thanks to [@artlepool](https://github.com/artlepool)) - Add custom videoconference system. - Make burndown chart collapsible at the backlog panel. +- Ability to choose a theme (thanks to [@astagi](https://github.com/astagi)) ### Misc - Improve performance: Show cropped images in timelines. diff --git a/app-loader/app-loader.coffee b/app-loader/app-loader.coffee index fba45384..02f5ad87 100644 --- a/app-loader/app-loader.coffee +++ b/app-loader/app-loader.coffee @@ -4,6 +4,8 @@ window.taigaConfig = { "eventsUrl": null, "debug": true, "defaultLanguage": "en", + "themes": ["taiga"], + "defaultTheme": "taiga", "publicRegisterEnabled": true, "feedbackEnabled": true, "privacyPolicyUrl": null, diff --git a/app/coffee/modules/auth.coffee b/app/coffee/modules/auth.coffee index 17c13a4a..55e7ea9a 100644 --- a/app/coffee/modules/auth.coffee +++ b/app/coffee/modules/auth.coffee @@ -37,9 +37,11 @@ class AuthService extends taiga.Service "$tgUrls", "$tgConfig", "$translate", - "tgCurrentUserService"] + "tgCurrentUserService", + "tgThemeService"] - constructor: (@rootscope, @storage, @model, @rs, @http, @urls, @config, @translate, @currentUserService) -> + constructor: (@rootscope, @storage, @model, @rs, @http, @urls, @config, @translate, @currentUserService, + @themeService) -> super() userModel = @.getUser() @.setUserdata(userModel) @@ -51,9 +53,12 @@ class AuthService extends taiga.Service else @.userData = null + _setTheme: -> + theme = @rootscope.user?.theme || @config.get("defaultTheme") || "taiga" + @themeService.use(theme) _setLocales: -> - lang = @rootscope.user.lang || @config.get("defaultLanguage") || "en" + lang = @rootscope.user?.lang || @config.get("defaultLanguage") || "en" @translate.preferredLanguage(lang) # Needed for calls to the api in the correct language @translate.use(lang) # Needed for change the interface in runtime @@ -66,6 +71,7 @@ class AuthService extends taiga.Service user = @model.make_model("users", userData) @rootscope.user = user @._setLocales() + @._setTheme() return user return null @@ -78,6 +84,7 @@ class AuthService extends taiga.Service @.setUserdata(user) @._setLocales() + @._setTheme() clear: -> @rootscope.auth = null @@ -117,9 +124,12 @@ class AuthService extends taiga.Service logout: -> @.removeToken() @.clear() - @currentUserService.removeUser() + @._setTheme() + @._setLocales() + + register: (data, type, existing) -> url = @urls.resolve("auth-register") diff --git a/app/coffee/modules/user-settings/main.coffee b/app/coffee/modules/user-settings/main.coffee index 151cc2dc..8d67c99e 100644 --- a/app/coffee/modules/user-settings/main.coffee +++ b/app/coffee/modules/user-settings/main.coffee @@ -57,6 +57,7 @@ class UserSettingsController extends mixOf(taiga.Controller, taiga.PageMixin) @location.replace() @scope.lang = @getLan() + @scope.theme = @getTheme() maxFileSize = @config.get("maxUploadFileSize", null) if maxFileSize @@ -68,6 +69,8 @@ class UserSettingsController extends mixOf(taiga.Controller, taiga.PageMixin) promise.then null, @.onInitialDataError.bind(@) loadInitialData: -> + @scope.availableThemes = @config.get("themes", []) + return @rs.locales.list().then (locales) => @scope.locales = locales return locales @@ -79,6 +82,11 @@ class UserSettingsController extends mixOf(taiga.Controller, taiga.PageMixin) return @scope.user.lang || @translate.preferredLanguage() + getTheme: -> + return @scope.user.theme || + @config.get("defaultTheme") || + "taiga" + module.controller("UserSettingsController", UserSettingsController) @@ -96,6 +104,7 @@ UserProfileDirective = ($confirm, $auth, $repo, $translate) -> changeEmail = $scope.user.isAttributeModified("email") $scope.user.lang = $scope.lang + $scope.user.theme = $scope.theme onSuccess = (data) => $auth.setUser(data) diff --git a/app/locales/locale-en.json b/app/locales/locale-en.json index 2a06e9de..b2ca0d4c 100644 --- a/app/locales/locale-en.json +++ b/app/locales/locale-en.json @@ -1193,7 +1193,9 @@ "BIO": "Bio (max. 210 chars)", "PLACEHOLDER_BIO": "Tell us something about you", "LANGUAGE": "Language", - "LANGUAGE_DEFAULT": "-- use default language --" + "LANGUAGE_DEFAULT": "-- use default language --", + "THEME": "Theme", + "THEME_DEFAULT": "-- use default theme --" } }, "WIZARD": { diff --git a/app/modules/services/theme.service.coffee b/app/modules/services/theme.service.coffee new file mode 100644 index 00000000..4e5733ce --- /dev/null +++ b/app/modules/services/theme.service.coffee @@ -0,0 +1,15 @@ +taiga = @.taiga + + +class ThemeService extends taiga.Service = -> + use: (themeName) -> + stylesheetEl = $("link[rel='stylesheet']") + + if stylesheetEl.length == 0 + stylesheetEl = $("") + $("head").append(stylesheetEl) + + stylesheetEl.attr("href", "/styles/theme-#{themeName}.css") + + +angular.module("taigaCommon").service("tgThemeService", ThemeService) diff --git a/app/modules/services/theme.service.spec.coffee b/app/modules/services/theme.service.spec.coffee new file mode 100644 index 00000000..17a208f0 --- /dev/null +++ b/app/modules/services/theme.service.spec.coffee @@ -0,0 +1,17 @@ +describe "ThemeService", -> + themeService = null + data = { + theme: "testTheme" + } + + _inject = () -> + inject (_tgThemeService_) -> + themeService = _tgThemeService_ + + beforeEach -> + module "taigaCommon" + _inject() + + it "use a test theme", () -> + themeService.use(data.theme) + expect($("link[rel='stylesheet']")).to.have.attr("href", "/styles/theme-#{data.theme}.css") diff --git a/app/partials/user/user-profile.jade b/app/partials/user/user-profile.jade index 872b6e79..dd94f565 100644 --- a/app/partials/user/user-profile.jade +++ b/app/partials/user/user-profile.jade @@ -30,7 +30,7 @@ div.wrapper(tg-user-profile, ng-controller="UserSettingsController as ctrl", div.data fieldset - label(for="email", translate="USER_PROFILE.FIELD.USERNAME") + label(for="username", translate="USER_PROFILE.FIELD.USERNAME") input(type="text", name="username", id="username", placeholder="{{'USER_PROFILE.FIELD.USERNAME' | translate}}", ng-model="user.username", data-required="true", data-maxlength="255", @@ -51,16 +51,23 @@ div.wrapper(tg-user-profile, ng-controller="UserSettingsController as ctrl", data-maxlength="256") fieldset - label(for="full-name", translate="USER_PROFILE.FIELD.LANGUAGE") - select(ng-model="lang", + label(for="lang", translate="USER_PROFILE.FIELD.LANGUAGE") + select(name="lang", id="lang", ng-model="lang", ng-options="locale.code as locale.name for locale in locales") option(value="", translate="USER_PROFILE.FIELD.LANGUAGE_DEFAULT") + fieldset + label(for="theme", translate="USER_PROFILE.FIELD.THEME") + select(name="theme", id="theme", ng-model="theme", + ng-options="availableTheme for availableTheme in availableThemes") + option(value="", translate="USER_PROFILE.FIELD.THEME_DEFAULT") + fieldset label(for="bio", translate="USER_PROFILE.FIELD.BIO") textarea(name="bio", id="bio", ng-model="user.bio", - ng-attr-placeholder="{{'USER_PROFILE.FIELD.PLACEHOLDER_BIO' | translate}}", ng-maxlength="210", maxlength="210") + ng-attr-placeholder="{{'USER_PROFILE.FIELD.PLACEHOLDER_BIO' | translate}}", + ng-maxlength="210", maxlength="210") fieldset.submit button.button-green.submit-button(type="submit", title="{{'COMMON.SAVE' | translate}}", diff --git a/conf/conf.example.json b/conf/conf.example.json index 2a78a5ea..d039bb77 100644 --- a/conf/conf.example.json +++ b/conf/conf.example.json @@ -4,6 +4,8 @@ "debug": true, "debugInfo": false, "defaultLanguage": "en", + "themes": ["taiga"], + "defaultTheme": "taiga", "publicRegisterEnabled": true, "feedbackEnabled": true, "privacyPolicyUrl": null,