Skip to content

Commit cbb63bc

Browse files
committed
fix
1 parent 1a2ae64 commit cbb63bc

File tree

9 files changed

+158
-30
lines changed

9 files changed

+158
-30
lines changed

routers/web/user/setting/profile.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -319,13 +319,7 @@ func Repos(ctx *context.Context) {
319319
func Appearance(ctx *context.Context) {
320320
ctx.Data["Title"] = ctx.Tr("settings.appearance")
321321
ctx.Data["PageIsSettingsAppearance"] = true
322-
323-
allThemes := webtheme.GetAvailableThemes()
324-
if webtheme.IsThemeAvailable(setting.UI.DefaultTheme) {
325-
allThemes = util.SliceRemoveAll(allThemes, setting.UI.DefaultTheme)
326-
allThemes = append([]string{setting.UI.DefaultTheme}, allThemes...) // move the default theme to the top
327-
}
328-
ctx.Data["AllThemes"] = allThemes
322+
ctx.Data["AllThemes"] = webtheme.GetAvailableThemes()
329323

330324
var hiddenCommentTypes *big.Int
331325
val, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyHiddenCommentTypes)

services/webtheme/webtheme.go

Lines changed: 116 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package webtheme
55

66
import (
7+
"regexp"
78
"sort"
89
"strings"
910
"sync"
@@ -12,63 +13,156 @@ import (
1213
"code.gitea.io/gitea/modules/log"
1314
"code.gitea.io/gitea/modules/public"
1415
"code.gitea.io/gitea/modules/setting"
16+
"code.gitea.io/gitea/modules/util"
1517
)
1618

1719
var (
18-
availableThemes []string
19-
availableThemesSet container.Set[string]
20-
themeOnce sync.Once
20+
availableThemes []*ThemeMetaInfo
21+
availableThemeInternalNames container.Set[string]
22+
themeOnce sync.Once
2123
)
2224

25+
const (
26+
fileNamePrefix = "theme-"
27+
fileNameSuffix = ".css"
28+
)
29+
30+
type ThemeMetaInfo struct {
31+
FileName string
32+
InternalName string
33+
DisplayName string
34+
PreferColorSchemes container.Set[string]
35+
}
36+
37+
func parseThemeMetaInfoToMap(cssContent string) map[string]string {
38+
metaInfoContent := cssContent
39+
if pos := strings.LastIndex(metaInfoContent, "gitea-theme-meta-info"); pos >= 0 {
40+
metaInfoContent = metaInfoContent[pos:]
41+
}
42+
43+
reMetaInfoItem := `
44+
(
45+
\s*(--[-\w]+)
46+
\s*:
47+
\s*("(\\"|[^"])*")
48+
\s*;
49+
\s*
50+
)
51+
`
52+
reMetaInfoItem = strings.ReplaceAll(reMetaInfoItem, "\n", "")
53+
reMetaInfoBlock := `\bgitea-theme-meta-info\s*\{(` + reMetaInfoItem + `+)\}`
54+
re := regexp.MustCompile(reMetaInfoBlock)
55+
matchedMetaInfoBlock := re.FindAllStringSubmatch(metaInfoContent, -1)
56+
if len(matchedMetaInfoBlock) == 0 {
57+
return nil
58+
}
59+
re = regexp.MustCompile(strings.ReplaceAll(reMetaInfoItem, "\n", ""))
60+
matchedItems := re.FindAllStringSubmatch(matchedMetaInfoBlock[0][1], -1)
61+
m := map[string]string{}
62+
for _, item := range matchedItems {
63+
v := item[3]
64+
v = strings.TrimPrefix(v, "\"")
65+
v = strings.TrimSuffix(v, "\"")
66+
v = strings.ReplaceAll(v, `\"`, `"`)
67+
m[item[2]] = v
68+
}
69+
return m
70+
}
71+
72+
// @media (prefers-color-scheme: dark)
73+
func parseThemePreferColorSchemes(cssContent string) container.Set[string] {
74+
re := regexp.MustCompile(`@media\s*\(\s*prefers-color-scheme\s*:\s*([-\w]+)\s*\)`)
75+
matched := re.FindAllStringSubmatch(cssContent, -1)
76+
if len(matched) == 0 {
77+
return nil
78+
}
79+
schemes := container.Set[string]{}
80+
for _, m := range matched {
81+
schemes.Add(m[1])
82+
}
83+
return schemes
84+
}
85+
86+
func defaultThemeMetaInfoByFileName(fileName string) *ThemeMetaInfo {
87+
themeInfo := &ThemeMetaInfo{
88+
FileName: fileName,
89+
InternalName: strings.TrimSuffix(strings.TrimPrefix(fileName, fileNamePrefix), fileNameSuffix),
90+
}
91+
themeInfo.DisplayName = themeInfo.InternalName
92+
return themeInfo
93+
}
94+
95+
func defaultThemeMetaInfoByInternalName(fileName string) *ThemeMetaInfo {
96+
return defaultThemeMetaInfoByFileName(fileNamePrefix + fileName + fileNameSuffix)
97+
}
98+
99+
func parseThemeMetaInfo(fileName, cssContent string) *ThemeMetaInfo {
100+
themeInfo := defaultThemeMetaInfoByFileName(fileName)
101+
themeInfo.PreferColorSchemes = parseThemePreferColorSchemes(cssContent)
102+
m := parseThemeMetaInfoToMap(cssContent)
103+
if m == nil {
104+
return themeInfo
105+
}
106+
themeInfo.DisplayName = m["--theme-display-name"]
107+
return themeInfo
108+
}
109+
23110
func initThemes() {
24111
availableThemes = nil
25112
defer func() {
26-
availableThemesSet = container.SetOf(availableThemes...)
27-
if !availableThemesSet.Contains(setting.UI.DefaultTheme) {
113+
availableThemeInternalNames = container.Set[string]{}
114+
for _, theme := range availableThemes {
115+
availableThemeInternalNames.Add(theme.InternalName)
116+
}
117+
if !availableThemeInternalNames.Contains(setting.UI.DefaultTheme) {
28118
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)
29119
}
30120
}()
31121
cssFiles, err := public.AssetFS().ListFiles("/assets/css")
32122
if err != nil {
33123
log.Error("Failed to list themes: %v", err)
34-
availableThemes = []string{setting.UI.DefaultTheme}
124+
availableThemes = []*ThemeMetaInfo{defaultThemeMetaInfoByInternalName(setting.UI.DefaultTheme)}
35125
return
36126
}
37-
var foundThemes []string
38-
for _, name := range cssFiles {
39-
name, ok := strings.CutPrefix(name, "theme-")
40-
if !ok {
41-
continue
42-
}
43-
name, ok = strings.CutSuffix(name, ".css")
44-
if !ok {
45-
continue
127+
var foundThemes []*ThemeMetaInfo
128+
for _, fileName := range cssFiles {
129+
if strings.HasPrefix(fileName, fileNamePrefix) && strings.HasSuffix(fileName, fileNameSuffix) {
130+
content, err := public.AssetFS().ReadFile("/assets/css/" + fileName)
131+
if err != nil {
132+
log.Error("Failed to read theme file %q: %v", fileName, err)
133+
continue
134+
}
135+
foundThemes = append(foundThemes, parseThemeMetaInfo(fileName, util.UnsafeBytesToString(content)))
46136
}
47-
foundThemes = append(foundThemes, name)
48137
}
49138
if len(setting.UI.Themes) > 0 {
50139
allowedThemes := container.SetOf(setting.UI.Themes...)
51140
for _, theme := range foundThemes {
52-
if allowedThemes.Contains(theme) {
141+
if allowedThemes.Contains(theme.InternalName) {
53142
availableThemes = append(availableThemes, theme)
54143
}
55144
}
56145
} else {
57146
availableThemes = foundThemes
58147
}
59-
sort.Strings(availableThemes)
148+
sort.Slice(availableThemes, func(i, j int) bool {
149+
if availableThemes[i].InternalName == setting.UI.DefaultTheme {
150+
return true
151+
}
152+
return availableThemes[i].DisplayName < availableThemes[j].DisplayName
153+
})
60154
if len(availableThemes) == 0 {
61155
setting.LogStartupProblem(1, log.ERROR, "No theme candidate in asset files, but Gitea requires there should be at least one usable theme")
62-
availableThemes = []string{setting.UI.DefaultTheme}
156+
availableThemes = []*ThemeMetaInfo{defaultThemeMetaInfoByInternalName(setting.UI.DefaultTheme)}
63157
}
64158
}
65159

66-
func GetAvailableThemes() []string {
160+
func GetAvailableThemes() []*ThemeMetaInfo {
67161
themeOnce.Do(initThemes)
68162
return availableThemes
69163
}
70164

71-
func IsThemeAvailable(name string) bool {
165+
func IsThemeAvailable(internalName string) bool {
72166
themeOnce.Do(initThemes)
73-
return availableThemesSet.Contains(name)
167+
return availableThemeInternalNames.Contains(internalName)
74168
}

services/webtheme/webtheme_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package webtheme
5+
6+
import (
7+
"testing"
8+
9+
"code.gitea.io/gitea/modules/container"
10+
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func TestParseThemeMetaInfo(t *testing.T) {
15+
m := parseThemeMetaInfoToMap(`gitea-theme-meta-info { --k1: "v1"; --k2: "a\"b"; }`)
16+
assert.Equal(t, map[string]string{"--k1": "v1", "--k2": `a"b`}, m)
17+
18+
schemes := parseThemePreferColorSchemes(`@media (prefers-color-scheme: dark) {} @media (prefers-color-scheme: light) {} @media (prefers-color-scheme:dark) {}`)
19+
assert.Equal(t, container.SetOf("dark", "light"), schemes)
20+
}

templates/user/settings/appearance.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<label>{{ctx.Locale.Tr "settings.ui"}}</label>
1919
<select name="theme" class="ui dropdown">
2020
{{range $theme := .AllThemes}}
21-
<option value="{{$theme}}" {{Iif (eq $.SignedUser.Theme $theme) "selected"}}>{{$theme}}</option>
21+
<option value="{{$theme.InternalName}}" {{Iif (eq $.SignedUser.Theme $theme.InternalName) "selected"}}>{{$theme.DisplayName}}</option>
2222
{{end}}
2323
</select>
2424
</div>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
@import "./theme-gitea-light.css" (prefers-color-scheme: light);
22
@import "./theme-gitea-dark.css" (prefers-color-scheme: dark);
3+
4+
gitea-theme-meta-info {
5+
--theme-display-name: "Auto";
6+
}

web_src/css/themes/theme-gitea-dark-protanopia-deuteranopia.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
@import "./theme-gitea-dark.css";
22

3+
gitea-theme-meta-info {
4+
--theme-display-name: "Dark (Red/Green Colorblind-Friendly)";
5+
}
6+
37
/* red/green colorblind-friendly colors */
48
/* from GitHub: --diffBlob-addition-*, --diffBlob-deletion-*, etc */
59
:root {

web_src/css/themes/theme-gitea-dark.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
@import "../chroma/dark.css";
22
@import "../codemirror/dark.css";
33

4+
gitea-theme-meta-info {
5+
--theme-display-name: "Dark";
6+
}
7+
48
:root {
59
--is-dark-theme: true;
610
--color-primary: #4183c4;

web_src/css/themes/theme-gitea-light-protanopia-deuteranopia.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
@import "./theme-gitea-light.css";
22

3+
gitea-theme-meta-info {
4+
--theme-display-name: "Light (Red/Green Colorblind-Friendly)";
5+
}
6+
37
/* red/green colorblind-friendly colors */
48
/* from GitHub: --diffBlob-addition-*, --diffBlob-deletion-*, etc */
59
:root {

web_src/css/themes/theme-gitea-light.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
@import "../chroma/light.css";
22
@import "../codemirror/light.css";
33

4+
gitea-theme-meta-info {
5+
--theme-display-name: "Light";
6+
}
7+
48
:root {
59
--is-dark-theme: false;
610
--color-primary: #4183c4;

0 commit comments

Comments
 (0)