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,
|
IsActive: true,
|
||||||
IsAdmin: c.Bool("admin"),
|
IsAdmin: c.Bool("admin"),
|
||||||
MustChangePassword: changePassword,
|
MustChangePassword: changePassword,
|
||||||
|
Theme: setting.UI.DefaultTheme,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return fmt.Errorf("CreateUser: %v", err)
|
return fmt.Errorf("CreateUser: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,8 @@ MAX_DISPLAY_FILE_SIZE = 8388608
|
||||||
SHOW_USER_EMAIL = true
|
SHOW_USER_EMAIL = true
|
||||||
; Set the default theme for the Gitea install
|
; Set the default theme for the Gitea install
|
||||||
DEFAULT_THEME = gitea
|
DEFAULT_THEME = gitea
|
||||||
|
; All available themes
|
||||||
|
THEMES = gitea,arc-green
|
||||||
|
|
||||||
[ui.admin]
|
[ui.admin]
|
||||||
; Number of users that are displayed on one page
|
; Number of users that are displayed on one page
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
"github.com/Unknwon/com"
|
"github.com/Unknwon/com"
|
||||||
"github.com/go-xorm/xorm"
|
"github.com/go-xorm/xorm"
|
||||||
gouuid "github.com/satori/go.uuid"
|
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/generate"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"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),
|
NewMigration("clear nonused data which not deleted when user was deleted", clearNonusedData),
|
||||||
// v76 -> v77
|
// v76 -> v77
|
||||||
NewMigration("add pull request rebase with merge commit", addPullRequestRebaseWithMerge),
|
NewMigration("add pull request rebase with merge commit", addPullRequestRebaseWithMerge),
|
||||||
|
// v77 -> v78
|
||||||
|
NewMigration("add theme to users", addUserDefaultTheme),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate database to current version
|
// 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
|
// Preferences
|
||||||
DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"`
|
DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"`
|
||||||
|
Theme string `xorm:"NOT NULL DEFAULT ''"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BeforeUpdate is invoked from XORM before updating this object.
|
// BeforeUpdate is invoked from XORM before updating this object.
|
||||||
|
@ -165,6 +166,13 @@ func (u *User) BeforeUpdate() {
|
||||||
u.Description = base.TruncateString(u.Description, 255)
|
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
|
// SetLastLogin set time to last login
|
||||||
func (u *User) SetLastLogin() {
|
func (u *User) SetLastLogin() {
|
||||||
u.LastLoginUnix = util.TimeStampNow()
|
u.LastLoginUnix = util.TimeStampNow()
|
||||||
|
@ -176,6 +184,12 @@ func (u *User) UpdateDiffViewStyle(style string) error {
|
||||||
return UpdateUserCols(u, "diff_view_style")
|
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
|
// getEmail returns an noreply email, if the user has set to keep his
|
||||||
// email address private, otherwise the primary email address.
|
// email address private, otherwise the primary email address.
|
||||||
func (u *User) getEmail() string {
|
func (u *User) getEmail() string {
|
||||||
|
@ -777,6 +791,7 @@ func CreateUser(u *User) (err error) {
|
||||||
u.HashPassword(u.Passwd)
|
u.HashPassword(u.Passwd)
|
||||||
u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization
|
u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization
|
||||||
u.MaxRepoCreation = -1
|
u.MaxRepoCreation = -1
|
||||||
|
u.Theme = setting.UI.DefaultTheme
|
||||||
|
|
||||||
if _, err = sess.Insert(u); err != nil {
|
if _, err = sess.Insert(u); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/go-macaron/binding"
|
"github.com/go-macaron/binding"
|
||||||
"gopkg.in/macaron.v1"
|
macaron "gopkg.in/macaron.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InstallForm form for installation page
|
// 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)
|
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
|
// ChangePasswordForm form for changing password
|
||||||
type ChangePasswordForm struct {
|
type ChangePasswordForm struct {
|
||||||
OldPassword string `form:"old_password" binding:"MaxSize(255)"`
|
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", ",")
|
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,한국어", ",")
|
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]", ",")
|
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"
|
||||||
_ "github.com/go-macaron/session/redis" // redis plugin for store session
|
_ "github.com/go-macaron/session/redis" // redis plugin for store session
|
||||||
"github.com/go-xorm/core"
|
"github.com/go-xorm/core"
|
||||||
"github.com/kballard/go-shellquote"
|
shellquote "github.com/kballard/go-shellquote"
|
||||||
"github.com/mcuadros/go-version"
|
version "github.com/mcuadros/go-version"
|
||||||
"gopkg.in/ini.v1"
|
ini "gopkg.in/ini.v1"
|
||||||
"strk.kbt.io/projects/go/libravatar"
|
"strk.kbt.io/projects/go/libravatar"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -303,6 +303,7 @@ var (
|
||||||
MaxDisplayFileSize int64
|
MaxDisplayFileSize int64
|
||||||
ShowUserEmail bool
|
ShowUserEmail bool
|
||||||
DefaultTheme string
|
DefaultTheme string
|
||||||
|
Themes []string
|
||||||
|
|
||||||
Admin struct {
|
Admin struct {
|
||||||
UserPagingNum int
|
UserPagingNum int
|
||||||
|
@ -329,6 +330,7 @@ var (
|
||||||
ThemeColorMetaTag: `#6cc644`,
|
ThemeColorMetaTag: `#6cc644`,
|
||||||
MaxDisplayFileSize: 8388608,
|
MaxDisplayFileSize: 8388608,
|
||||||
DefaultTheme: `gitea`,
|
DefaultTheme: `gitea`,
|
||||||
|
Themes: []string{`gitea`, `arc-green`},
|
||||||
Admin: struct {
|
Admin: struct {
|
||||||
UserPagingNum int
|
UserPagingNum int
|
||||||
RepoPagingNum int
|
RepoPagingNum int
|
||||||
|
|
|
@ -355,6 +355,7 @@ password_username_disabled = Non-local users are not allowed to change their use
|
||||||
full_name = Full Name
|
full_name = Full Name
|
||||||
website = Website
|
website = Website
|
||||||
location = Location
|
location = Location
|
||||||
|
update_theme = Update Theme
|
||||||
update_profile = Update Profile
|
update_profile = Update Profile
|
||||||
update_profile_success = Your profile has been updated.
|
update_profile_success = Your profile has been updated.
|
||||||
change_username = Your username has been changed.
|
change_username = Your username has been changed.
|
||||||
|
@ -362,6 +363,7 @@ change_username_prompt = Note: username changes also change your account URL.
|
||||||
continue = Continue
|
continue = Continue
|
||||||
cancel = Cancel
|
cancel = Cancel
|
||||||
language = Language
|
language = Language
|
||||||
|
ui = Theme
|
||||||
|
|
||||||
lookup_avatar_by_mail = Look Up Avatar by Email Address
|
lookup_avatar_by_mail = Look Up Avatar by Email Address
|
||||||
federated_avatar_lookup = Federated Avatar Lookup
|
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
|
emails = Email Addresses
|
||||||
manage_emails = Manage Email Addresses
|
manage_emails = Manage Email Addresses
|
||||||
|
manage_themes = Select default theme
|
||||||
manage_openid = Manage OpenID Addresses
|
manage_openid = Manage OpenID Addresses
|
||||||
email_desc = Your primary email address will be used for notifications and other operations.
|
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 = Primary
|
||||||
primary_email = Make Primary
|
primary_email = Make Primary
|
||||||
delete_email = Remove
|
delete_email = Remove
|
||||||
email_deletion = Remove Email Address
|
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_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.
|
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 = Remove OpenID Address
|
||||||
openid_deletion_desc = Removing this OpenID address from your account will prevent you from signing in with it. Continue?
|
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.
|
openid_deletion_success = The OpenID address has been removed.
|
||||||
|
|
|
@ -42,7 +42,7 @@ import (
|
||||||
"github.com/go-macaron/toolbox"
|
"github.com/go-macaron/toolbox"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/tstranex/u2f"
|
"github.com/tstranex/u2f"
|
||||||
"gopkg.in/macaron.v1"
|
macaron "gopkg.in/macaron.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewMacaron initializes Macaron instance.
|
// NewMacaron initializes Macaron instance.
|
||||||
|
@ -243,6 +243,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||||
m.Post("/email", bindIgnErr(auth.AddEmailForm{}), userSetting.EmailPost)
|
m.Post("/email", bindIgnErr(auth.AddEmailForm{}), userSetting.EmailPost)
|
||||||
m.Post("/email/delete", userSetting.DeleteEmail)
|
m.Post("/email/delete", userSetting.DeleteEmail)
|
||||||
m.Post("/delete", userSetting.DeleteAccount)
|
m.Post("/delete", userSetting.DeleteAccount)
|
||||||
|
m.Post("/theme", bindIgnErr(auth.UpdateThemeForm{}), userSetting.UpdateUIThemePost)
|
||||||
})
|
})
|
||||||
m.Group("/security", func() {
|
m.Group("/security", func() {
|
||||||
m.Get("", userSetting.Security)
|
m.Get("", userSetting.Security)
|
||||||
|
@ -292,6 +293,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||||
})
|
})
|
||||||
}, reqSignIn, func(ctx *context.Context) {
|
}, reqSignIn, func(ctx *context.Context) {
|
||||||
ctx.Data["PageIsUserSettings"] = true
|
ctx.Data["PageIsUserSettings"] = true
|
||||||
|
ctx.Data["AllThemes"] = setting.UI.Themes
|
||||||
})
|
})
|
||||||
|
|
||||||
m.Group("/user", func() {
|
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) {
|
func loadAccountData(ctx *context.Context) {
|
||||||
emails, err := models.GetEmailAddresses(ctx.User.ID)
|
emails, err := models.GetEmailAddresses(ctx.User.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -147,7 +147,11 @@
|
||||||
<meta property="og:url" content="{{AppUrl}}" />
|
<meta property="og:url" content="{{AppUrl}}" />
|
||||||
<meta property="og:description" content="{{MetaDescription}}">
|
<meta property="og:description" content="{{MetaDescription}}">
|
||||||
{{end}}
|
{{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">
|
<link rel="stylesheet" href="{{AppSubUrl}}/css/theme-{{DefaultTheme}}.css">
|
||||||
{{end}}
|
{{end}}
|
||||||
{{template "custom/header" .}}
|
{{template "custom/header" .}}
|
||||||
|
|
|
@ -32,8 +32,12 @@ var urlsToCache = [
|
||||||
'{{AppSubUrl}}/vendor/plugins/jquery.minicolors/jquery.minicolors.css',
|
'{{AppSubUrl}}/vendor/plugins/jquery.minicolors/jquery.minicolors.css',
|
||||||
'{{AppSubUrl}}/vendor/plugins/jquery.datetimepicker/jquery.datetimepicker.css',
|
'{{AppSubUrl}}/vendor/plugins/jquery.datetimepicker/jquery.datetimepicker.css',
|
||||||
'{{AppSubUrl}}/vendor/plugins/dropzone/dropzone.css',
|
'{{AppSubUrl}}/vendor/plugins/dropzone/dropzone.css',
|
||||||
{{if ne DefaultTheme "gitea"}}
|
{{if .IsSigned }}
|
||||||
'{{AppSubUrl}}/css/theme-{{DefaultTheme}}.css',
|
{{ if ne .SignedUser.Theme "gitea" }}
|
||||||
|
'{{AppSubUrl}}/css/theme-{{.SignedUser.Theme}}.css'
|
||||||
|
{{end}}
|
||||||
|
{{else if ne DefaultTheme "gitea"}}
|
||||||
|
'{{AppSubUrl}}/css/theme-{{DefaultTheme}}.css'
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
// img
|
// img
|
||||||
|
|
|
@ -85,6 +85,44 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</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">
|
<h4 class="ui top attached warning header">
|
||||||
{{.i18n.Tr "settings.delete_account"}}
|
{{.i18n.Tr "settings.delete_account"}}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
Reference in a new issue