* add migration and basic UI for changing a user's theme * update user themem * use right text on button * load theme based on users' selection * load theme based on users' selection in pwa too * update sample config * delete older theme loading * implement AfterLoad to set users' theme properly * set up default theme when creating a user. This uses the installation wide theme * use flash messages for error * set default theme when creating a user from the cli * fix @lunny reviewtags/v1.21.12.1
| @@ -340,6 +340,7 @@ func runCreateUser(c *cli.Context) error { | |||
| IsActive: true, | |||
| IsAdmin: c.Bool("admin"), | |||
| MustChangePassword: changePassword, | |||
| Theme: setting.UI.DefaultTheme, | |||
| }); err != nil { | |||
| return fmt.Errorf("CreateUser: %v", err) | |||
| } | |||
| @@ -85,6 +85,8 @@ MAX_DISPLAY_FILE_SIZE = 8388608 | |||
| SHOW_USER_EMAIL = true | |||
| ; Set the default theme for the Gitea install | |||
| DEFAULT_THEME = gitea | |||
| ; All available themes | |||
| THEMES = gitea,arc-green | |||
| [ui.admin] | |||
| ; Number of users that are displayed on one page | |||
| @@ -18,7 +18,7 @@ import ( | |||
| "github.com/Unknwon/com" | |||
| "github.com/go-xorm/xorm" | |||
| gouuid "github.com/satori/go.uuid" | |||
| "gopkg.in/ini.v1" | |||
| ini "gopkg.in/ini.v1" | |||
| "code.gitea.io/gitea/modules/generate" | |||
| "code.gitea.io/gitea/modules/log" | |||
| @@ -206,6 +206,8 @@ var migrations = []Migration{ | |||
| NewMigration("clear nonused data which not deleted when user was deleted", clearNonusedData), | |||
| // v76 -> v77 | |||
| NewMigration("add pull request rebase with merge commit", addPullRequestRebaseWithMerge), | |||
| // v77 -> v78 | |||
| NewMigration("add theme to users", addUserDefaultTheme), | |||
| } | |||
| // Migrate database to current version | |||
| @@ -0,0 +1,17 @@ | |||
| // Copyright 2019 The Gitea Authors. All rights reserved. | |||
| // Use of this source code is governed by a MIT-style | |||
| // license that can be found in the LICENSE file. | |||
| package migrations | |||
| import ( | |||
| "github.com/go-xorm/xorm" | |||
| ) | |||
| func addUserDefaultTheme(x *xorm.Engine) error { | |||
| type User struct { | |||
| Theme string `xorm:"VARCHAR(30)"` | |||
| } | |||
| return x.Sync2(new(User)) | |||
| } | |||
| @@ -140,6 +140,7 @@ type User struct { | |||
| // Preferences | |||
| DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"` | |||
| Theme string `xorm:"NOT NULL DEFAULT ''"` | |||
| } | |||
| // BeforeUpdate is invoked from XORM before updating this object. | |||
| @@ -165,6 +166,13 @@ func (u *User) BeforeUpdate() { | |||
| u.Description = base.TruncateString(u.Description, 255) | |||
| } | |||
| // AfterLoad is invoked from XORM after filling all the fields of this object. | |||
| func (u *User) AfterLoad() { | |||
| if u.Theme == "" { | |||
| u.Theme = setting.UI.DefaultTheme | |||
| } | |||
| } | |||
| // SetLastLogin set time to last login | |||
| func (u *User) SetLastLogin() { | |||
| u.LastLoginUnix = util.TimeStampNow() | |||
| @@ -176,6 +184,12 @@ func (u *User) UpdateDiffViewStyle(style string) error { | |||
| return UpdateUserCols(u, "diff_view_style") | |||
| } | |||
| // UpdateTheme updates a users' theme irrespective of the site wide theme | |||
| func (u *User) UpdateTheme(themeName string) error { | |||
| u.Theme = themeName | |||
| return UpdateUserCols(u, "theme") | |||
| } | |||
| // getEmail returns an noreply email, if the user has set to keep his | |||
| // email address private, otherwise the primary email address. | |||
| func (u *User) getEmail() string { | |||
| @@ -777,6 +791,7 @@ func CreateUser(u *User) (err error) { | |||
| u.HashPassword(u.Passwd) | |||
| u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization | |||
| u.MaxRepoCreation = -1 | |||
| u.Theme = setting.UI.DefaultTheme | |||
| if _, err = sess.Insert(u); err != nil { | |||
| return err | |||
| @@ -12,7 +12,7 @@ import ( | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "github.com/go-macaron/binding" | |||
| "gopkg.in/macaron.v1" | |||
| macaron "gopkg.in/macaron.v1" | |||
| ) | |||
| // InstallForm form for installation page | |||
| @@ -189,6 +189,30 @@ func (f *AddEmailForm) Validate(ctx *macaron.Context, errs binding.Errors) bindi | |||
| return validate(errs, ctx.Data, f, ctx.Locale) | |||
| } | |||
| // UpdateThemeForm form for updating a users' theme | |||
| type UpdateThemeForm struct { | |||
| Theme string `binding:"Required;MaxSize(30)"` | |||
| } | |||
| // Validate validates the field | |||
| func (f *UpdateThemeForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | |||
| return validate(errs, ctx.Data, f, ctx.Locale) | |||
| } | |||
| // IsThemeExists checks if the theme is a theme available in the config. | |||
| func (f UpdateThemeForm) IsThemeExists() bool { | |||
| var exists bool | |||
| for _, v := range setting.UI.Themes { | |||
| if strings.ToLower(v) == strings.ToLower(f.Theme) { | |||
| exists = true | |||
| break | |||
| } | |||
| } | |||
| return exists | |||
| } | |||
| // ChangePasswordForm form for changing password | |||
| type ChangePasswordForm struct { | |||
| OldPassword string `form:"old_password" binding:"MaxSize(255)"` | |||
| @@ -8,4 +8,5 @@ var ( | |||
| defaultLangs = strings.Split("en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,uk-UA,ja-JP,es-ES,pt-BR,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR", ",") | |||
| defaultLangNames = strings.Split("English,简体中文,繁體中文(香港),繁體中文(台灣),Deutsch,français,Nederlands,latviešu,русский,Українська,日本語,español,português do Brasil,polski,български,italiano,suomi,Türkçe,čeština,српски,svenska,한국어", ",") | |||
| defaultPullRequestWorkInProgressPrefixes = strings.Split("WIP:,[WIP]", ",") | |||
| defaultThemes = strings.Split("gitea", "arc-green") | |||
| ) | |||
| @@ -33,9 +33,9 @@ import ( | |||
| "github.com/go-macaron/session" | |||
| _ "github.com/go-macaron/session/redis" // redis plugin for store session | |||
| "github.com/go-xorm/core" | |||
| "github.com/kballard/go-shellquote" | |||
| "github.com/mcuadros/go-version" | |||
| "gopkg.in/ini.v1" | |||
| shellquote "github.com/kballard/go-shellquote" | |||
| version "github.com/mcuadros/go-version" | |||
| ini "gopkg.in/ini.v1" | |||
| "strk.kbt.io/projects/go/libravatar" | |||
| ) | |||
| @@ -303,6 +303,7 @@ var ( | |||
| MaxDisplayFileSize int64 | |||
| ShowUserEmail bool | |||
| DefaultTheme string | |||
| Themes []string | |||
| Admin struct { | |||
| UserPagingNum int | |||
| @@ -329,6 +330,7 @@ var ( | |||
| ThemeColorMetaTag: `#6cc644`, | |||
| MaxDisplayFileSize: 8388608, | |||
| DefaultTheme: `gitea`, | |||
| Themes: []string{`gitea`, `arc-green`}, | |||
| Admin: struct { | |||
| UserPagingNum int | |||
| RepoPagingNum int | |||
| @@ -355,6 +355,7 @@ password_username_disabled = Non-local users are not allowed to change their use | |||
| full_name = Full Name | |||
| website = Website | |||
| location = Location | |||
| update_theme = Update Theme | |||
| update_profile = Update Profile | |||
| update_profile_success = Your profile has been updated. | |||
| change_username = Your username has been changed. | |||
| @@ -362,6 +363,7 @@ change_username_prompt = Note: username changes also change your account URL. | |||
| continue = Continue | |||
| cancel = Cancel | |||
| language = Language | |||
| ui = Theme | |||
| lookup_avatar_by_mail = Look Up Avatar by Email Address | |||
| federated_avatar_lookup = Federated Avatar Lookup | |||
| @@ -382,14 +384,18 @@ password_change_disabled = Non-local users can not update their password through | |||
| emails = Email Addresses | |||
| manage_emails = Manage Email Addresses | |||
| manage_themes = Select default theme | |||
| manage_openid = Manage OpenID Addresses | |||
| email_desc = Your primary email address will be used for notifications and other operations. | |||
| theme_desc = This will be your default theme across the site. | |||
| primary = Primary | |||
| primary_email = Make Primary | |||
| delete_email = Remove | |||
| email_deletion = Remove Email Address | |||
| email_deletion_desc = The email address and related information will be removed from your account. Git commits by this email address will remain unchanged. Continue? | |||
| email_deletion_success = The email address has been removed. | |||
| theme_update_success = Your theme was updated. | |||
| theme_update_error = The selected theme does not exist. | |||
| openid_deletion = Remove OpenID Address | |||
| openid_deletion_desc = Removing this OpenID address from your account will prevent you from signing in with it. Continue? | |||
| openid_deletion_success = The OpenID address has been removed. | |||
| @@ -42,7 +42,7 @@ import ( | |||
| "github.com/go-macaron/toolbox" | |||
| "github.com/prometheus/client_golang/prometheus" | |||
| "github.com/tstranex/u2f" | |||
| "gopkg.in/macaron.v1" | |||
| macaron "gopkg.in/macaron.v1" | |||
| ) | |||
| // NewMacaron initializes Macaron instance. | |||
| @@ -243,6 +243,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Post("/email", bindIgnErr(auth.AddEmailForm{}), userSetting.EmailPost) | |||
| m.Post("/email/delete", userSetting.DeleteEmail) | |||
| m.Post("/delete", userSetting.DeleteAccount) | |||
| m.Post("/theme", bindIgnErr(auth.UpdateThemeForm{}), userSetting.UpdateUIThemePost) | |||
| }) | |||
| m.Group("/security", func() { | |||
| m.Get("", userSetting.Security) | |||
| @@ -292,6 +293,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| }) | |||
| }, reqSignIn, func(ctx *context.Context) { | |||
| ctx.Data["PageIsUserSettings"] = true | |||
| ctx.Data["AllThemes"] = setting.UI.Themes | |||
| }) | |||
| m.Group("/user", func() { | |||
| @@ -168,6 +168,34 @@ func DeleteAccount(ctx *context.Context) { | |||
| } | |||
| } | |||
| // UpdateUIThemePost is used to update users' specific theme | |||
| func UpdateUIThemePost(ctx *context.Context, form auth.UpdateThemeForm) { | |||
| ctx.Data["Title"] = ctx.Tr("settings") | |||
| ctx.Data["PageIsSettingsAccount"] = true | |||
| if ctx.HasError() { | |||
| ctx.Redirect(setting.AppSubURL + "/user/settings/account") | |||
| return | |||
| } | |||
| if !form.IsThemeExists() { | |||
| ctx.Flash.Error(ctx.Tr("settings.theme_update_error")) | |||
| ctx.Redirect(setting.AppSubURL + "/user/settings/account") | |||
| return | |||
| } | |||
| if err := ctx.User.UpdateTheme(form.Theme); err != nil { | |||
| ctx.Flash.Error(ctx.Tr("settings.theme_update_error")) | |||
| ctx.Redirect(setting.AppSubURL + "/user/settings/account") | |||
| return | |||
| } | |||
| log.Trace("Update user theme: %s", ctx.User.Name) | |||
| ctx.Flash.Success(ctx.Tr("settings.theme_update_success")) | |||
| ctx.Redirect(setting.AppSubURL + "/user/settings/account") | |||
| } | |||
| func loadAccountData(ctx *context.Context) { | |||
| emails, err := models.GetEmailAddresses(ctx.User.ID) | |||
| if err != nil { | |||
| @@ -6,7 +6,7 @@ | |||
| <meta http-equiv="x-ua-compatible" content="ie=edge"> | |||
| <title>{{if .Title}}{{.Title}} - {{end}}{{AppName}}</title> | |||
| <link rel="manifest" href="{{AppSubUrl}}/manifest.json"> | |||
| <script> | |||
| if ('serviceWorker' in navigator) { | |||
| window.addEventListener('load', function() { | |||
| @@ -147,7 +147,11 @@ | |||
| <meta property="og:url" content="{{AppUrl}}" /> | |||
| <meta property="og:description" content="{{MetaDescription}}"> | |||
| {{end}} | |||
| {{if ne DefaultTheme "gitea"}} | |||
| {{if .IsSigned }} | |||
| {{ if ne .SignedUser.Theme "gitea" }} | |||
| <link rel="stylesheet" href="{{AppSubUrl}}/css/theme-{{.SignedUser.Theme}}.css"> | |||
| {{end}} | |||
| {{else if ne DefaultTheme "gitea"}} | |||
| <link rel="stylesheet" href="{{AppSubUrl}}/css/theme-{{DefaultTheme}}.css"> | |||
| {{end}} | |||
| {{template "custom/header" .}} | |||
| @@ -32,10 +32,14 @@ var urlsToCache = [ | |||
| '{{AppSubUrl}}/vendor/plugins/jquery.minicolors/jquery.minicolors.css', | |||
| '{{AppSubUrl}}/vendor/plugins/jquery.datetimepicker/jquery.datetimepicker.css', | |||
| '{{AppSubUrl}}/vendor/plugins/dropzone/dropzone.css', | |||
| {{if ne DefaultTheme "gitea"}} | |||
| '{{AppSubUrl}}/css/theme-{{DefaultTheme}}.css', | |||
| {{if .IsSigned }} | |||
| {{ if ne .SignedUser.Theme "gitea" }} | |||
| '{{AppSubUrl}}/css/theme-{{.SignedUser.Theme}}.css' | |||
| {{end}} | |||
| {{else if ne DefaultTheme "gitea"}} | |||
| '{{AppSubUrl}}/css/theme-{{DefaultTheme}}.css' | |||
| {{end}} | |||
| // img | |||
| '{{AppSubUrl}}/img/gitea-sm.png', | |||
| '{{AppSubUrl}}/img/gitea-lg.png', | |||
| @@ -85,6 +85,44 @@ | |||
| </form> | |||
| </div> | |||
| <h4 class="ui top attached header"> | |||
| {{.i18n.Tr "settings.manage_themes"}} | |||
| </h4> | |||
| <div class="ui attached segment"> | |||
| <div class="ui email list"> | |||
| <div class="item"> | |||
| {{.i18n.Tr "settings.theme_desc"}} | |||
| </div> | |||
| <form class="ui form" action="{{.Link}}/theme" method="post"> | |||
| {{.CsrfTokenHtml}} | |||
| <div class="field"> | |||
| <label for="ui">{{.i18n.Tr "settings.ui"}}</label> | |||
| <div class="ui selection dropdown" id="ui"> | |||
| <input name="theme" type="hidden" value="{{.SignedUser.Theme}}"> | |||
| <i class="dropdown icon"></i> | |||
| <div class="text"> | |||
| {{range $i,$a := .AllThemes}} | |||
| {{if eq $.SignedUser.Theme $a}}{{$a}}{{end}} | |||
| {{end}} | |||
| </div> | |||
| <div class="menu"> | |||
| {{range $i,$a := .AllThemes}} | |||
| <div class="item{{if eq $.SignedUser.Theme $a}} active selected{{end}}" data-value="{{$a}}"> | |||
| {{$a}} | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="field"> | |||
| <button class="ui green button">{{$.i18n.Tr "settings.update_theme"}}</button> | |||
| </div> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| <h4 class="ui top attached warning header"> | |||
| {{.i18n.Tr "settings.delete_account"}} | |||
| </h4> | |||