From 6c014b3ad279a9f878c1edd58f3bece50184ac42 Mon Sep 17 00:00:00 2001 From: tamakyi Date: Tue, 20 Oct 2026 18:23:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=20test01=EF=BC=9A=E7=AA=84=E5=B1=8F?= =?UTF-8?q?=E8=A3=85=E9=A5=B0=E5=85=A8=E8=97=8F=E5=AF=BC=E8=87=B4=E7=B4=A0?= =?UTF-8?q?=E6=9D=90=E6=B6=88=E5=A4=B1=E3=80=81=E4=B8=BB=E9=A2=98=E5=90=8D?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E6=88=90=20i18n=20key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 生成器:勾「窄屏隐藏全部装饰层」时给橙色警告(几层会消失)、图层行标「窄屏隐藏」、加层与导出结果再提醒;「窄屏」下拉在被全藏覆盖时标注不生效 - 新增 ?narrow=hide 复现该状态(这是「素材在手机上看不到」的头号来源:@media 把 10 槽全写 none,桌面完全正常) - 主题名:新增 theme.DisplayName + 模板函数 themeName,i18n 未登记 theme.name. 时回退 slug;原来模板直接调用 i18n.T,缺词条就显示 "theme.name.test01" - test01.css 删掉 @media 全 none 段:装饰是 fit 单位,浏览器会自己按视口缩,不需要这段 - StaticVersion → 20261020g(主题 css 内容变了,必须换缓存键,否则浏览器还会用旧文件) --- dev/theme-builder/README.md | 9 ++++ dev/theme-builder/index.html | 66 +++++++++++++++++++++-- internal/conf/static.go | 2 +- internal/template/template.go | 7 +++ internal/theme/theme.go | 15 ++++++ internal/theme/theme_test.go | 15 ++++++ static/css/themes/test01.css | 99 ----------------------------------- templates/user/profile.html | 2 +- 8 files changed, 110 insertions(+), 105 deletions(-) diff --git a/dev/theme-builder/README.md b/dev/theme-builder/README.md index 416911d..14760f3 100644 --- a/dev/theme-builder/README.md +++ b/dev/theme-builder/README.md @@ -216,6 +216,14 @@ INSTALL-.txt 2. `internal/conf/static.go` 的 `StaticVersion` +1; 3. `go build`(模板与静态资源都是编译期 embed,只重启不重编不会变)。 +设置页下拉里显示的**就是 slug**。想要中文/日文名,到 `internal/i18n/dict_*.go` 加 +`theme.name.` 词条(四份词典都要加)。不加也不会露 key —— 模板走 `themeName` +函数,缺词条时回退成 slug(见 `internal/theme.DisplayName`)。 + +⚠️ **窄屏段导出前先看一眼「窄屏隐藏全部装饰层」有没有勾着**:勾着的话 `@media` 会把 +10 个槽位全写成 `none`,桌面完全正常、手机上一个素材都没有 —— 这是「素材在窄屏下消失」 +最常见的来源。现在勾选时面板会给橙色警告、图层行标「窄屏隐藏」、加层和导出时也会再提醒一次。 + ## 它不做什么 - **不做复杂修图**:没有笔刷、蒙版、自由裁切、调色曲线 —— 这些该在专门的工具里做完再进来。 @@ -231,6 +239,7 @@ INSTALL-.txt ``` ?demo=1 载入示例素材(canvas 画的背景 + 剪影人物) +?narrow=hide 直接落在「窄屏隐藏全部装饰层」状态(复现「素材在手机上不见了」) ?rows=2 加 2 层并依次绑到不同素材(截图多图层效果用) ?scope=admin 直接落在某个作用域 ?fresh=1 忽略 localStorage 草稿,从空白开始 diff --git a/dev/theme-builder/index.html b/dev/theme-builder/index.html index 801b14d..c591262 100644 --- a/dev/theme-builder/index.html +++ b/dev/theme-builder/index.html @@ -1091,6 +1091,9 @@ function renderLayers(){ const d = document.createElement('div'); d.className = 'ly' + (l.id === S.curLayer ? ' on' : ''); const a = l.kind === 'image' ? assetById(l.asset) : null; + // 窄屏下这一层会不会消失:全局「全藏」优先,其次是层自己的策略。 + // 标出来是因为「素材在手机上不见了」绝大多数就是这个,而桌面预览完全正常。 + const mobOff = S.narrow.enabled && (S.narrow.hideDeco || (l.mob || 'inherit') === 'hide'); const color = l.kind === 'image' ? '#5b9bd5' : l.kind === 'radial' ? '#d8a657' : '#9b7fd0'; const thumb = l.kind !== 'image' ? `~` @@ -1099,7 +1102,7 @@ function renderLayers(){ d.innerHTML = `
${thumb} ${esc(layerName(l))} - ${i === 0 ? '最上层' : '第 ' + (i + 1) + ' 层'} + ${i === 0 ? '最上层' : '第 ' + (i + 1) + ' 层'}${mobOff ? ' · 窄屏隐藏' : ''} @@ -1212,6 +1215,14 @@ function renderLayerEditor(l){ row('窄屏', sel([['inherit','原样'],['scale',`缩小到 ${Math.round((S.narrow.scale||MOB_SCALE_DEFAULT)*100)}%`],['hide','隐藏']], l.mob || 'inherit', v => { l.mob = v; renderAll(); }, '只在 @media 断点以下生效;缩小是把高度与偏移一起乘系数,位置锚点不变')); + // 全局「全藏」开着时这一项不生效 —— 不写出来,用户会以为自己设的缩小没起作用 + if (S.narrow.enabled && S.narrow.hideDeco){ + const ntip = document.createElement('p'); + ntip.className = 'hint'; + ntip.style.color = '#e0a33e'; + ntip.textContent = '↑ 被「窄屏隐藏全部装饰层」覆盖,这一项当前不生效'; + box.appendChild(ntip); + } const a0 = assetById(l.asset); if (a0){ @@ -1402,7 +1413,25 @@ function renderNarrowEditor(){ row2.append(sp2, sel); el.appendChild(row2); - el.appendChild(mk('隐藏全部装饰层(急停)', n.hideDeco, v => { n.hideDeco = v; renderAll(); })); + el.appendChild(mk('窄屏隐藏全部装饰层', n.hideDeco, v => { n.hideDeco = v; renderAll(); })); + + // 这个开关一旦勾上,用户新加的素材在手机上也一起消失,而桌面预览完全正常 + // —— 是「素材在窄屏下不见了」最常见的来源。所以勾上时必须当场把后果写出来, + // 不能只靠一个复选框的标题。 + if (n.hideDeco){ + let cnt = 0; + SCOPES.forEach(sc => { + const c = S.scopes[sc.k]; + if (sc.k !== 'global' && c.inherit) return; + cnt += (c.layers || []).length; + }); + const warn = document.createElement('p'); + warn.className = 'hint'; + warn.style.color = '#e0a33e'; + warn.textContent = `⚠ 断点以下 ${cnt} 层全不显示,新加的层也一样。` + + '要在手机上保留素材:取消勾选,改用每层「窄屏」。'; + el.appendChild(warn); + } const tip = document.createElement('p'); tip.className = 'hint'; @@ -1832,6 +1861,9 @@ function installTxt(slug){ 然后重新 go build —— 模板与静态资源都是编译期 embed,只重启不重编不会变。 4) 箱主设置页 → 账号 → 外观,选择「${slug}」 + 下拉里显示的就是 slug。想要中文/日文名,到 internal/i18n/dict_*.go 加 + theme.name.${slug} 词条(四份词典都要加)。不加也不会显示成 key —— + 模板函数 themeName 会回退成 slug。 页面作用域 ---------- @@ -1841,8 +1873,11 @@ ${own} ---- ${S.narrow.enabled ? `已导出 @media (max-width: ${S.narrow.maxWidth}px):${S.narrow.bg ? '换成竖版背景图' : '背景沿用桌面'}` - + `${S.narrow.hideDeco ? ' + 隐藏全部装饰层' : ''}` + `\n 缩放系数 ${Math.round((S.narrow.scale || MOB_SCALE_DEFAULT) * 100)}%(作用于策略为「缩小」的层)` + + (S.narrow.hideDeco + ? '\n ⚠ 「窄屏隐藏全部装饰层」已勾选:断点以下所有素材都不显示(桌面正常)。\n' + + ' 想保留就把这个勾去掉,改用每层「窄屏 = 原样 / 缩小」。' + : '') : '未导出窄屏规则(桌面装饰会在手机上被卡片盖住 / 背景被 cover 裁成一条)'} 尺寸适配 @@ -1911,12 +1946,26 @@ function doExport(){ setTimeout(() => URL.revokeObjectURL(a.href), 4000); const fmt = kb => kb >= 1024 ? (kb / 1024).toFixed(2) + 'MB' : kb + 'KB'; const over = overflowReport(); + // 窄屏全藏是「素材不见了」的头号原因,导出时必须写进结果里 —— + // 用户只看桌面预览是发现不了的。 + let narrowNote = ''; + if (S.narrow.enabled && S.narrow.hideDeco){ + let cnt = 0; + SCOPES.forEach(sc => { + const c = S.scopes[sc.k]; + if (sc.k !== 'global' && c.inherit) return; + cnt += (c.layers || []).length; + }); + narrowNote = `\n\n⚠ 窄屏规则(≤${S.narrow.maxWidth}px)会隐藏全部 ${cnt} 层装饰。` + + '这是「窄屏隐藏全部装饰层」勾选的结果;要在手机上保留素材就取消它,改用每层「窄屏」策略。'; + } out(`已导出 theme-${slug}.zip(${files.length} 个文件,${fmt(Math.round(blob.size / 1024))};` + `其中 ${imgN} 张图共 ${fmt(imgKB)})。\n` + `解压到项目根,按 INSTALL-${slug}.txt 走三步;包里的 theme-${slug}.json 是工程源文件,下次用「打开工程」接着改。` + (over.length ? `\n\n⚠ ${over.length} 处素材在部分视口下会被裁(不阻止导出,供你判断):\n` + over.slice(0, 6).map(s => '· ' + s).join('\n') - + (over.length > 6 ? `\n…… 另有 ${over.length - 6} 处` : '') : ''), over.length ? 'w' : 'ok'); + + (over.length > 6 ? `\n…… 另有 ${over.length - 6} 处` : '') : '') + + narrowNote, (over.length || narrowNote) ? 'w' : 'ok'); } /** 全站扫一遍:哪些层在预设视口下会溢出被裁。 @@ -2553,6 +2602,9 @@ function boot(){ if (kind === 'image' && !l.asset){ log('新图层还没选图片 —— 在这一层的「图片」下拉里挑一张', 'w'); } + if (S.narrow.enabled && S.narrow.hideDeco){ + log('注意:「窄屏隐藏全部装饰层」开着 —— 这一层在手机/窄窗口下也不会显示', 'w'); + } }; $('btn-save').onclick = () => { const errs = validate(); @@ -2603,6 +2655,12 @@ function boot(){ window.addEventListener('resize', fitStage); if (params.get('demo')) makeDemo(); + // ?narrow=hide 直接落在「窄屏隐藏全部装饰层」状态:截图与回归用。 + // 这个状态是「素材在手机上不见了」最典型的来源,得能一键复现。 + if (params.get('narrow') === 'hide'){ + S.narrow.hideDeco = true; + renderAll(); + } // ?rows=N 加 N 层并依次绑到不同素材(截图 / 人工核对多图层效果用) const rows = parseInt(params.get('rows') || '0', 10); for (let i = 0; i < rows; i++){ diff --git a/internal/conf/static.go b/internal/conf/static.go index b062fd8..38fbed9 100644 --- a/internal/conf/static.go +++ b/internal/conf/static.go @@ -10,7 +10,7 @@ var ( BuildCommit = "dev" // StaticVersion 静态资源缓存版本号:base.css 与主题 css 共用,改样式只改这一处。 - StaticVersion = "20261020f" + StaticVersion = "20261020g" ) // 文本审核通道取值([security] text_censor_source)。定义在 conf 侧, diff --git a/internal/template/template.go b/internal/template/template.go index dd431c2..284364b 100644 --- a/internal/template/template.go +++ b/internal/template/template.go @@ -19,6 +19,7 @@ import ( "github.com/tamakyi/TamaBox/internal/conf" "github.com/tamakyi/TamaBox/internal/i18n" "github.com/tamakyi/TamaBox/internal/security/moderation" + "github.com/tamakyi/TamaBox/internal/theme" ) var ( @@ -192,6 +193,12 @@ func FuncMap() []template.FuncMap { "langName": func(lang string) string { return i18n.Name(lang) }, + // themeName 返回主题显示名:i18n 没有 theme.name. 时回退成 slug 本身。 + // 别在模板里直接 {{t .LangCode (printf "theme.name.%s" .Slug)}} —— + // i18n.T 缺词条返回 key,设置页就会显示 "theme.name.test01"。 + "themeName": func(lang, slug string) string { + return theme.DisplayName(lang, slug) + }, // moderationStrictness 返回账号「最终生效」的审核严格度档位(lax / standard / strict)。 // diff --git a/internal/theme/theme.go b/internal/theme/theme.go index aacda6e..0ee00a0 100644 --- a/internal/theme/theme.go +++ b/internal/theme/theme.go @@ -14,6 +14,7 @@ import ( "github.com/sirupsen/logrus" "github.com/tamakyi/TamaBox/internal/conf" + "github.com/tamakyi/TamaBox/internal/i18n" "github.com/tamakyi/TamaBox/static" ) @@ -46,6 +47,20 @@ var ( siteDef string // 站点默认主题;空串表示「基础外观,不加载主题 css」 ) +// DisplayName 返回主题在设置页里的显示名。 +// +// 优先取 i18n 的 theme.name.;**没登记就原样返回 slug**。 +// 这条回退是必需的:自建主题(生成器导出的)只放一个 css 就到手, +// 不该为了显示名去改四份词典 —— 否则设置页会把 key 直接印出来(theme.name.test01), +// 而 i18n.T 缺词条时返回 key 本身,调用方无从判断「是缺失还是文案就叫这个」。 +func DisplayName(lang, slug string) string { + key := "theme.name." + slug + if name := i18n.T(lang, key); name != key { + return name + } + return slug +} + func load() { once.Do(func() { themes, siteDef, bySlug = build(conf.Theme.Default, conf.Theme.Available) diff --git a/internal/theme/theme_test.go b/internal/theme/theme_test.go index 2a5e315..71a953b 100644 --- a/internal/theme/theme_test.go +++ b/internal/theme/theme_test.go @@ -115,3 +115,18 @@ func TestRuleBodyIgnoresComments(t *testing.T) { assert.Equal(t, "#ffffff", tokenValue(body, "--tb-bg")) assert.Equal(t, "rgba(74, 127, 193, 0.11)", tokenValue(body, "--tb-primary")) } + +// DisplayName 的回退是「自建主题零改代码」的前提: +// 生成器导出的主题只放一个 css,没人会为了名字去改四份词典。 +// 一旦回退错,设置页会直接把 key 印出来(theme.name.test01), +// 用户看到的就是「主题名变成了键名」。 +func TestDisplayName(t *testing.T) { + // 已登记词条的仍走翻译 + assert.Equal(t, "基础外观", DisplayName("zh-CN", DefaultSlug)) + assert.Equal(t, "Shiroko", DisplayName("en", "shiroko")) + + // 没登记词条的自建主题:回退 slug,且绝不能返回 key 本身 + assert.Equal(t, "test01", DisplayName("zh-CN", "test01")) + assert.Equal(t, "test01", DisplayName("ja", "test01")) + assert.Equal(t, "my-theme", DisplayName("en", "my-theme")) +} diff --git a/static/css/themes/test01.css b/static/css/themes/test01.css index 409d93c..e65f80f 100644 --- a/static/css/themes/test01.css +++ b/static/css/themes/test01.css @@ -169,102 +169,3 @@ html[data-theme="dark"][data-theme-style="test01"][data-page-scope="admin"] { --tb-deco-size: auto, auto, auto, auto, auto, auto, auto, auto, auto, auto; --tb-deco-position: left top, left top, left top, left top, left top, left top, left top, left top, left top, left top; } - -/* 窄屏:卡片几乎占满宽度,两侧没有留白,桌面那套背景与装饰在这里只会被裁/被盖。 - 每个独立配置的页面都要写一条 —— 页面覆盖块特异性更高(0,2,1), - 只写 html[data-theme-style] 的话,独立配置的页面在手机上根本不生效。 */ -@media (max-width: 900px) { - html[data-theme-style="test01"] { - --tb-deco: none, - none, - none, - none, - none, - none, - none, - none, - none, - none; - --tb-deco-size: auto, auto, auto, auto, auto, auto, auto, auto, auto, auto; - --tb-deco-position: left top, - left top, - left top, - left top, - left top, - left top, - left top, - left top, - left top, - left top; - } - html[data-theme-style="test01"][data-page-scope="box"] { - --tb-page-position: center center; - --tb-deco: none, - none, - none, - none, - none, - none, - none, - none, - none, - none; - --tb-deco-size: auto, auto, auto, auto, auto, auto, auto, auto, auto, auto; - --tb-deco-position: left top, - left top, - left top, - left top, - left top, - left top, - left top, - left top, - left top, - left top; - } - html[data-theme-style="test01"][data-page-scope="question"] { - --tb-deco: none, - none, - none, - none, - none, - none, - none, - none, - none, - none; - --tb-deco-size: auto, auto, auto, auto, auto, auto, auto, auto, auto, auto; - --tb-deco-position: left top, - left top, - left top, - left top, - left top, - left top, - left top, - left top, - left top, - left top; - } - html[data-theme-style="test01"][data-page-scope="admin"] { - --tb-deco: none, - none, - none, - none, - none, - none, - none, - none, - none, - none; - --tb-deco-size: auto, auto, auto, auto, auto, auto, auto, auto, auto, auto; - --tb-deco-position: left top, - left top, - left top, - left top, - left top, - left top, - left top, - left top, - left top, - left top; - } -} diff --git a/templates/user/profile.html b/templates/user/profile.html index 99e59f4..65a20b5 100644 --- a/templates/user/profile.html +++ b/templates/user/profile.html @@ -805,7 +805,7 @@ {{ end }}