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:
qwerty287 2021-10-27 17:40:08 +02:00 committed by GitHub
parent 89beceeb9a
commit 01fc24c78c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 161 additions and 89 deletions

View file

@ -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.

View file

@ -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 {

View file

@ -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")
}

View file

@ -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() {

View file

@ -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"

View file

@ -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>

View 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" .}}

View file

@ -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>

View file

@ -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 -->