mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-27 00:23:41 +09: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] | [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. | ||||||
|   | |||||||
| @@ -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 { | ||||||
|   | |||||||
| @@ -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") | ||||||
|  |  | ||||||
|  | } | ||||||
|   | |||||||
| @@ -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() { | ||||||
|   | |||||||
| @@ -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" | ||||||
|   | |||||||
| @@ -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> | ||||||
|   | |||||||
							
								
								
									
										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"> | 		<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> | ||||||
|   | |||||||
| @@ -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 --> | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user