Add built-in white and black themes
This commit is contained in:
1 parent
7f65162115
commit
9fd7e2a948
6 files changed
+103
-5
No files matched your search
@@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -19,6 +20,29 @@ type themeManifest struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
//go:embed themes/white.css
|
||||
var whiteThemeCSS string
|
||||
|
||||
//go:embed themes/black.css
|
||||
var blackThemeCSS string
|
||||
|
||||
var builtinThemes = []struct {
|
||||
themeManifest
|
||||
css string
|
||||
}{
|
||||
{themeManifest{"white", "白色", "1"}, whiteThemeCSS},
|
||||
{themeManifest{"black", "黑色", "1"}, blackThemeCSS},
|
||||
}
|
||||
|
||||
func builtinTheme(id string) (themeManifest, string, bool) {
|
||||
for _, theme := range builtinThemes {
|
||||
if theme.ID == id {
|
||||
return theme.themeManifest, theme.css, true
|
||||
}
|
||||
}
|
||||
return themeManifest{}, "", false
|
||||
}
|
||||
|
||||
func readThemeFile(root *os.Root, name string, limit int64) ([]byte, error) {
|
||||
f, err := root.Open(name)
|
||||
if err != nil {
|
||||
@@ -43,6 +67,9 @@ func readThemeFile(root *os.Root, name string, limit int64) ([]byte, error) {
|
||||
}
|
||||
|
||||
func (s *Server) readTheme(id string) (themeManifest, string, error) {
|
||||
if manifest, css, ok := builtinTheme(id); ok {
|
||||
return manifest, css, nil
|
||||
}
|
||||
var manifest themeManifest
|
||||
if !themeIDPattern.MatchString(id) || id == "default" || s.ThemesDir == "" {
|
||||
return manifest, "", fs.ErrNotExist
|
||||
@@ -73,6 +100,9 @@ func (s *Server) readTheme(id string) (themeManifest, string, error) {
|
||||
|
||||
func (s *Server) listThemes(w http.ResponseWriter, r *http.Request) {
|
||||
themes := []themeManifest{{ID: "default", Name: "默认", Version: "1"}}
|
||||
for _, theme := range builtinThemes {
|
||||
themes = append(themes, theme.themeManifest)
|
||||
}
|
||||
if s.ThemesDir != "" {
|
||||
entries, err := os.ReadDir(s.ThemesDir)
|
||||
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
@@ -80,6 +110,9 @@ func (s *Server) listThemes(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if _, _, ok := builtinTheme(entry.Name()); ok {
|
||||
continue
|
||||
}
|
||||
if !entry.IsDir() || !themeIDPattern.MatchString(entry.Name()) || entry.Name() == "default" {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--rm-bg: #0b0b0c;
|
||||
--rm-sidebar-bg: #111113;
|
||||
--rm-text: #e6e8eb;
|
||||
--rm-text-muted: #9b9fa8;
|
||||
--rm-divider: #2b2d33;
|
||||
--rm-accent: #8eafff;
|
||||
--rm-accent-strong: #b3c9ff;
|
||||
--rm-selection: rgba(142, 175, 255, 0.25);
|
||||
--rm-hover: rgba(230, 232, 235, 0.065);
|
||||
--rm-active: rgba(142, 175, 255, 0.14);
|
||||
--rm-danger: #ff8291;
|
||||
--rm-warn-bg: #2c2413;
|
||||
--rm-warn-text: #e8c477;
|
||||
--rm-shadow: 0 12px 36px rgba(0, 0, 0, 0.6);
|
||||
--rm-inline-code-bg: #202127;
|
||||
--rm-inline-code-text: #c3d5f5;
|
||||
--rm-code-bg: #18191e;
|
||||
--rm-code-text: #e1e4e8;
|
||||
--rm-highlight-bg: rgba(232, 196, 119, 0.25);
|
||||
}
|
||||
|
||||
/* 浅蓝按钮使用深色文字,保持选中状态与操作按钮的对比度。 */
|
||||
.rm-btn-primary,
|
||||
.rm-btn-primary:hover,
|
||||
.rm-segmented button.on,
|
||||
.rm-share-password button {
|
||||
color: #0b0b0c;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--rm-bg: #ffffff;
|
||||
--rm-sidebar-bg: #f6f7f9;
|
||||
--rm-text: #202124;
|
||||
--rm-text-muted: #656b75;
|
||||
--rm-divider: #e3e6eb;
|
||||
--rm-accent: #2563eb;
|
||||
--rm-accent-strong: #1d4ed8;
|
||||
--rm-selection: rgba(37, 99, 235, 0.18);
|
||||
--rm-hover: rgba(32, 33, 36, 0.045);
|
||||
--rm-active: rgba(37, 99, 235, 0.10);
|
||||
--rm-danger: #c42b3a;
|
||||
--rm-warn-bg: #fff8e6;
|
||||
--rm-warn-text: #855600;
|
||||
--rm-shadow: 0 12px 36px rgba(24, 31, 44, 0.12);
|
||||
--rm-inline-code-bg: #f0f2f5;
|
||||
--rm-inline-code-text: #40536d;
|
||||
--rm-code-bg: #292d3e;
|
||||
--rm-code-text: #e1e4e8;
|
||||
--rm-highlight-bg: #fff0a8;
|
||||
}
|
||||
@@ -29,9 +29,15 @@ func TestThemeAndShareAppearanceBoundaries(t *testing.T) {
|
||||
t.Fatalf("匿名用户不应读取主题,得到 %d", code)
|
||||
}
|
||||
themes := decode[struct{ Themes []themeManifest }](t, f.do(f.admin, "GET", "/api/v1/themes", "", nil))
|
||||
if len(themes.Themes) != 2 || themes.Themes[1].ID != "paper" {
|
||||
if len(themes.Themes) != 4 || themes.Themes[0].ID != "default" || themes.Themes[1].ID != "white" || themes.Themes[2].ID != "black" || themes.Themes[3].ID != "paper" {
|
||||
t.Fatalf("主题列表错误:%+v", themes.Themes)
|
||||
}
|
||||
for _, id := range []string{"white", "black"} {
|
||||
theme := decode[map[string]string](t, f.do(f.admin, "GET", "/api/v1/themes/"+id, "", nil))
|
||||
if theme["id"] != id || !strings.Contains(theme["css"], "--rm-bg:") {
|
||||
t.Fatalf("内置主题未提供配色:%s", id)
|
||||
}
|
||||
}
|
||||
theme := decode[map[string]string](t, f.do(f.admin, "GET", "/api/v1/themes/paper", "", nil))
|
||||
if !strings.Contains(theme["css"], "#faf6ed") {
|
||||
t.Fatal("未读取到主题 CSS")
|
||||
|
||||
Reference in new issue
Block a user