mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-08-29 22:06:06 +02:00
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
This commit is contained in:
parent
ea518681d9
commit
8d2c24f7f9
14 changed files with 157 additions and 11 deletions
|
@ -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
|
||||
|
|
17
models/migrations/v77.go
Normal file
17
models/migrations/v77.go
Normal file
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue