mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08: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:
		@@ -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>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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">
 | 
			
		||||
			{{.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 -->
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user