diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 31d5bf08c6..309ff99c43 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -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. diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index 249793578a..47014dc814 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -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 { diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index d75149b8fc..d181ae1720 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -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") + +} diff --git a/routers/web/web.go b/routers/web/web.go index 88565d6d85..c0bb74da2e 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -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() { diff --git a/services/forms/user_form.go b/services/forms/user_form.go index 241ebdc0d9..9f86bf6166 100644 --- a/services/forms/user_form.go +++ b/services/forms/user_form.go @@ -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" diff --git a/templates/user/settings/account.tmpl b/templates/user/settings/account.tmpl index 9ed5d3a6dd..3753a77cec 100644 --- a/templates/user/settings/account.tmpl +++ b/templates/user/settings/account.tmpl @@ -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> diff --git a/templates/user/settings/appearance.tmpl b/templates/user/settings/appearance.tmpl new file mode 100644 index 0000000000..777b89c750 --- /dev/null +++ b/templates/user/settings/appearance.tmpl @@ -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" .}} diff --git a/templates/user/settings/navbar.tmpl b/templates/user/settings/navbar.tmpl index 7255819374..3477a5949b 100644 --- a/templates/user/settings/navbar.tmpl +++ b/templates/user/settings/navbar.tmpl @@ -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> diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl index 1f1585a787..0b14e3c0d3 100644 --- a/templates/user/settings/profile.tmpl +++ b/templates/user/settings/profile.tmpl @@ -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 -->