mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 07:21:36 +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
					
				| @ -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 --> | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user