Initial support for colorblindness-friendly themes (#30625)
Initial support for #25680 This PR only adds some simple styles from GitHub, it is big enough and it focuses on adding the necessary framework-level supports. More styles could be fine-tuned later.
This commit is contained in:
parent
dd2aaadce3
commit
b79e3db264
|
@ -1231,7 +1231,8 @@ LEVEL = Info
|
||||||
;DEFAULT_THEME = gitea-auto
|
;DEFAULT_THEME = gitea-auto
|
||||||
;;
|
;;
|
||||||
;; All available themes. Allow users select personalized themes regardless of the value of `DEFAULT_THEME`.
|
;; All available themes. Allow users select personalized themes regardless of the value of `DEFAULT_THEME`.
|
||||||
;THEMES = gitea-auto,gitea-light,gitea-dark
|
;; Leave it empty to allow users to select any theme from "{CustomPath}/public/assets/css/theme-*.css"
|
||||||
|
;THEMES =
|
||||||
;;
|
;;
|
||||||
;; All available reactions users can choose on issues/prs and comments.
|
;; All available reactions users can choose on issues/prs and comments.
|
||||||
;; Values can be emoji alias (:smile:) or a unicode emoji.
|
;; Values can be emoji alias (:smile:) or a unicode emoji.
|
||||||
|
|
|
@ -214,10 +214,9 @@ The following configuration set `Content-Type: application/vnd.android.package-a
|
||||||
- `SITEMAP_PAGING_NUM`: **20**: Number of items that are displayed in a single subsitemap.
|
- `SITEMAP_PAGING_NUM`: **20**: Number of items that are displayed in a single subsitemap.
|
||||||
- `GRAPH_MAX_COMMIT_NUM`: **100**: Number of maximum commits shown in the commit graph.
|
- `GRAPH_MAX_COMMIT_NUM`: **100**: Number of maximum commits shown in the commit graph.
|
||||||
- `CODE_COMMENT_LINES`: **4**: Number of line of codes shown for a code comment.
|
- `CODE_COMMENT_LINES`: **4**: Number of line of codes shown for a code comment.
|
||||||
- `DEFAULT_THEME`: **gitea-auto**: \[gitea-auto, gitea-light, gitea-dark\]: Set the default theme for the Gitea installation.
|
- `DEFAULT_THEME`: **gitea-auto**: Set the default theme for the Gitea installation, custom themes could be provided by "{CustomPath}/public/assets/css/theme-*.css".
|
||||||
- `SHOW_USER_EMAIL`: **true**: Whether the email of the user should be shown in the Explore Users page.
|
- `SHOW_USER_EMAIL`: **true**: Whether the email of the user should be shown in the Explore Users page.
|
||||||
- `THEMES`: **gitea-auto,gitea-light,gitea-dark**: All available themes. Allow users select personalized themes.
|
- `THEMES`: **_empty_**: All available themes by "{CustomPath}/public/assets/css/theme-*.css". Allow users select personalized themes.
|
||||||
regardless of the value of `DEFAULT_THEME`.
|
|
||||||
- `MAX_DISPLAY_FILE_SIZE`: **8388608**: Max size of files to be displayed (default is 8MiB)
|
- `MAX_DISPLAY_FILE_SIZE`: **8388608**: Max size of files to be displayed (default is 8MiB)
|
||||||
- `AMBIGUOUS_UNICODE_DETECTION`: **true**: Detect ambiguous unicode characters in file contents and show warnings on the UI
|
- `AMBIGUOUS_UNICODE_DETECTION`: **true**: Detect ambiguous unicode characters in file contents and show warnings on the UI
|
||||||
- `REACTIONS`: All available reactions users can choose on issues/prs and comments
|
- `REACTIONS`: All available reactions users can choose on issues/prs and comments
|
||||||
|
|
|
@ -212,10 +212,9 @@ menu:
|
||||||
- `SITEMAP_PAGING_NUM`: **20**: 在单个子SiteMap中显示的项数。
|
- `SITEMAP_PAGING_NUM`: **20**: 在单个子SiteMap中显示的项数。
|
||||||
- `GRAPH_MAX_COMMIT_NUM`: **100**: 提交图中显示的最大commit数量。
|
- `GRAPH_MAX_COMMIT_NUM`: **100**: 提交图中显示的最大commit数量。
|
||||||
- `CODE_COMMENT_LINES`: **4**: 在代码评论中能够显示的最大代码行数。
|
- `CODE_COMMENT_LINES`: **4**: 在代码评论中能够显示的最大代码行数。
|
||||||
- `DEFAULT_THEME`: **gitea-auto**: \[gitea-auto, gitea-light, gitea-dark\]: 在Gitea安装时候设置的默认主题。
|
- `DEFAULT_THEME`: **gitea-auto**: 在Gitea安装时候设置的默认主题,自定义的主题可以通过 "{CustomPath}/public/assets/css/theme-*.css" 提供。
|
||||||
- `SHOW_USER_EMAIL`: **true**: 用户的电子邮件是否应该显示在`Explore Users`页面中。
|
- `SHOW_USER_EMAIL`: **true**: 用户的电子邮件是否应该显示在`Explore Users`页面中。
|
||||||
- `THEMES`: **gitea-auto,gitea-light,gitea-dark**: 所有可用的主题。允许用户选择个性化的主题,
|
- `THEMES`: **_empty_**: 所有可用的主题(由 "{CustomPath}/public/assets/css/theme-*.css" 提供)。允许用户选择个性化的主题,
|
||||||
而不受DEFAULT_THEME 值的影响。
|
|
||||||
- `MAX_DISPLAY_FILE_SIZE`: **8388608**: 能够显示文件的最大大小(默认为8MiB)。
|
- `MAX_DISPLAY_FILE_SIZE`: **8388608**: 能够显示文件的最大大小(默认为8MiB)。
|
||||||
- `REACTIONS`: 用户可以在问题(Issue)、Pull Request(PR)以及评论中选择的所有可选的反应。
|
- `REACTIONS`: 用户可以在问题(Issue)、Pull Request(PR)以及评论中选择的所有可选的反应。
|
||||||
这些值可以是表情符号别名(例如::smile:)或Unicode表情符号。
|
这些值可以是表情符号别名(例如::smile:)或Unicode表情符号。
|
||||||
|
|
|
@ -381,7 +381,7 @@ To make a custom theme available to all users:
|
||||||
|
|
||||||
1. Add a CSS file to `$GITEA_CUSTOM/public/assets/css/theme-<theme-name>.css`.
|
1. Add a CSS file to `$GITEA_CUSTOM/public/assets/css/theme-<theme-name>.css`.
|
||||||
The value of `$GITEA_CUSTOM` of your instance can be queried by calling `gitea help` and looking up the value of "CustomPath".
|
The value of `$GITEA_CUSTOM` of your instance can be queried by calling `gitea help` and looking up the value of "CustomPath".
|
||||||
2. Add `<theme-name>` to the comma-separated list of setting `THEMES` in `app.ini`
|
2. Add `<theme-name>` to the comma-separated list of setting `THEMES` in `app.ini`, or leave `THEMES` empty to allow all themes.
|
||||||
|
|
||||||
Community themes are listed in [gitea/awesome-gitea#themes](https://gitea.com/gitea/awesome-gitea#themes).
|
Community themes are listed in [gitea/awesome-gitea#themes](https://gitea.com/gitea/awesome-gitea#themes).
|
||||||
|
|
||||||
|
|
|
@ -178,17 +178,6 @@ At some point, a customer or third party needs access to a specific repo and onl
|
||||||
|
|
||||||
Use [Fail2Ban](administration/fail2ban-setup.md) to monitor and stop automated login attempts or other malicious behavior based on log patterns
|
Use [Fail2Ban](administration/fail2ban-setup.md) to monitor and stop automated login attempts or other malicious behavior based on log patterns
|
||||||
|
|
||||||
## How to add/use custom themes
|
|
||||||
|
|
||||||
Gitea supports three official themes right now, `gitea-light`, `gitea-dark`, and `gitea-auto` (automatically switches between the previous two depending on operating system settings).
|
|
||||||
To add your own theme, currently the only way is to provide a complete theme (not just color overrides)
|
|
||||||
|
|
||||||
As an example, let's say our theme is `arc-blue` (this is a real theme, and can be found [in this issue](https://github.com/go-gitea/gitea/issues/6011))
|
|
||||||
|
|
||||||
Name the `.css` file `theme-arc-blue.css` and add it to your custom folder in `custom/public/assets/css`
|
|
||||||
|
|
||||||
Allow users to use it by adding `arc-blue` to the list of `THEMES` in your `app.ini`
|
|
||||||
|
|
||||||
## SSHD vs built-in SSH
|
## SSHD vs built-in SSH
|
||||||
|
|
||||||
SSHD is the built-in SSH server on most Unix systems.
|
SSHD is the built-in SSH server on most Unix systems.
|
||||||
|
|
|
@ -182,17 +182,6 @@ Gitea不提供内置的Pages服务器。您需要一个专用的域名来提供
|
||||||
|
|
||||||
使用 [Fail2Ban](administration/fail2ban-setup.md) 监视并阻止基于日志模式的自动登录尝试或其他恶意行为。
|
使用 [Fail2Ban](administration/fail2ban-setup.md) 监视并阻止基于日志模式的自动登录尝试或其他恶意行为。
|
||||||
|
|
||||||
## 如何添加/使用自定义主题
|
|
||||||
|
|
||||||
Gitea 目前支持三个官方主题,分别是 `gitea-light`、`gitea-dark` 和 `gitea-auto`(根据操作系统设置自动切换前两个主题)。
|
|
||||||
要添加自己的主题,目前唯一的方法是提供一个完整的主题(不仅仅是颜色覆盖)。
|
|
||||||
|
|
||||||
假设我们的主题是 `arc-blue`(这是一个真实的主题,可以在[此问题](https://github.com/go-gitea/gitea/issues/6011)中找到)
|
|
||||||
|
|
||||||
将`.css`文件命名为`theme-arc-blue.css`并将其添加到`custom/public/assets/css`文件夹中
|
|
||||||
|
|
||||||
通过将`arc-blue`添加到`app.ini`中的`THEMES`列表中,允许用户使用该主题
|
|
||||||
|
|
||||||
## SSHD vs 内建SSH
|
## SSHD vs 内建SSH
|
||||||
|
|
||||||
SSHD是大多数Unix系统上内建的SSH服务器。
|
SSHD是大多数Unix系统上内建的SSH服务器。
|
||||||
|
|
|
@ -318,7 +318,7 @@ func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting any) {
|
||||||
// StartupProblems contains the messages for various startup problems, including: setting option, file/folder, etc
|
// StartupProblems contains the messages for various startup problems, including: setting option, file/folder, etc
|
||||||
var StartupProblems []string
|
var StartupProblems []string
|
||||||
|
|
||||||
func logStartupProblem(skip int, level log.Level, format string, args ...any) {
|
func LogStartupProblem(skip int, level log.Level, format string, args ...any) {
|
||||||
msg := fmt.Sprintf(format, args...)
|
msg := fmt.Sprintf(format, args...)
|
||||||
log.Log(skip+1, level, "%s", msg)
|
log.Log(skip+1, level, "%s", msg)
|
||||||
StartupProblems = append(StartupProblems, msg)
|
StartupProblems = append(StartupProblems, msg)
|
||||||
|
@ -326,14 +326,14 @@ func logStartupProblem(skip int, level log.Level, format string, args ...any) {
|
||||||
|
|
||||||
func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) {
|
func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) {
|
||||||
if rootCfg.Section(oldSection).HasKey(oldKey) {
|
if rootCfg.Section(oldSection).HasKey(oldKey) {
|
||||||
logStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version)
|
LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini
|
// deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini
|
||||||
func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) {
|
func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) {
|
||||||
if rootCfg.Section(oldSection).HasKey(oldKey) {
|
if rootCfg.Section(oldSection).HasKey(oldKey) {
|
||||||
logStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents but it won't take effect because it has been moved to admin panel -> config setting", oldSection, oldKey)
|
LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents but it won't take effect because it has been moved to admin panel -> config setting", oldSection, oldKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -174,7 +174,7 @@ func GetGeneralTokenSigningSecret() []byte {
|
||||||
}
|
}
|
||||||
if generalSigningSecret.CompareAndSwap(old, &jwtSecret) {
|
if generalSigningSecret.CompareAndSwap(old, &jwtSecret) {
|
||||||
// FIXME: in main branch, the signing token should be refactored (eg: one unique for LFS/OAuth2/etc ...)
|
// FIXME: in main branch, the signing token should be refactored (eg: one unique for LFS/OAuth2/etc ...)
|
||||||
logStartupProblem(1, log.WARN, "OAuth2 is not enabled, unable to use a persistent signing secret, a new one is generated, which is not persistent between restarts and cluster nodes")
|
LogStartupProblem(1, log.WARN, "OAuth2 is not enabled, unable to use a persistent signing secret, a new one is generated, which is not persistent between restarts and cluster nodes")
|
||||||
return jwtSecret
|
return jwtSecret
|
||||||
}
|
}
|
||||||
return *generalSigningSecret.Load()
|
return *generalSigningSecret.Load()
|
||||||
|
|
|
@ -235,7 +235,7 @@ var configuredPaths = make(map[string]string)
|
||||||
func checkOverlappedPath(name, path string) {
|
func checkOverlappedPath(name, path string) {
|
||||||
// TODO: some paths shouldn't overlap (storage.xxx.path), while some could (data path is the base path for storage path)
|
// TODO: some paths shouldn't overlap (storage.xxx.path), while some could (data path is the base path for storage path)
|
||||||
if targetName, ok := configuredPaths[path]; ok && targetName != name {
|
if targetName, ok := configuredPaths[path]; ok && targetName != name {
|
||||||
logStartupProblem(1, log.ERROR, "Configured path %q is used by %q and %q at the same time. The paths must be unique to prevent data loss.", path, targetName, name)
|
LogStartupProblem(1, log.ERROR, "Configured path %q is used by %q and %q at the same time. The paths must be unique to prevent data loss.", path, targetName, name)
|
||||||
}
|
}
|
||||||
configuredPaths[path] = name
|
configuredPaths[path] = name
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,6 @@ var UI = struct {
|
||||||
ReactionMaxUserNum: 10,
|
ReactionMaxUserNum: 10,
|
||||||
MaxDisplayFileSize: 8388608,
|
MaxDisplayFileSize: 8388608,
|
||||||
DefaultTheme: `gitea-auto`,
|
DefaultTheme: `gitea-auto`,
|
||||||
Themes: []string{`gitea-auto`, `gitea-light`, `gitea-dark`},
|
|
||||||
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`},
|
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`},
|
||||||
CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`},
|
CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`},
|
||||||
CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"},
|
CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"},
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/gitdiff"
|
"code.gitea.io/gitea/services/gitdiff"
|
||||||
|
"code.gitea.io/gitea/services/webtheme"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewFuncMap returns functions for injecting to templates
|
// NewFuncMap returns functions for injecting to templates
|
||||||
|
@ -137,12 +138,7 @@ func NewFuncMap() template.FuncMap {
|
||||||
"DisableImportLocal": func() bool {
|
"DisableImportLocal": func() bool {
|
||||||
return !setting.ImportLocalPaths
|
return !setting.ImportLocalPaths
|
||||||
},
|
},
|
||||||
"ThemeName": func(user *user_model.User) string {
|
"UserThemeName": UserThemeName,
|
||||||
if user == nil || user.Theme == "" {
|
|
||||||
return setting.UI.DefaultTheme
|
|
||||||
}
|
|
||||||
return user.Theme
|
|
||||||
},
|
|
||||||
"NotificationSettings": func() map[string]any {
|
"NotificationSettings": func() map[string]any {
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"MinTimeout": int(setting.UI.Notification.MinTimeout / time.Millisecond),
|
"MinTimeout": int(setting.UI.Notification.MinTimeout / time.Millisecond),
|
||||||
|
@ -261,3 +257,13 @@ func Eval(tokens ...any) (any, error) {
|
||||||
n, err := eval.Expr(tokens...)
|
n, err := eval.Expr(tokens...)
|
||||||
return n.Value, err
|
return n.Value, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UserThemeName(user *user_model.User) string {
|
||||||
|
if user == nil || user.Theme == "" {
|
||||||
|
return setting.UI.DefaultTheme
|
||||||
|
}
|
||||||
|
if webtheme.IsThemeAvailable(user.Theme) {
|
||||||
|
return user.Theme
|
||||||
|
}
|
||||||
|
return setting.UI.DefaultTheme
|
||||||
|
}
|
||||||
|
|
|
@ -763,6 +763,8 @@ manage_themes = Select default theme
|
||||||
manage_openid = Manage OpenID Addresses
|
manage_openid = Manage OpenID Addresses
|
||||||
email_desc = Your primary email address will be used for notifications, password recovery and, provided that it is not hidden, web-based Git operations.
|
email_desc = Your primary email address will be used for notifications, password recovery and, provided that it is not hidden, web-based Git operations.
|
||||||
theme_desc = This will be your default theme across the site.
|
theme_desc = This will be your default theme across the site.
|
||||||
|
theme_colorblindness_help = Colorblindness Theme Support
|
||||||
|
theme_colorblindness_prompt = Gitea just gets some themes with basic colorblindness support, which only have a few colors defined. The work is still in progress. More improvements could be done by defining more colors in the theme CSS files.
|
||||||
primary = Primary
|
primary = Primary
|
||||||
activated = Activated
|
activated = Activated
|
||||||
requires_activation = Requires activation
|
requires_activation = Requires activation
|
||||||
|
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
user_service "code.gitea.io/gitea/services/user"
|
user_service "code.gitea.io/gitea/services/user"
|
||||||
|
"code.gitea.io/gitea/services/webtheme"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -319,6 +320,13 @@ func Appearance(ctx *context.Context) {
|
||||||
ctx.Data["Title"] = ctx.Tr("settings.appearance")
|
ctx.Data["Title"] = ctx.Tr("settings.appearance")
|
||||||
ctx.Data["PageIsSettingsAppearance"] = true
|
ctx.Data["PageIsSettingsAppearance"] = true
|
||||||
|
|
||||||
|
allThemes := webtheme.GetAvailableThemes()
|
||||||
|
if webtheme.IsThemeAvailable(setting.UI.DefaultTheme) {
|
||||||
|
allThemes = util.SliceRemoveAll(allThemes, setting.UI.DefaultTheme)
|
||||||
|
allThemes = append([]string{setting.UI.DefaultTheme}, allThemes...) // move the default theme to the top
|
||||||
|
}
|
||||||
|
ctx.Data["AllThemes"] = allThemes
|
||||||
|
|
||||||
var hiddenCommentTypes *big.Int
|
var hiddenCommentTypes *big.Int
|
||||||
val, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyHiddenCommentTypes)
|
val, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyHiddenCommentTypes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -341,11 +349,12 @@ func UpdateUIThemePost(ctx *context.Context) {
|
||||||
ctx.Data["PageIsSettingsAppearance"] = true
|
ctx.Data["PageIsSettingsAppearance"] = true
|
||||||
|
|
||||||
if ctx.HasError() {
|
if ctx.HasError() {
|
||||||
|
ctx.Flash.Error(ctx.GetErrMsg())
|
||||||
ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
|
ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !form.IsThemeExists() {
|
if !webtheme.IsThemeAvailable(form.Theme) {
|
||||||
ctx.Flash.Error(ctx.Tr("settings.theme_update_error"))
|
ctx.Flash.Error(ctx.Tr("settings.theme_update_error"))
|
||||||
ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
|
ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
|
||||||
return
|
return
|
||||||
|
|
|
@ -652,7 +652,7 @@ func registerRoutes(m *web.Route) {
|
||||||
m.Get("", user_setting.BlockedUsers)
|
m.Get("", user_setting.BlockedUsers)
|
||||||
m.Post("", web.Bind(forms.BlockUserForm{}), user_setting.BlockedUsersPost)
|
m.Post("", web.Bind(forms.BlockUserForm{}), user_setting.BlockedUsersPost)
|
||||||
})
|
})
|
||||||
}, reqSignIn, ctxDataSet("PageIsUserSettings", true, "AllThemes", setting.UI.Themes, "EnablePackages", setting.Packages.Enabled))
|
}, reqSignIn, ctxDataSet("PageIsUserSettings", true, "EnablePackages", setting.Packages.Enabled))
|
||||||
|
|
||||||
m.Group("/user", func() {
|
m.Group("/user", func() {
|
||||||
m.Get("/activate", auth.Activate)
|
m.Get("/activate", auth.Activate)
|
||||||
|
|
|
@ -230,6 +230,7 @@ func Contexter() func(next http.Handler) http.Handler {
|
||||||
|
|
||||||
// HasError returns true if error occurs in form validation.
|
// HasError returns true if error occurs in form validation.
|
||||||
// Attention: this function changes ctx.Data and ctx.Flash
|
// Attention: this function changes ctx.Data and ctx.Flash
|
||||||
|
// If HasError is called, then before Redirect, the error message should be stored by ctx.Flash.Error(ctx.GetErrMsg()) again.
|
||||||
func (ctx *Context) HasError() bool {
|
func (ctx *Context) HasError() bool {
|
||||||
hasErr, ok := ctx.Data["HasError"]
|
hasErr, ok := ctx.Data["HasError"]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
@ -273,7 +272,7 @@ func (f *AddEmailForm) Validate(req *http.Request, errs binding.Errors) binding.
|
||||||
|
|
||||||
// UpdateThemeForm form for updating a users' theme
|
// UpdateThemeForm form for updating a users' theme
|
||||||
type UpdateThemeForm struct {
|
type UpdateThemeForm struct {
|
||||||
Theme string `binding:"Required;MaxSize(30)"`
|
Theme string `binding:"Required;MaxSize(255)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates the field
|
// Validate validates the field
|
||||||
|
@ -282,20 +281,6 @@ func (f *UpdateThemeForm) Validate(req *http.Request, errs binding.Errors) bindi
|
||||||
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
|
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsThemeExists checks if the theme is a theme available in the config.
|
|
||||||
func (f UpdateThemeForm) IsThemeExists() bool {
|
|
||||||
var exists bool
|
|
||||||
|
|
||||||
for _, v := range setting.UI.Themes {
|
|
||||||
if strings.EqualFold(v, f.Theme) {
|
|
||||||
exists = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return exists
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChangePasswordForm form for changing password
|
// ChangePasswordForm form for changing password
|
||||||
type ChangePasswordForm struct {
|
type ChangePasswordForm struct {
|
||||||
OldPassword string `form:"old_password" binding:"MaxSize(255)"`
|
OldPassword string `form:"old_password" binding:"MaxSize(255)"`
|
||||||
|
|
74
services/webtheme/webtheme.go
Normal file
74
services/webtheme/webtheme.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package webtheme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/container"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/public"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
availableThemes []string
|
||||||
|
availableThemesSet container.Set[string]
|
||||||
|
themeOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
func initThemes() {
|
||||||
|
availableThemes = nil
|
||||||
|
defer func() {
|
||||||
|
availableThemesSet = container.SetOf(availableThemes...)
|
||||||
|
if !availableThemesSet.Contains(setting.UI.DefaultTheme) {
|
||||||
|
setting.LogStartupProblem(1, log.ERROR, "Default theme %q is not available, please correct the '[ui].DEFAULT_THEME' setting in the config file", setting.UI.DefaultTheme)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
cssFiles, err := public.AssetFS().ListFiles("/assets/css")
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to list themes: %v", err)
|
||||||
|
availableThemes = []string{setting.UI.DefaultTheme}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var foundThemes []string
|
||||||
|
for _, name := range cssFiles {
|
||||||
|
name, ok := strings.CutPrefix(name, "theme-")
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name, ok = strings.CutSuffix(name, ".css")
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
foundThemes = append(foundThemes, name)
|
||||||
|
}
|
||||||
|
if len(setting.UI.Themes) > 0 {
|
||||||
|
allowedThemes := container.SetOf(setting.UI.Themes...)
|
||||||
|
for _, theme := range foundThemes {
|
||||||
|
if allowedThemes.Contains(theme) {
|
||||||
|
availableThemes = append(availableThemes, theme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
availableThemes = foundThemes
|
||||||
|
}
|
||||||
|
sort.Strings(availableThemes)
|
||||||
|
if len(availableThemes) == 0 {
|
||||||
|
setting.LogStartupProblem(1, log.ERROR, "No theme candidate in asset files, but Gitea requires there should be at least one usable theme")
|
||||||
|
availableThemes = []string{setting.UI.DefaultTheme}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAvailableThemes() []string {
|
||||||
|
themeOnce.Do(initThemes)
|
||||||
|
return availableThemes
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsThemeAvailable(name string) bool {
|
||||||
|
themeOnce.Do(initThemes)
|
||||||
|
return availableThemesSet.Contains(name)
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ctx.Locale.Lang}}" data-theme="{{ThemeName .SignedUser}}">
|
<html lang="{{ctx.Locale.Lang}}" data-theme="{{UserThemeName .SignedUser}}">
|
||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{{if .Title}}{{.Title}} - {{end}}{{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}}</title>
|
<title>{{if .Title}}{{.Title}} - {{end}}{{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}}</title>
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/index.css?v={{AssetVersion}}">
|
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/index.css?v={{AssetVersion}}">
|
||||||
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/theme-{{ThemeName .SignedUser | PathEscape}}.css?v={{AssetVersion}}">
|
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/theme-{{UserThemeName .SignedUser | PathEscape}}.css?v={{AssetVersion}}">
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{{/* This page should only depend the minimal template functions/variables, to avoid triggering new panics.
|
{{/* This page should only depend the minimal template functions/variables, to avoid triggering new panics.
|
||||||
* base template functions: AppName, AssetUrlPrefix, AssetVersion, AppSubUrl, ThemeName
|
* base template functions: AppName, AssetUrlPrefix, AssetVersion, AppSubUrl, UserThemeName
|
||||||
* ctx.Locale
|
* ctx.Locale
|
||||||
* .Flash
|
* .Flash
|
||||||
* .ErrorMsg
|
* .ErrorMsg
|
||||||
* .SignedUser (optional)
|
* .SignedUser (optional)
|
||||||
*/}}
|
*/}}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ctx.Locale.Lang}}" data-theme="{{ThemeName .SignedUser}}">
|
<html lang="{{ctx.Locale.Lang}}" data-theme="{{UserThemeName .SignedUser}}">
|
||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Internal Server Error - {{AppName}}</title>
|
<title>Internal Server Error - {{AppName}}</title>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{{template "user/settings/layout_head" (dict "ctxData" . "pageClass" "user settings sshkeys")}}
|
{{template "user/settings/layout_head" (dict "ctxData" . "pageClass" "user settings")}}
|
||||||
<div class="user-setting-content">
|
<div class="user-setting-content">
|
||||||
|
|
||||||
<!-- Theme -->
|
<!-- Theme -->
|
||||||
|
@ -6,39 +6,26 @@
|
||||||
{{ctx.Locale.Tr "settings.manage_themes"}}
|
{{ctx.Locale.Tr "settings.manage_themes"}}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="ui attached segment">
|
<div class="ui attached segment">
|
||||||
<div class="ui email list">
|
|
||||||
<div class="item">
|
|
||||||
{{ctx.Locale.Tr "settings.theme_desc"}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form class="ui form" action="{{.Link}}/theme" method="post">
|
<form class="ui form" action="{{.Link}}/theme" method="post">
|
||||||
{{.CsrfTokenHtml}}
|
{{.CsrfTokenHtml}}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="ui">{{ctx.Locale.Tr "settings.ui"}}</label>
|
{{ctx.Locale.Tr "settings.theme_desc"}}
|
||||||
<div class="ui selection dropdown" id="ui">
|
<a class="muted" target="_blank" href="https://github.com/go-gitea/gitea/blob/main/web_src/css/themes/" data-tooltip-content="{{ctx.Locale.Tr "settings.theme_colorblindness_prompt"}}">
|
||||||
<input name="theme" type="hidden" value="{{.SignedUser.Theme}}">
|
{{svg "octicon-question"}} {{ctx.Locale.Tr "settings.theme_colorblindness_help"}}
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
</a>
|
||||||
<div class="text">
|
</div>
|
||||||
{{range $i,$a := .AllThemes}}
|
<div class="field">
|
||||||
{{if eq $.SignedUser.Theme $a}}{{$a}}{{end}}
|
<label>{{ctx.Locale.Tr "settings.ui"}}</label>
|
||||||
{{end}}
|
<select name="theme" class="ui dropdown">
|
||||||
</div>
|
{{range $theme := .AllThemes}}
|
||||||
|
<option value="{{$theme}}" {{Iif (eq $.SignedUser.Theme $theme) "selected"}}>{{$theme}}</option>
|
||||||
<div class="menu">
|
{{end}}
|
||||||
{{range $i,$a := .AllThemes}}
|
</select>
|
||||||
<div class="item{{if eq $.SignedUser.Theme $a}} active selected{{end}}" data-value="{{$a}}">
|
</div>
|
||||||
{{$a}}
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<button class="ui primary button">{{ctx.Locale.Tr "settings.update_theme"}}</button>
|
<button class="ui primary button">{{ctx.Locale.Tr "settings.update_theme"}}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Language -->
|
<!-- Language -->
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
@import "./theme-gitea-dark.css";
|
||||||
|
|
||||||
|
/* red/green colorblind-friendly colors */
|
||||||
|
/* from GitHub: --diffBlob-addition-*, --diffBlob-deletion-*, etc */
|
||||||
|
:root {
|
||||||
|
--color-diff-added-word-bg: #388bfd66;
|
||||||
|
--color-diff-added-row-bg: #388bfd26;
|
||||||
|
|
||||||
|
--color-diff-removed-word-bg: #db6d2866;
|
||||||
|
--color-diff-removed-row-bg: #db6d2826;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
@import "./theme-gitea-light.css";
|
||||||
|
|
||||||
|
/* red/green colorblind-friendly colors */
|
||||||
|
/* from GitHub: --diffBlob-addition-*, --diffBlob-deletion-*, etc */
|
||||||
|
:root {
|
||||||
|
--color-diff-added-word-bg: #54aeff66;
|
||||||
|
--color-diff-added-row-bg: #ddf4ff80;
|
||||||
|
|
||||||
|
--color-diff-removed-word-bg: #ffb77c80;
|
||||||
|
--color-diff-removed-row-bg: #fff1e580;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user