theme v1
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{.LangCode}}">
|
||||
<html lang="{{.LangCode}}"{{ if .ThemeSlug }} data-theme-style="{{ .ThemeSlug }}"{{ end }}>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
@@ -20,8 +20,10 @@
|
||||
<link rel="preconnect" href="https://cdnjs.snrat.com" crossorigin>
|
||||
<!-- UIkit 3.3.3 切到公共 CDN(cdnjs.snrat.com,国内实测全量下载 ~0.25s,原 R2 自建 ~1.4-2.0s) -->
|
||||
<link rel="stylesheet" href="https://cdnjs.snrat.com/ajax/libs/uikit/3.3.3/css/uikit.min.css"/>
|
||||
<!-- 全站基础样式(由原内联块抽出,可被浏览器缓存):改 static/css/base.css 后把 v 版本号 +1 -->
|
||||
<link rel="stylesheet" href="/static/css/base.css?v=20261019d"/>
|
||||
<!-- 全站基础样式(由原内联块抽出,可被浏览器缓存):改样式后把 conf.StaticVersion +1 -->
|
||||
<link rel="stylesheet" href="/static/css/base.css?v={{ .StaticVersion }}"/>
|
||||
{{ if .ThemeSlug }}<!-- 箱主主题:只覆盖配色/图案/页面背景,不涉及布局;仅提问箱相关页面加载 -->
|
||||
<link rel="stylesheet" href="/static/css/themes/{{ .ThemeSlug }}.css?v={{ .StaticVersion }}"/>{{ end }}
|
||||
<!-- Waline 评论区样式:非阻塞加载,不阻塞首屏渲染 -->
|
||||
<link rel="stylesheet" href="https://r2.337845818.best/tamabox/cdn/waline/client/v2/waline-no-commits.css" media="print" onload="this.media='all'"/>
|
||||
<script defer src="https://cdnjs.snrat.com/ajax/libs/uikit/3.3.3/js/uikit.min.js"></script>
|
||||
|
||||
@@ -788,6 +788,34 @@
|
||||
<span class="tb-account-note">{{t .LangCode "admin.deactivateHint" | safe}}</span>
|
||||
</div>
|
||||
</section>
|
||||
<!-- 主题外观:只改配色 / 图案 / 整页背景,不涉及布局;仅提问箱相关页面生效 -->
|
||||
{{ if .Themes }}
|
||||
<section class="tb-pcard">
|
||||
<h3 class="tb-pcard-title">{{t .LangCode "profile.theme.title"}}</h3>
|
||||
<form method="post" action="/user/profile/theme">
|
||||
{{ .CSRFTokenHTML }}
|
||||
<p class="tb-theme-hint">{{t .LangCode "profile.theme.hint"}}</p>
|
||||
<div class="tb-theme-grid">
|
||||
<label class="tb-theme-opt">
|
||||
<input class="uk-radio" type="radio" name="theme" value=""{{ if eq .CurrentTheme "" }} checked{{ end }}>
|
||||
<span class="tb-theme-swatch" style="background: linear-gradient(135deg, var(--tb-surface-2) 0 55%, var(--tb-border) 55% 100%)"></span>
|
||||
<span class="tb-theme-name">{{t .LangCode "profile.theme.followDefault"}}</span>
|
||||
</label>
|
||||
{{ range .Themes }}
|
||||
<label class="tb-theme-opt">
|
||||
<input class="uk-radio" type="radio" name="theme" value="{{ .Slug }}"{{ if eq $.CurrentTheme .Slug }} checked{{ end }}>
|
||||
<span class="tb-theme-swatch"{{ if .Swatch }} style="background: {{ .Swatch }}"{{ end }}></span>
|
||||
<span class="tb-theme-name">{{ t $.LangCode (printf "theme.name.%s" .Slug) }}</span>
|
||||
</label>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="tb-card-save">
|
||||
<button type="submit" class="uk-button uk-button-primary">{{t .LangCode "profile.theme.save"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
{{ end }}
|
||||
|
||||
</div><!-- /用户设置面板 -->
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package templates
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -72,6 +73,29 @@ func TestGenPreview(t *testing.T) {
|
||||
writePreview(t, outDir, "preview-box-"+theme+".html", box, theme, "../static")
|
||||
}
|
||||
|
||||
// 主题预览:与箱主页同一份数据,只是打开 sakura 主题(验证令牌覆盖与深色块)
|
||||
themeBox := publicBaseData()
|
||||
themeBox["VConsole"] = false
|
||||
themeBox["ThemeSlug"] = "sakura"
|
||||
themeBox["PageUser"].(map[string]interface{})["Background"] = bgURI
|
||||
themeBox["PageUser"].(map[string]interface{})["Usernamecolor"] = "#3a2b30"
|
||||
themeBox["PageUser"].(map[string]interface{})["Introcolor"] = "#6b545c"
|
||||
themeCSS, err := os.ReadFile(filepath.Join("..", "static", "css", "themes", "sakura.css"))
|
||||
if err != nil {
|
||||
t.Fatalf("read theme css: %v", err)
|
||||
}
|
||||
for _, theme := range []string{"light", "dark"} {
|
||||
page, err := renderToString(t, "question/list", themeBox)
|
||||
if err != nil {
|
||||
t.Fatalf("render themed list: %v", err)
|
||||
}
|
||||
// 预览页走 file://,把主题 css 内联进去(真实页面是一次带缓存的静态请求)。
|
||||
// 用正则匹配:预览数据里没有 StaticVersion,v 参数为空串,不能按固定串替换。
|
||||
link := regexp.MustCompile(`<link rel="stylesheet" href="/static/css/themes/sakura\.css\?v=[^"]*"/>`)
|
||||
page = link.ReplaceAllString(page, "<style>"+string(themeCSS)+"</style>")
|
||||
writePreview(t, outDir, "preview-theme-sakura-"+theme+".html", page, theme, "../static")
|
||||
}
|
||||
|
||||
// 探索页:核对令牌替换后的卡片/列表配色
|
||||
explore := voteBaseData()
|
||||
explore["LangCode"] = "zh-CN"
|
||||
@@ -102,6 +126,12 @@ func TestGenPreview(t *testing.T) {
|
||||
|
||||
// 设置页「内容防护」面板:核对是否与其它面板同样是白底卡片
|
||||
profile := profileBaseData()
|
||||
// 主题选择器(account 面板):用固定假数据,避免测试依赖 conf 的 [theme] 段
|
||||
profile["Themes"] = []map[string]interface{}{
|
||||
{"Slug": "default", "Swatch": ""},
|
||||
{"Slug": "sakura", "Swatch": "linear-gradient(135deg, #fff6f8 0 55%, #e5678f 55% 100%)"},
|
||||
}
|
||||
profile["CurrentTheme"] = "sakura"
|
||||
profile["ActiveProfileTab"] = "security"
|
||||
profile["FeatureQuestion"] = nil
|
||||
profile["FeatureChat"] = nil
|
||||
@@ -113,6 +143,22 @@ func TestGenPreview(t *testing.T) {
|
||||
}
|
||||
writePreview(t, outDir, "preview-profile-"+theme+".html", page, theme, "../static")
|
||||
}
|
||||
|
||||
// 设置页「账号」面板:主题选择器的实际排版(卡片在面板末尾)
|
||||
profileAccount := profileBaseData()
|
||||
profileAccount["Themes"] = profile["Themes"]
|
||||
profileAccount["CurrentTheme"] = "sakura"
|
||||
profileAccount["ActiveProfileTab"] = "account"
|
||||
profileAccount["FeatureQuestion"] = nil
|
||||
profileAccount["FeatureChat"] = nil
|
||||
profileAccount["FeatureVote"] = nil
|
||||
for _, theme := range []string{"light"} {
|
||||
page, err := renderToString(t, "user/profile", profileAccount)
|
||||
if err != nil {
|
||||
t.Fatalf("render account profile: %v", err)
|
||||
}
|
||||
writePreview(t, outDir, "preview-profile-account-"+theme+".html", page, theme, "../static")
|
||||
}
|
||||
}
|
||||
|
||||
// writePreview 把 CDN 换成可直连的镜像、钉死主题,仅用于本地预览。
|
||||
@@ -120,7 +166,16 @@ func writePreview(t *testing.T, dir, name, html, theme, staticPrefix string) {
|
||||
t.Helper()
|
||||
html = strings.ReplaceAll(html, "https://cdnjs.snrat.com/", "https://cdnjs.cloudflare.com/")
|
||||
if theme == "dark" {
|
||||
html = strings.Replace(html, `<html lang="zh-CN">`, `<html lang="zh-CN" data-theme="dark">`, 1)
|
||||
// <html> 上可能还有 data-theme-style,只在这个开标签内判断/插入;
|
||||
// 不能全文搜索 —— 内联的主题 css 里有 html[data-theme="dark"] 选择器
|
||||
if i := strings.Index(html, "<html"); i >= 0 {
|
||||
if j := strings.Index(html[i:], ">"); j > 0 {
|
||||
tag := html[i : i+j]
|
||||
if !strings.Contains(tag, "data-theme=") {
|
||||
html = html[:i+j] + ` data-theme="dark"` + html[i+j:]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
html = strings.ReplaceAll(html, `href="/favicon.ico"`, `href="`+staticPrefix+`/favicon.ico"`)
|
||||
html = strings.Replace(html, `<title>`, `<title>预览 `+time.Now().Format("15:04")+` · `, 1)
|
||||
|
||||
Reference in New Issue
Block a user