* 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>tags/v1.16.0-rc1
@@ -490,6 +490,7 @@ form.name_chars_not_allowed = User name '%s' contains invalid characters. | |||
[settings] | |||
profile = Profile | |||
account = Account | |||
appearance = Appearance | |||
password = Password | |||
security = Security | |||
avatar = Avatar | |||
@@ -514,7 +515,9 @@ website = Website | |||
location = Location | |||
update_theme = Update Theme | |||
update_profile = Update Profile | |||
update_language = Update Language | |||
update_language_not_found = Language '%s' is not available. | |||
update_language_success = Language has been updated. | |||
update_profile_success = Your profile has been updated. | |||
change_username = Your username has been changed. | |||
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) { | |||
emlist, err := models.GetEmailAddresses(ctx.User.ID) | |||
if err != nil { |
@@ -32,6 +32,7 @@ import ( | |||
const ( | |||
tplSettingsProfile base.TplName = "user/settings/profile" | |||
tplSettingsAppearance base.TplName = "user/settings/appearance" | |||
tplSettingsOrganization base.TplName = "user/settings/organization" | |||
tplSettingsRepositories base.TplName = "user/settings/repos" | |||
) | |||
@@ -115,14 +116,6 @@ func ProfilePost(ctx *context.Context) { | |||
ctx.User.KeepEmailPrivate = form.KeepEmailPrivate | |||
ctx.User.Website = form.Website | |||
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.KeepActivityPrivate = form.KeepActivityPrivate | |||
ctx.User.Visibility = form.Visibility | |||
@@ -329,3 +322,68 @@ func Repos(ctx *context.Context) { | |||
ctx.Data["Page"] = pager | |||
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/delete", userSetting.DeleteEmail) | |||
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.Group("/security", func() { |
@@ -240,7 +240,6 @@ type UpdateProfileForm struct { | |||
KeepEmailPrivate bool | |||
Website string `binding:"ValidSiteUrl;MaxSize(255)"` | |||
Location string `binding:"MaxSize(50)"` | |||
Language string | |||
Description string `binding:"MaxSize(255)"` | |||
Visibility structs.VisibleType | |||
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) | |||
} | |||
// 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 | |||
const ( | |||
AvatarLocal string = "local" |
@@ -130,44 +130,6 @@ | |||
</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}}"> | |||
{{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"> | |||
{{.i18n.Tr "settings.delete_account"}} | |||
</h4> |
@@ -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"> | |||
{{.i18n.Tr "settings.account"}} | |||
</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"> | |||
{{.i18n.Tr "settings.security"}} | |||
</a> |
@@ -47,20 +47,6 @@ | |||
<input id="location" name="location" value="{{.SignedUser.Location}}"> | |||
</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> | |||
<!-- private block --> | |||