mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-20 20:07:03 +01:00
Add appearance section in settings (#17433)
* Add appearance section in settings * Fix lint * Fix lint * Apply suggestions from code review Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
parent
89beceeb9a
commit
01fc24c78c
9 changed files with 161 additions and 89 deletions
|
@ -490,6 +490,7 @@ form.name_chars_not_allowed = User name '%s' contains invalid characters.
|
||||||
[settings]
|
[settings]
|
||||||
profile = Profile
|
profile = Profile
|
||||||
account = Account
|
account = Account
|
||||||
|
appearance = Appearance
|
||||||
password = Password
|
password = Password
|
||||||
security = Security
|
security = Security
|
||||||
avatar = Avatar
|
avatar = Avatar
|
||||||
|
@ -514,7 +515,9 @@ website = Website
|
||||||
location = Location
|
location = Location
|
||||||
update_theme = Update Theme
|
update_theme = Update Theme
|
||||||
update_profile = Update Profile
|
update_profile = Update Profile
|
||||||
|
update_language = Update Language
|
||||||
update_language_not_found = Language '%s' is not available.
|
update_language_not_found = Language '%s' is not available.
|
||||||
|
update_language_success = Language has been updated.
|
||||||
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.
|
||||||
change_username_prompt = Note: username changes also change your account URL.
|
change_username_prompt = Note: username changes also change your account URL.
|
||||||
|
|
|
@ -257,34 +257,6 @@ func DeleteAccount(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUIThemePost is used to update users' specific theme
|
|
||||||
func UpdateUIThemePost(ctx *context.Context) {
|
|
||||||
form := web.GetForm(ctx).(*forms.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) {
|
||||||
emlist, err := models.GetEmailAddresses(ctx.User.ID)
|
emlist, err := models.GetEmailAddresses(ctx.User.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -32,6 +32,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tplSettingsProfile base.TplName = "user/settings/profile"
|
tplSettingsProfile base.TplName = "user/settings/profile"
|
||||||
|
tplSettingsAppearance base.TplName = "user/settings/appearance"
|
||||||
tplSettingsOrganization base.TplName = "user/settings/organization"
|
tplSettingsOrganization base.TplName = "user/settings/organization"
|
||||||
tplSettingsRepositories base.TplName = "user/settings/repos"
|
tplSettingsRepositories base.TplName = "user/settings/repos"
|
||||||
)
|
)
|
||||||
|
@ -115,14 +116,6 @@ func ProfilePost(ctx *context.Context) {
|
||||||
ctx.User.KeepEmailPrivate = form.KeepEmailPrivate
|
ctx.User.KeepEmailPrivate = form.KeepEmailPrivate
|
||||||
ctx.User.Website = form.Website
|
ctx.User.Website = form.Website
|
||||||
ctx.User.Location = form.Location
|
ctx.User.Location = form.Location
|
||||||
if len(form.Language) != 0 {
|
|
||||||
if !util.IsStringInSlice(form.Language, setting.Langs) {
|
|
||||||
ctx.Flash.Error(ctx.Tr("settings.update_language_not_found", form.Language))
|
|
||||||
ctx.Redirect(setting.AppSubURL + "/user/settings")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.User.Language = form.Language
|
|
||||||
}
|
|
||||||
ctx.User.Description = form.Description
|
ctx.User.Description = form.Description
|
||||||
ctx.User.KeepActivityPrivate = form.KeepActivityPrivate
|
ctx.User.KeepActivityPrivate = form.KeepActivityPrivate
|
||||||
ctx.User.Visibility = form.Visibility
|
ctx.User.Visibility = form.Visibility
|
||||||
|
@ -329,3 +322,68 @@ func Repos(ctx *context.Context) {
|
||||||
ctx.Data["Page"] = pager
|
ctx.Data["Page"] = pager
|
||||||
ctx.HTML(http.StatusOK, tplSettingsRepositories)
|
ctx.HTML(http.StatusOK, tplSettingsRepositories)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Appearance render user's appearance settings
|
||||||
|
func Appearance(ctx *context.Context) {
|
||||||
|
ctx.Data["Title"] = ctx.Tr("settings")
|
||||||
|
ctx.Data["PageIsSettingsAppearance"] = true
|
||||||
|
|
||||||
|
ctx.HTML(http.StatusOK, tplSettingsAppearance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUIThemePost is used to update users' specific theme
|
||||||
|
func UpdateUIThemePost(ctx *context.Context) {
|
||||||
|
form := web.GetForm(ctx).(*forms.UpdateThemeForm)
|
||||||
|
ctx.Data["Title"] = ctx.Tr("settings")
|
||||||
|
ctx.Data["PageIsSettingsAppearance"] = true
|
||||||
|
|
||||||
|
if ctx.HasError() {
|
||||||
|
ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !form.IsThemeExists() {
|
||||||
|
ctx.Flash.Error(ctx.Tr("settings.theme_update_error"))
|
||||||
|
ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
|
||||||
|
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/appearance")
|
||||||
|
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/appearance")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserLang update a user's language
|
||||||
|
func UpdateUserLang(ctx *context.Context) {
|
||||||
|
form := web.GetForm(ctx).(*forms.UpdateLanguageForm)
|
||||||
|
ctx.Data["Title"] = ctx.Tr("settings")
|
||||||
|
ctx.Data["PageIsSettingsAppearance"] = true
|
||||||
|
|
||||||
|
if len(form.Language) != 0 {
|
||||||
|
if !util.IsStringInSlice(form.Language, setting.Langs) {
|
||||||
|
ctx.Flash.Error(ctx.Tr("settings.update_language_not_found", form.Language))
|
||||||
|
ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.User.Language = form.Language
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := models.UpdateUserSetting(ctx.User); err != nil {
|
||||||
|
ctx.ServerError("UpdateUserSetting", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the language to the one we just set
|
||||||
|
middleware.SetLocaleCookie(ctx.Resp, ctx.User.Language, 0)
|
||||||
|
|
||||||
|
log.Trace("User settings updated: %s", ctx.User.Name)
|
||||||
|
ctx.Flash.Success(i18n.Tr(ctx.User.Language, "settings.update_language_success"))
|
||||||
|
ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -317,6 +317,10 @@ func RegisterRoutes(m *web.Route) {
|
||||||
m.Post("/email", bindIgnErr(forms.AddEmailForm{}), userSetting.EmailPost)
|
m.Post("/email", bindIgnErr(forms.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.Group("/appearance", func() {
|
||||||
|
m.Get("", userSetting.Appearance)
|
||||||
|
m.Post("/language", bindIgnErr(forms.UpdateLanguageForm{}), userSetting.UpdateUserLang)
|
||||||
m.Post("/theme", bindIgnErr(forms.UpdateThemeForm{}), userSetting.UpdateUIThemePost)
|
m.Post("/theme", bindIgnErr(forms.UpdateThemeForm{}), userSetting.UpdateUIThemePost)
|
||||||
})
|
})
|
||||||
m.Group("/security", func() {
|
m.Group("/security", func() {
|
||||||
|
|
|
@ -240,7 +240,6 @@ type UpdateProfileForm struct {
|
||||||
KeepEmailPrivate bool
|
KeepEmailPrivate bool
|
||||||
Website string `binding:"ValidSiteUrl;MaxSize(255)"`
|
Website string `binding:"ValidSiteUrl;MaxSize(255)"`
|
||||||
Location string `binding:"MaxSize(50)"`
|
Location string `binding:"MaxSize(50)"`
|
||||||
Language string
|
|
||||||
Description string `binding:"MaxSize(255)"`
|
Description string `binding:"MaxSize(255)"`
|
||||||
Visibility structs.VisibleType
|
Visibility structs.VisibleType
|
||||||
KeepActivityPrivate bool
|
KeepActivityPrivate bool
|
||||||
|
@ -252,6 +251,17 @@ func (f *UpdateProfileForm) Validate(req *http.Request, errs binding.Errors) bin
|
||||||
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
|
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateLanguageForm form for updating profile
|
||||||
|
type UpdateLanguageForm struct {
|
||||||
|
Language string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates the fields
|
||||||
|
func (f *UpdateLanguageForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||||
|
ctx := context.GetContext(req)
|
||||||
|
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||||
|
}
|
||||||
|
|
||||||
// Avatar types
|
// Avatar types
|
||||||
const (
|
const (
|
||||||
AvatarLocal string = "local"
|
AvatarLocal string = "local"
|
||||||
|
|
|
@ -130,44 +130,6 @@
|
||||||
</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}}">
|
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
|
||||||
<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 error header">
|
<h4 class="ui top attached error header">
|
||||||
{{.i18n.Tr "settings.delete_account"}}
|
{{.i18n.Tr "settings.delete_account"}}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
74
templates/user/settings/appearance.tmpl
Normal file
74
templates/user/settings/appearance.tmpl
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
{{template "base/head" .}}
|
||||||
|
<div class="page-content user settings sshkeys">
|
||||||
|
{{template "user/settings/navbar" .}}
|
||||||
|
<div class="ui container">
|
||||||
|
{{template "base/alert" .}}
|
||||||
|
|
||||||
|
<!-- Theme -->
|
||||||
|
<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}}">
|
||||||
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<!-- Language -->
|
||||||
|
<h4 class="ui top attached header">
|
||||||
|
{{.i18n.Tr "settings.language"}}
|
||||||
|
</h4>
|
||||||
|
<div class="ui attached segment">
|
||||||
|
<form class="ui form" action="{{.Link}}/language" method="post">
|
||||||
|
{{.CsrfTokenHtml}}
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui language selection dropdown" id="language">
|
||||||
|
<input name="language" type="hidden" value="{{.SignedUser.Language}}">
|
||||||
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
|
<div class="text">{{range .AllLangs}}{{if eq $.SignedUser.Language .Lang}}{{.Name}}{{end}}{{end}}</div>
|
||||||
|
<div class="menu">
|
||||||
|
{{range .AllLangs}}
|
||||||
|
<div class="item{{if eq $.SignedUser.Language .Lang}} active selected{{end}}" data-value="{{.Lang}}">{{.Name}}</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<button class="ui green button">{{$.i18n.Tr "settings.update_language"}}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{template "base/footer" .}}
|
|
@ -6,6 +6,9 @@
|
||||||
<a class="{{if .PageIsSettingsAccount}}active{{end}} item" href="{{AppSubUrl}}/user/settings/account">
|
<a class="{{if .PageIsSettingsAccount}}active{{end}} item" href="{{AppSubUrl}}/user/settings/account">
|
||||||
{{.i18n.Tr "settings.account"}}
|
{{.i18n.Tr "settings.account"}}
|
||||||
</a>
|
</a>
|
||||||
|
<a class="{{if .PageIsSettingsAppearance}}active{{end}} item" href="{{AppSubUrl}}/user/settings/appearance">
|
||||||
|
{{.i18n.Tr "settings.appearance"}}
|
||||||
|
</a>
|
||||||
<a class="{{if .PageIsSettingsSecurity}}active{{end}} item" href="{{AppSubUrl}}/user/settings/security">
|
<a class="{{if .PageIsSettingsSecurity}}active{{end}} item" href="{{AppSubUrl}}/user/settings/security">
|
||||||
{{.i18n.Tr "settings.security"}}
|
{{.i18n.Tr "settings.security"}}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -47,20 +47,6 @@
|
||||||
<input id="location" name="location" value="{{.SignedUser.Location}}">
|
<input id="location" name="location" value="{{.SignedUser.Location}}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label for="language">{{.i18n.Tr "settings.language"}}</label>
|
|
||||||
<div class="ui language selection dropdown" id="language">
|
|
||||||
<input name="language" type="hidden" value="{{.SignedUser.Language}}">
|
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
|
||||||
<div class="text">{{range .AllLangs}}{{if eq $.SignedUser.Language .Lang}}{{.Name}}{{end}}{{end}}</div>
|
|
||||||
<div class="menu">
|
|
||||||
{{range .AllLangs}}
|
|
||||||
<div class="item{{if eq $.SignedUser.Language .Lang}} active selected{{end}}" data-value="{{.Lang}}">{{.Name}}</div>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<!-- private block -->
|
<!-- private block -->
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue