* 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.9.0-dev
@@ -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> |