Skip to content

Commit 8d2c24f

Browse files
adelowotechknowlogick
authored andcommitted
Allow for user specific themes (#5668)
* 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 review
1 parent ea51868 commit 8d2c24f

File tree

14 files changed

+157
-11
lines changed

14 files changed

+157
-11
lines changed

cmd/admin.go

+1
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ func runCreateUser(c *cli.Context) error {
340340
IsActive: true,
341341
IsAdmin: c.Bool("admin"),
342342
MustChangePassword: changePassword,
343+
Theme: setting.UI.DefaultTheme,
343344
}); err != nil {
344345
return fmt.Errorf("CreateUser: %v", err)
345346
}

custom/conf/app.ini.sample

+2
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ MAX_DISPLAY_FILE_SIZE = 8388608
8585
SHOW_USER_EMAIL = true
8686
; Set the default theme for the Gitea install
8787
DEFAULT_THEME = gitea
88+
; All available themes
89+
THEMES = gitea,arc-green
8890

8991
[ui.admin]
9092
; Number of users that are displayed on one page

models/migrations/migrations.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
"github.com/Unknwon/com"
1919
"github.com/go-xorm/xorm"
2020
gouuid "github.com/satori/go.uuid"
21-
"gopkg.in/ini.v1"
21+
ini "gopkg.in/ini.v1"
2222

2323
"code.gitea.io/gitea/modules/generate"
2424
"code.gitea.io/gitea/modules/log"
@@ -206,6 +206,8 @@ var migrations = []Migration{
206206
NewMigration("clear nonused data which not deleted when user was deleted", clearNonusedData),
207207
// v76 -> v77
208208
NewMigration("add pull request rebase with merge commit", addPullRequestRebaseWithMerge),
209+
// v77 -> v78
210+
NewMigration("add theme to users", addUserDefaultTheme),
209211
}
210212

211213
// Migrate database to current version

models/migrations/v77.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2019 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
"github.com/go-xorm/xorm"
9+
)
10+
11+
func addUserDefaultTheme(x *xorm.Engine) error {
12+
type User struct {
13+
Theme string `xorm:"VARCHAR(30)"`
14+
}
15+
16+
return x.Sync2(new(User))
17+
}

models/user.go

+15
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ type User struct {
140140

141141
// Preferences
142142
DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"`
143+
Theme string `xorm:"NOT NULL DEFAULT ''"`
143144
}
144145

145146
// BeforeUpdate is invoked from XORM before updating this object.
@@ -165,6 +166,13 @@ func (u *User) BeforeUpdate() {
165166
u.Description = base.TruncateString(u.Description, 255)
166167
}
167168

169+
// AfterLoad is invoked from XORM after filling all the fields of this object.
170+
func (u *User) AfterLoad() {
171+
if u.Theme == "" {
172+
u.Theme = setting.UI.DefaultTheme
173+
}
174+
}
175+
168176
// SetLastLogin set time to last login
169177
func (u *User) SetLastLogin() {
170178
u.LastLoginUnix = util.TimeStampNow()
@@ -176,6 +184,12 @@ func (u *User) UpdateDiffViewStyle(style string) error {
176184
return UpdateUserCols(u, "diff_view_style")
177185
}
178186

187+
// UpdateTheme updates a users' theme irrespective of the site wide theme
188+
func (u *User) UpdateTheme(themeName string) error {
189+
u.Theme = themeName
190+
return UpdateUserCols(u, "theme")
191+
}
192+
179193
// getEmail returns an noreply email, if the user has set to keep his
180194
// email address private, otherwise the primary email address.
181195
func (u *User) getEmail() string {
@@ -777,6 +791,7 @@ func CreateUser(u *User) (err error) {
777791
u.HashPassword(u.Passwd)
778792
u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization
779793
u.MaxRepoCreation = -1
794+
u.Theme = setting.UI.DefaultTheme
780795

781796
if _, err = sess.Insert(u); err != nil {
782797
return err

modules/auth/user_form.go

+25-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"code.gitea.io/gitea/modules/setting"
1313

1414
"github.com/go-macaron/binding"
15-
"gopkg.in/macaron.v1"
15+
macaron "gopkg.in/macaron.v1"
1616
)
1717

1818
// InstallForm form for installation page
@@ -189,6 +189,30 @@ func (f *AddEmailForm) Validate(ctx *macaron.Context, errs binding.Errors) bindi
189189
return validate(errs, ctx.Data, f, ctx.Locale)
190190
}
191191

192+
// UpdateThemeForm form for updating a users' theme
193+
type UpdateThemeForm struct {
194+
Theme string `binding:"Required;MaxSize(30)"`
195+
}
196+
197+
// Validate validates the field
198+
func (f *UpdateThemeForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
199+
return validate(errs, ctx.Data, f, ctx.Locale)
200+
}
201+
202+
// IsThemeExists checks if the theme is a theme available in the config.
203+
func (f UpdateThemeForm) IsThemeExists() bool {
204+
var exists bool
205+
206+
for _, v := range setting.UI.Themes {
207+
if strings.ToLower(v) == strings.ToLower(f.Theme) {
208+
exists = true
209+
break
210+
}
211+
}
212+
213+
return exists
214+
}
215+
192216
// ChangePasswordForm form for changing password
193217
type ChangePasswordForm struct {
194218
OldPassword string `form:"old_password" binding:"MaxSize(255)"`

modules/setting/defaults.go

+1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ var (
88
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", ",")
99
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,한국어", ",")
1010
defaultPullRequestWorkInProgressPrefixes = strings.Split("WIP:,[WIP]", ",")
11+
defaultThemes = strings.Split("gitea", "arc-green")
1112
)

modules/setting/setting.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ import (
3333
"github.com/go-macaron/session"
3434
_ "github.com/go-macaron/session/redis" // redis plugin for store session
3535
"github.com/go-xorm/core"
36-
"github.com/kballard/go-shellquote"
37-
"github.com/mcuadros/go-version"
38-
"gopkg.in/ini.v1"
36+
shellquote "github.com/kballard/go-shellquote"
37+
version "github.com/mcuadros/go-version"
38+
ini "gopkg.in/ini.v1"
3939
"strk.kbt.io/projects/go/libravatar"
4040
)
4141

@@ -303,6 +303,7 @@ var (
303303
MaxDisplayFileSize int64
304304
ShowUserEmail bool
305305
DefaultTheme string
306+
Themes []string
306307

307308
Admin struct {
308309
UserPagingNum int
@@ -329,6 +330,7 @@ var (
329330
ThemeColorMetaTag: `#6cc644`,
330331
MaxDisplayFileSize: 8388608,
331332
DefaultTheme: `gitea`,
333+
Themes: []string{`gitea`, `arc-green`},
332334
Admin: struct {
333335
UserPagingNum int
334336
RepoPagingNum int

options/locale/locale_en-US.ini

+6
Original file line numberDiff line numberDiff line change
@@ -355,13 +355,15 @@ password_username_disabled = Non-local users are not allowed to change their use
355355
full_name = Full Name
356356
website = Website
357357
location = Location
358+
update_theme = Update Theme
358359
update_profile = Update Profile
359360
update_profile_success = Your profile has been updated.
360361
change_username = Your username has been changed.
361362
change_username_prompt = Note: username changes also change your account URL.
362363
continue = Continue
363364
cancel = Cancel
364365
language = Language
366+
ui = Theme
365367
366368
lookup_avatar_by_mail = Look Up Avatar by Email Address
367369
federated_avatar_lookup = Federated Avatar Lookup
@@ -382,14 +384,18 @@ password_change_disabled = Non-local users can not update their password through
382384
383385
emails = Email Addresses
384386
manage_emails = Manage Email Addresses
387+
manage_themes = Select default theme
385388
manage_openid = Manage OpenID Addresses
386389
email_desc = Your primary email address will be used for notifications and other operations.
390+
theme_desc = This will be your default theme across the site.
387391
primary = Primary
388392
primary_email = Make Primary
389393
delete_email = Remove
390394
email_deletion = Remove Email Address
391395
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?
392396
email_deletion_success = The email address has been removed.
397+
theme_update_success = Your theme was updated.
398+
theme_update_error = The selected theme does not exist.
393399
openid_deletion = Remove OpenID Address
394400
openid_deletion_desc = Removing this OpenID address from your account will prevent you from signing in with it. Continue?
395401
openid_deletion_success = The OpenID address has been removed.

routers/routes/routes.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import (
4242
"github.com/go-macaron/toolbox"
4343
"github.com/prometheus/client_golang/prometheus"
4444
"github.com/tstranex/u2f"
45-
"gopkg.in/macaron.v1"
45+
macaron "gopkg.in/macaron.v1"
4646
)
4747

4848
// NewMacaron initializes Macaron instance.
@@ -243,6 +243,7 @@ func RegisterRoutes(m *macaron.Macaron) {
243243
m.Post("/email", bindIgnErr(auth.AddEmailForm{}), userSetting.EmailPost)
244244
m.Post("/email/delete", userSetting.DeleteEmail)
245245
m.Post("/delete", userSetting.DeleteAccount)
246+
m.Post("/theme", bindIgnErr(auth.UpdateThemeForm{}), userSetting.UpdateUIThemePost)
246247
})
247248
m.Group("/security", func() {
248249
m.Get("", userSetting.Security)
@@ -292,6 +293,7 @@ func RegisterRoutes(m *macaron.Macaron) {
292293
})
293294
}, reqSignIn, func(ctx *context.Context) {
294295
ctx.Data["PageIsUserSettings"] = true
296+
ctx.Data["AllThemes"] = setting.UI.Themes
295297
})
296298

297299
m.Group("/user", func() {

routers/user/setting/account.go

+28
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,34 @@ func DeleteAccount(ctx *context.Context) {
168168
}
169169
}
170170

171+
// UpdateUIThemePost is used to update users' specific theme
172+
func UpdateUIThemePost(ctx *context.Context, form auth.UpdateThemeForm) {
173+
174+
ctx.Data["Title"] = ctx.Tr("settings")
175+
ctx.Data["PageIsSettingsAccount"] = true
176+
177+
if ctx.HasError() {
178+
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
179+
return
180+
}
181+
182+
if !form.IsThemeExists() {
183+
ctx.Flash.Error(ctx.Tr("settings.theme_update_error"))
184+
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
185+
return
186+
}
187+
188+
if err := ctx.User.UpdateTheme(form.Theme); err != nil {
189+
ctx.Flash.Error(ctx.Tr("settings.theme_update_error"))
190+
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
191+
return
192+
}
193+
194+
log.Trace("Update user theme: %s", ctx.User.Name)
195+
ctx.Flash.Success(ctx.Tr("settings.theme_update_success"))
196+
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
197+
}
198+
171199
func loadAccountData(ctx *context.Context) {
172200
emails, err := models.GetEmailAddresses(ctx.User.ID)
173201
if err != nil {

templates/base/head.tmpl

+6-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<meta http-equiv="x-ua-compatible" content="ie=edge">
77
<title>{{if .Title}}{{.Title}} - {{end}}{{AppName}}</title>
88
<link rel="manifest" href="{{AppSubUrl}}/manifest.json">
9-
9+
1010
<script>
1111
if ('serviceWorker' in navigator) {
1212
window.addEventListener('load', function() {
@@ -147,7 +147,11 @@
147147
<meta property="og:url" content="{{AppUrl}}" />
148148
<meta property="og:description" content="{{MetaDescription}}">
149149
{{end}}
150-
{{if ne DefaultTheme "gitea"}}
150+
{{if .IsSigned }}
151+
{{ if ne .SignedUser.Theme "gitea" }}
152+
<link rel="stylesheet" href="{{AppSubUrl}}/css/theme-{{.SignedUser.Theme}}.css">
153+
{{end}}
154+
{{else if ne DefaultTheme "gitea"}}
151155
<link rel="stylesheet" href="{{AppSubUrl}}/css/theme-{{DefaultTheme}}.css">
152156
{{end}}
153157
{{template "custom/header" .}}

templates/pwa/serviceworker_js.tmpl

+7-3
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,14 @@ var urlsToCache = [
3232
'{{AppSubUrl}}/vendor/plugins/jquery.minicolors/jquery.minicolors.css',
3333
'{{AppSubUrl}}/vendor/plugins/jquery.datetimepicker/jquery.datetimepicker.css',
3434
'{{AppSubUrl}}/vendor/plugins/dropzone/dropzone.css',
35-
{{if ne DefaultTheme "gitea"}}
36-
'{{AppSubUrl}}/css/theme-{{DefaultTheme}}.css',
35+
{{if .IsSigned }}
36+
{{ if ne .SignedUser.Theme "gitea" }}
37+
'{{AppSubUrl}}/css/theme-{{.SignedUser.Theme}}.css'
38+
{{end}}
39+
{{else if ne DefaultTheme "gitea"}}
40+
'{{AppSubUrl}}/css/theme-{{DefaultTheme}}.css'
3741
{{end}}
38-
42+
3943
// img
4044
'{{AppSubUrl}}/img/gitea-sm.png',
4145
'{{AppSubUrl}}/img/gitea-lg.png',

templates/user/settings/account.tmpl

+38
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,44 @@
8585
</form>
8686
</div>
8787

88+
<h4 class="ui top attached header">
89+
{{.i18n.Tr "settings.manage_themes"}}
90+
</h4>
91+
<div class="ui attached segment">
92+
<div class="ui email list">
93+
<div class="item">
94+
{{.i18n.Tr "settings.theme_desc"}}
95+
</div>
96+
97+
<form class="ui form" action="{{.Link}}/theme" method="post">
98+
{{.CsrfTokenHtml}}
99+
<div class="field">
100+
<label for="ui">{{.i18n.Tr "settings.ui"}}</label>
101+
<div class="ui selection dropdown" id="ui">
102+
<input name="theme" type="hidden" value="{{.SignedUser.Theme}}">
103+
<i class="dropdown icon"></i>
104+
<div class="text">
105+
{{range $i,$a := .AllThemes}}
106+
{{if eq $.SignedUser.Theme $a}}{{$a}}{{end}}
107+
{{end}}
108+
</div>
109+
110+
<div class="menu">
111+
{{range $i,$a := .AllThemes}}
112+
<div class="item{{if eq $.SignedUser.Theme $a}} active selected{{end}}" data-value="{{$a}}">
113+
{{$a}}
114+
</div>
115+
{{end}}
116+
</div>
117+
</div>
118+
</div>
119+
120+
<div class="field">
121+
<button class="ui green button">{{$.i18n.Tr "settings.update_theme"}}</button>
122+
</div>
123+
</form>
124+
</div>
125+
</div>
88126
<h4 class="ui top attached warning header">
89127
{{.i18n.Tr "settings.delete_account"}}
90128
</h4>

0 commit comments

Comments
 (0)