diff --git a/.workbuddy/memory/2026-10-19.md b/.workbuddy/memory/2026-10-19.md index bc87fa3..08972db 100644 --- a/.workbuddy/memory/2026-10-19.md +++ b/.workbuddy/memory/2026-10-19.md @@ -406,3 +406,31 @@ P1 剩余两项(首屏跟随系统深色、首页 Waline 懒加载)用户明 验证:`go build ./...`(须带 webp overlay)、i18n/theme/context/templates 四个包测试全绿。 渲染法:抓线上 `/_/admin6` HTML → 把 base.css 指到本地新文件、主题 css 内联 → 无头 Chrome `--headless=old` 截图。 + +## 投票页 + 标签主色(部署复核后第二轮) + +- `/vote/{token}` 主题:`route/vote/vote.go` 的 `Page` 里本来就会 `db.Users.GetByID(vote.UserID)` 取 owner, + 直接在同一分支补 `ctx.Data["ThemeSlug"] = theme.Resolve(owner.Theme)`;box 页 / /user 后台 / 投票页三处 + 全部由发布者配置决定。GetByID 失败(箱主已删)保持站点默认外观。 +- **`uk-label` 默认色就是 UIkit 主色 #1e87f0**:主题下提问管理的「未回答 / 未读」两枚胶囊还是蓝的。 + base.css 追加 + `.uk-label:not(.uk-label-danger):not(.uk-label-warning):not(.uk-label-success):not(.uk-label-secondary)` + → `--tb-primary`。**必须用 :not() 排除语义色**:同特异性且 base.css 在 UIkit 之后,不排除会把 + danger/warning/success 一起刷成主色。默认主题实测仍是 (30,135,240),零回归。 +- StaticVersion → `20261019g`。 +- 线上复核(admin6 专属账号,curl 走完整登录:GET /login 取 _csrf → /api/captcha/generate → + /api/captcha/verify(**answer 必须是数字,传字符串会 400 请求格式错误**)→ POST /login): + `/user/profile`、`/user/questions` 均已输出 `data-theme-style="sakura"` 与 themes link(v=20261019f)。 + 投票页在 f 版本仍无主题 → 需再部署一次。 + +## 投票页语言跟随发布者(第三轮) + +- 抽出 `applyOwnerLocale(data map[string]interface{}, owner *db.User)`(route/vote/vote.go 顶部): + 同时写 `Owner` / `Lang` / `LangCode` / `ThemeSlug`。 + 签名故意收 Data 而不是 `context.Context`:`Context` 内部字段不可在包外构造,收 map 才能单测 + (`route/vote/locale_test.go`,纯内存,不碰 DB)。 +- `Page`:owner 查询提到 `FeatureStateOf` **之前**——否则「投票功能已关闭」这类提示先按访客语言生成。 +- `Submit`:同样先取 owner 再取 `lang`,保证提交后回跳页的 flash 与页面语言一致; + 顺带删掉尾部重复的 `GetByID(owner)`(表单类型通知邮件复用同一个 owner),少一次查询。 +- 四语言 `profile.theme.hint` 与 changelog 范围说明补上「你发布的投票页」。 +- 验证:vote/i18n/context/theme/templates 全绿;线上 `lang="ja"` + 中文内容的不一致待部署后复测。 diff --git a/.workbuddy/memory/MEMORY.md b/.workbuddy/memory/MEMORY.md index 24a1827..15f8a94 100644 --- a/.workbuddy/memory/MEMORY.md +++ b/.workbuddy/memory/MEMORY.md @@ -156,14 +156,21 @@ ## 主题(箱主个性化) -- 两个注入点,别再漏:`/_/{domain}/*` → `question.Pager` 按 `pageUser.Theme`; - `/user/*` → `internal/context` 的 Contexter 按 `c.User.Theme`(`isThemeScope()` 只认 `/user` 与 `/user/`)。 - 公开页(`/`、`/explore`、`/vote/{token}`)不跟随登录用户主题。 +- 三个注入点,别再漏:`/_/{domain}/*` → `question.Pager` 按 `pageUser.Theme`; + `/user/*` → `internal/context` 的 Contexter 按 `c.User.Theme`(`isThemeScope()` 只认 `/user` 与 `/user/`); + `/vote/{token}` → `route/vote.Page` 按发布者 `owner.Theme`。都看「箱主/发布者」的配置,不看访客。 + 全站页(`/`、`/explore`、`/login`)不跟随任何人的主题。 +- 投票页**语言**也跟发布者(`applyOwnerLocale(ctx.Data, owner)`,与主题同一个函数): + 必须在任何 i18n 取词之前调用,否则先渲染出的提示会按访客语言。箱主未配语言时才回退访客/站点默认。 - 主题 = 一个 css 文件只覆盖令牌;新增主题不用改 Go/模板,放 `static/css/themes/.css` + `[theme] available` 登记即可(`static/fs.go` 已嵌整个目录)。底稿 `_scaffold.css`。 - **必须同时写浅色块与深色块**(`html[data-theme-style=x]` / `html[data-theme="dark"][data-theme-style=x]`), `TestThemeCSSComplete` 会查;色块预览由浅色块的 `--tb-bg` + `--tb-primary` 自动生成(色值过正则防注入)。 - 令牌 `--tb-navbar`:顶栏底色,默认 `var(--tb-surface)`。页面底色偏彩时主题应覆盖它, 否则顶栏是纯白带、压在彩底上割裂(用户实测反馈过)。 +- UIkit 主色是编译期常量,主题要走通必须逐个组件映射(见 base.css 末尾那段):a/uk-link、 + uk-button-primary、checkbox/radio、input:focus、uk-tab active、`.uk-progress`, + 以及 **`.uk-label:not(danger|warning|success|secondary)`**——`uk-label` 默认色就是主色, + 但必须用 :not() 排除语义色(同特异性 + base.css 后加载,不排除会连带刷掉 danger/success)。 - 主题底色用**纯色**,别用整页渐变——会被读成「底色分布不均匀」;图案建议 ≥24px、透明度 ≤0.06。 - 改了 base.css / 主题 css 必须 +1 `conf.StaticVersion`(两者共用同一个 v)。 diff --git a/internal/conf/static.go b/internal/conf/static.go index d57287f..8c014b6 100644 --- a/internal/conf/static.go +++ b/internal/conf/static.go @@ -10,7 +10,7 @@ var ( BuildCommit = "dev" // StaticVersion 静态资源缓存版本号:base.css 与主题 css 共用,改样式只改这一处。 - StaticVersion = "20261019f" + StaticVersion = "20261019g" ) // 文本审核通道取值([security] text_censor_source)。定义在 conf 侧, diff --git a/internal/i18n/dict_en.go b/internal/i18n/dict_en.go index 86e4ffd..88ecd68 100644 --- a/internal/i18n/dict_en.go +++ b/internal/i18n/dict_en.go @@ -933,7 +933,7 @@ var enDict = map[string]string{ // 主题:设置页选择器与主题显示名(theme.name. 由 internal/theme 约定) "profile.theme.title": "Theme", - "profile.theme.hint": "Applies to your question box pages and your dashboard; public pages (home, explore) keep the default look.", + "profile.theme.hint": "Applies to your box pages, the votes you publish, and your dashboard; site pages such as home and explore keep the default look.", "profile.theme.followDefault": "Follow site default", "profile.theme.save": "Save theme", "profile.theme.saved": "Theme saved", diff --git a/internal/i18n/dict_ja.go b/internal/i18n/dict_ja.go index 8064eb1..ed9b073 100644 --- a/internal/i18n/dict_ja.go +++ b/internal/i18n/dict_ja.go @@ -933,7 +933,7 @@ var jaDict = map[string]string{ // 主题:设置页选择器与主题显示名(theme.name. 由 internal/theme 约定) "profile.theme.title": "テーマ", - "profile.theme.hint": "質問箱のページと管理画面に適用されます。ホーム・探索などの公開ページは既定の外観のままです。", + "profile.theme.hint": "質問箱のページ、あなたが作成した投票、管理画面に適用されます。ホーム・探索などのサイト共通ページは既定の外観のままです。", "profile.theme.followDefault": "サイト既定に従う", "profile.theme.save": "テーマを保存", "profile.theme.saved": "テーマを保存しました", diff --git a/internal/i18n/dict_zh_cn.go b/internal/i18n/dict_zh_cn.go index a823b78..0d7421f 100644 --- a/internal/i18n/dict_zh_cn.go +++ b/internal/i18n/dict_zh_cn.go @@ -874,7 +874,7 @@ var zhCNDict = map[string]string{ // 主题:设置页选择器与主题显示名(theme.name. 由 internal/theme 约定) "profile.theme.title": "主题外观", - "profile.theme.hint": "对提问箱页面和你的后台页面生效,首页/探索等公开页保持默认外观。", + "profile.theme.hint": "对提问箱页面、你发布的投票和你自己的后台生效;首页/探索等站方页面保持默认外观。", "profile.theme.followDefault": "跟随站点默认", "profile.theme.save": "保存主题", "profile.theme.saved": "主题已保存", diff --git a/internal/i18n/dict_zh_tw.go b/internal/i18n/dict_zh_tw.go index 789bbfa..38e822d 100644 --- a/internal/i18n/dict_zh_tw.go +++ b/internal/i18n/dict_zh_tw.go @@ -933,7 +933,7 @@ var zhTWDict = map[string]string{ // 主题:设置页选择器与主题显示名(theme.name. 由 internal/theme 约定) "profile.theme.title": "主題外觀", - "profile.theme.hint": "對提問箱頁面與你的後台頁面生效,首頁/探索等公開頁維持預設外觀。", + "profile.theme.hint": "對提問箱頁面、你發起的投票與你自己的後台生效;首頁/探索等站方頁面維持預設外觀。", "profile.theme.followDefault": "跟隨站點預設", "profile.theme.save": "儲存主題", "profile.theme.saved": "主題已儲存", diff --git a/outputs/crop-q-default-pills.png b/outputs/crop-q-default-pills.png new file mode 100644 index 0000000..c08b5e4 Binary files /dev/null and b/outputs/crop-q-default-pills.png differ diff --git a/outputs/crop-q-pills.png b/outputs/crop-q-pills.png new file mode 100644 index 0000000..45eeaa3 Binary files /dev/null and b/outputs/crop-q-pills.png differ diff --git a/outputs/live-user-profile.html b/outputs/live-user-profile.html new file mode 100644 index 0000000..49cef07 --- /dev/null +++ b/outputs/live-user-profile.html @@ -0,0 +1,2667 @@ + + + + + + 狼的提问箱 | 匿名提问箱 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + + +
+ + + +
+
+

人机验证

+

请计算下方的算术题以验证你不是机器人

+
+ 加载中... + +
+
+ +
+ +
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+

裁剪图片

+
+
+
+ +
+

+
+ +
+
+ + +
+ + + + + +
+ + +
+
+ + + +
+

基础资料

+
+ + +
+
+ + +
+
+ +
+ +
+
+ + +
+
+ +
+ +
+
+ + + 选择访客访问你的提问箱时显示的语言(后台页面跟随此设置) +
+
+ + +
+
+ +
+
+ + +
+

头像与背景

+
+
+ + + + 1:1 +
+ + +
+ 裁剪框已锁定为 1:1 正方形 +
+
+ + + + 16:9 +
+ + +
+ 裁剪框已锁定为 16:9 宽屏 +
+
+ +
+ +
+
+ + +
+
    +
  • + 二维码样式 +
    +
    +
    +
    + +
    + +
    +
    + +
    + +
    +
    + +
    + +
    +
    + + 仅在启用时生效,否则颜色由系统自动生成 +
    +
    + +
    + + 0.45 + +
    +
    +
    + +
    + + 0.11 + +
    +
    +
    + +
    + + +
    + 选择图片后预览将即时更新 +
    +
    +
    +
    +

    实时预览

    +
    +
    +
    + 预设主题 +
    + + + + +
    +
    +
    + +
    +

    调整仅用于预览,最终效果点击「保存修改」后生效

    +
    +
    +
    +
    +
  • +
+
+ +
+
+ + +
+
    +
  • + 社交信息设置 +
    + 勾选后填写主页地址(无需 https://),未勾选的项将不会在前台显示。 + + + + + +
    +
  • +
+
+ +
+
+ + +
+

修改密码

+
+ + +
+
+ + +
+

保存后当前会话立即失效,需使用新密码重新登录

+
+
+ +
+
+
+ + +
+

标签设置

+
+ +
+ + +
+
+ +
+
+ +
+
+
+ + +
+

账号设置

+ +
+ + +
+

主题外观

+
+ +

对提问箱页面和你的后台页面生效,首页/探索等公开页保持默认外观。

+
+ + + + + + +
+
+ +
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + +

+
+ 社区规则 | + + 找站长 +
+ 盘 + | 公告板 + | 聊天室 + | 监控板 + | 导航页 +
Powered by NekoBox | 狼ICP备 20230101 +
提问箱已成功存活: +

+ + +
+
+ + + + + diff --git a/outputs/live-user-questions-default.html b/outputs/live-user-questions-default.html new file mode 100644 index 0000000..c67a545 --- /dev/null +++ b/outputs/live-user-questions-default.html @@ -0,0 +1,1661 @@ + + + + + + 狼的提问箱 | 匿名提问箱 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + + +
+ + + +
+
+

人机验证

+

请计算下方的算术题以验证你不是机器人

+
+ 加载中... + +
+
+ +
+ +
+ + +
+
+
+ + + + + + + + +
+
+

我的提问

+ 设置 +
+ + +
+ + + +
+ + +
+ + + + + + + + + + + + + + +
+
6总提问数
+
5已回答
+
1未回答
+
5已读
+
1未读
+
5私密提问
+
0置顶
+
+ +
+ +
+
+
+ +
+
+ +
+ +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +

我收到的提问

+ + +
+
+ +
+ +
+ 2026-10-19 00:58:03 + + + 私密私密 + + + + + 已读已读 + +
+ +
+ + +
+ + +
+
+
+ +
等宽按钮自检B 005800 +
+
+
+ +
+
+ +
+ +
+ 2026-10-19 00:40:58 + + + + 待答未回答 + + + + 未读未读 + +
+ +
+ + +
+ + +
+ + +
+ + +
+
+
+ +
等宽按钮自检 004055 +
+
+
+ +
+
+ +
+ +
+ 2026-10-18 21:53:28 + + + 私密私密 + + + + + 已读已读 + +
+ +
+ + +
+ + +
+
+
+ +
自检用提问 L3:今天更新吗(restrict self-check L3) +
+
+
+ +
+
+ +
+ +
+ 2026-10-18 21:48:21 + + + 私密私密 + + + + + 已读已读 + +
+ +
+ + +
+ + +
+
+
+ +
自检用提问:今天更新吗(restrict self-check) +
+
+
+ +
+
+ +
+ +
+ 2026-10-18 21:39:41 + + + 私密私密 + + + + + 已读已读 + +
+ +
+ + +
+ + +
+
+
+ +
啊吧啊吧 +
+
+
+ +
+
+ +
+ +
+ 2026-08-20 16:01:14 + + + 私密私密 + + + + + 已读已读 + +
+ +
+ + +
+ + +
+
+
+ +
测试! +
+
+
+ + + +
+ + + + + + +
+ + + +

+
+ 社区规则 | + + 找站长 +
+ 盘 + | 公告板 + | 聊天室 + | 监控板 + | 导航页 +
Powered by NekoBox | 狼ICP备 20230101 +
提问箱已成功存活: +

+ + +
+
+ + + + + diff --git a/outputs/live-user-questions-local.html b/outputs/live-user-questions-local.html new file mode 100644 index 0000000..01d6dab --- /dev/null +++ b/outputs/live-user-questions-local.html @@ -0,0 +1,1661 @@ + + + + + + 狼的提问箱 | 匿名提问箱 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + + +
+ + + +
+
+

人机验证

+

请计算下方的算术题以验证你不是机器人

+
+ 加载中... + +
+
+ +
+ +
+ + +
+
+
+ + + + + + + + +
+
+

我的提问

+ 设置 +
+ + +
+ + + +
+ + +
+ + + + + + + + + + + + + + +
+
6总提问数
+
5已回答
+
1未回答
+
5已读
+
1未读
+
5私密提问
+
0置顶
+
+ +
+ +
+
+
+ +
+
+ +
+ +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +

我收到的提问

+ + +
+
+ +
+ +
+ 2026-10-19 00:58:03 + + + 私密私密 + + + + + 已读已读 + +
+ +
+ + +
+ + +
+
+
+ +
等宽按钮自检B 005800 +
+
+
+ +
+
+ +
+ +
+ 2026-10-19 00:40:58 + + + + 待答未回答 + + + + 未读未读 + +
+ +
+ + +
+ + +
+ + +
+ + +
+
+
+ +
等宽按钮自检 004055 +
+
+
+ +
+
+ +
+ +
+ 2026-10-18 21:53:28 + + + 私密私密 + + + + + 已读已读 + +
+ +
+ + +
+ + +
+
+
+ +
自检用提问 L3:今天更新吗(restrict self-check L3) +
+
+
+ +
+
+ +
+ +
+ 2026-10-18 21:48:21 + + + 私密私密 + + + + + 已读已读 + +
+ +
+ + +
+ + +
+
+
+ +
自检用提问:今天更新吗(restrict self-check) +
+
+
+ +
+
+ +
+ +
+ 2026-10-18 21:39:41 + + + 私密私密 + + + + + 已读已读 + +
+ +
+ + +
+ + +
+
+
+ +
啊吧啊吧 +
+
+
+ +
+
+ +
+ +
+ 2026-08-20 16:01:14 + + + 私密私密 + + + + + 已读已读 + +
+ +
+ + +
+ + +
+
+
+ +
测试! +
+
+
+ + + +
+ + + + + + +
+ + + +

+
+ 社区规则 | + + 找站长 +
+ 盘 + | 公告板 + | 聊天室 + | 监控板 + | 导航页 +
Powered by NekoBox | 狼ICP备 20230101 +
提问箱已成功存活: +

+ + +
+
+ + + + + diff --git a/outputs/live-user-questions.html b/outputs/live-user-questions.html new file mode 100644 index 0000000..1c04faa --- /dev/null +++ b/outputs/live-user-questions.html @@ -0,0 +1,1661 @@ + + + + + + 狼的提问箱 | 匿名提问箱 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + + +
+ + + +
+
+

人机验证

+

请计算下方的算术题以验证你不是机器人

+
+ 加载中... + +
+
+ +
+ +
+ + +
+
+
+ + + + + + + + +
+
+

我的提问

+ 设置 +
+ + +
+ + + +
+ + +
+ + + + + + + + + + + + + + +
+
6总提问数
+
5已回答
+
1未回答
+
5已读
+
1未读
+
5私密提问
+
0置顶
+
+ +
+ +
+
+
+ +
+
+ +
+ +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +

我收到的提问

+ + +
+
+ +
+ +
+ 2026-10-19 00:58:03 + + + 私密私密 + + + + + 已读已读 + +
+ +
+ + +
+ + +
+
+
+ +
等宽按钮自检B 005800 +
+
+
+ +
+
+ +
+ +
+ 2026-10-19 00:40:58 + + + + 待答未回答 + + + + 未读未读 + +
+ +
+ + +
+ + +
+ + +
+ + +
+
+
+ +
等宽按钮自检 004055 +
+
+
+ +
+
+ +
+ +
+ 2026-10-18 21:53:28 + + + 私密私密 + + + + + 已读已读 + +
+ +
+ + +
+ + +
+
+
+ +
自检用提问 L3:今天更新吗(restrict self-check L3) +
+
+
+ +
+
+ +
+ +
+ 2026-10-18 21:48:21 + + + 私密私密 + + + + + 已读已读 + +
+ +
+ + +
+ + +
+
+
+ +
自检用提问:今天更新吗(restrict self-check) +
+
+
+ +
+
+ +
+ +
+ 2026-10-18 21:39:41 + + + 私密私密 + + + + + 已读已读 + +
+ +
+ + +
+ + +
+
+
+ +
啊吧啊吧 +
+
+
+ +
+
+ +
+ +
+ 2026-08-20 16:01:14 + + + 私密私密 + + + + + 已读已读 + +
+ +
+ + +
+ + +
+
+
+ +
测试! +
+
+
+ + + +
+ + + + + + +
+ + + +

+
+ 社区规则 | + + 找站长 +
+ 盘 + | 公告板 + | 聊天室 + | 监控板 + | 导航页 +
Powered by NekoBox | 狼ICP备 20230101 +
提问箱已成功存活: +

+ + +
+
+ + + + + diff --git a/outputs/live-vote-before.html b/outputs/live-vote-before.html new file mode 100644 index 0000000..eb548db --- /dev/null +++ b/outputs/live-vote-before.html @@ -0,0 +1,886 @@ + + + + + + 测试 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + +
+ + + +
+
+

人機認証

+

以下の計算問題を解いて、ロボットでないことを証明してください

+
+ 読み込み中... + +
+
+ +
+ +
+ + +
+
+
+ + + + + + + + + + + + + + + + + + +
+
+ + +
+ admin6 +
の投票
+
+ +
+ + + 進行中 + +
+
+ +

测试

+ +

测试

+ +
+ + + + + + + + + +
匿名での参加が可能で、あなたの身元は記録されません(同一端末で最大1回)。
+ + +
+ + + +
+ + +
+
+ 1 + 一行テキスト +
测试 *
+
+ + + +
+ + + + +
+ +
+ + + +
+
+ + + + + + + + + +

+
+ ルール | + + 管理者へ +
+ ドライブ + | お知らせ + | チャット + | ステータス + | リンク +
Powered by NekoBox | 狼ICP备 20230101 +
質問箱は稼働中: +

+ + +
+
+ + + + + diff --git a/outputs/preview-assets/base.css b/outputs/preview-assets/base.css index a18b14e..4e7d1fe 100644 --- a/outputs/preview-assets/base.css +++ b/outputs/preview-assets/base.css @@ -410,6 +410,12 @@ a:hover, .uk-link:hover { background-color: var(--tb-primary); border-color: transparent; } +/* 胶囊按钮/状态标签(提问管理里的「置顶」「未回答」,投票的「已参与」)也走主色。 + danger / warning / success / secondary 是语义色,必须用 :not() 排除—— + 同特异性下 base.css 在 UIkit 之后加载,不排除会把它们一并刷成主色。 */ +.uk-label:not(.uk-label-danger):not(.uk-label-warning):not(.uk-label-success):not(.uk-label-secondary) { + background-color: var(--tb-primary); +} .uk-input:focus, .uk-select:focus, .uk-textarea:focus { diff --git a/outputs/shot-live-box-deployed.png b/outputs/shot-live-box-deployed.png new file mode 100644 index 0000000..0b15aa9 Binary files /dev/null and b/outputs/shot-live-box-deployed.png differ diff --git a/outputs/shot-live-user-profile.png b/outputs/shot-live-user-profile.png new file mode 100644 index 0000000..75f4c62 Binary files /dev/null and b/outputs/shot-live-user-profile.png differ diff --git a/outputs/shot-live-user-questions.png b/outputs/shot-live-user-questions.png new file mode 100644 index 0000000..c6b97d5 Binary files /dev/null and b/outputs/shot-live-user-questions.png differ diff --git a/outputs/shot-q-default.png b/outputs/shot-q-default.png new file mode 100644 index 0000000..9d4b5c7 Binary files /dev/null and b/outputs/shot-q-default.png differ diff --git a/outputs/shot-q-local.png b/outputs/shot-q-local.png new file mode 100644 index 0000000..ffa1940 Binary files /dev/null and b/outputs/shot-q-local.png differ diff --git a/outputs/shot-vote-before.png b/outputs/shot-vote-before.png new file mode 100644 index 0000000..060d56e Binary files /dev/null and b/outputs/shot-vote-before.png differ diff --git a/route/vote/locale_test.go b/route/vote/locale_test.go new file mode 100644 index 0000000..6058154 --- /dev/null +++ b/route/vote/locale_test.go @@ -0,0 +1,25 @@ +package vote + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/tamakyi/TamaBox/internal/db" +) + +// 投票链接是发布者对外分享的内容:语言必须跟着发布者走,否则中文箱主分享出去的 +// 投票页会变成站点默认语言,访客看到的语言和内容对不上。 +func TestApplyOwnerLocale(t *testing.T) { + data := map[string]interface{}{"Lang": "en", "LangCode": "en"} + applyOwnerLocale(data, &db.User{Language: "ja"}) + + assert.Equal(t, "ja", data["Lang"]) + assert.Equal(t, "ja", data["LangCode"]) + + // 发布者没配语言:保留 Contexter 解析出的访客/站点默认值,不要清空 + fallback := map[string]interface{}{"Lang": "en", "LangCode": "en"} + applyOwnerLocale(fallback, &db.User{Language: ""}) + + assert.Equal(t, "en", fallback["LangCode"]) +} diff --git a/route/vote/vote.go b/route/vote/vote.go index 5f88157..7753627 100644 --- a/route/vote/vote.go +++ b/route/vote/vote.go @@ -33,6 +33,7 @@ import ( "github.com/tamakyi/TamaBox/internal/security/censor" "github.com/tamakyi/TamaBox/internal/security/moderation" "github.com/tamakyi/TamaBox/internal/shared" + "github.com/tamakyi/TamaBox/internal/theme" ) // ctxLang 取当前请求已解析好的语言代码(由 Contexter 写入 Data)。 @@ -43,6 +44,19 @@ func ctxLang(ctx context.Context) string { return i18n.Default } +// applyOwnerLocale 让界面语言与主题跟随投票发布者,与提问箱页面同一套规则: +// 投票链接是发布者对外分享的内容,访客看到的应当是他的语言与配色。 +// 发布者未配置语言时保留 Contexter 解析出的值(访客 cookie / 站点默认)。 +func applyOwnerLocale(data map[string]interface{}, owner *db.User) { + data["Owner"] = owner + if owner.Language != "" { + lang := i18n.Resolve(owner.Language) + data["Lang"] = lang + data["LangCode"] = lang + } + data["ThemeSlug"] = theme.Resolve(owner.Theme) +} + // resultsCache 投票结果聚合统计的短 TTL 进程内缓存。 // 结果统计需在 Go 层逐条 json.Unmarshal 全部提交记录(O(N)),缓存可显著降低数据库实时重算与 CPU 开销; // 结果页非强一致,30s 陈旧可接受(新提交最多延迟 30s 反映)。多实例部署时各实例独立缓存,不影响正确性。 @@ -596,6 +610,14 @@ func Page(ctx context.Context) { now := time.Now() isOwner := ctx.IsLogged && ctx.User.ID == vote.UserID + // 发布者的语言与主题:必须在任何 i18n 取词之前应用,否则「投票功能已关闭」 + // 这类提示会先按访客语言生成。箱主已删除(GetByID 失败)则保持站点默认外观。 + if owner, err := db.Users.GetByID(ctx.Request().Context(), vote.UserID); err == nil { + applyOwnerLocale(ctx.Data, owner) + } else { + logrus.WithContext(ctx.Request().Context()).WithError(err).Warn("Failed to get vote owner") + } + // 箱主侧的投票功能开关(开关 / 定时开放 / 定量收集):关闭时访客不可参与, // 页面本身仍可访问(结果展示策略不受影响)。 voteState := db.FeatureStateOf(ctx.Request().Context(), vote.UserID, db.FeatureVote) @@ -609,12 +631,6 @@ func Page(ctx context.Context) { ctx.Data["Ended"] = isVoteEnded(vote, now) ctx.Data["VoteScopeRegisteredOnly"] = vote.VoterScope == db.VoteVoterScopeRegisteredOnly - // 箱主信息 - owner, err := db.Users.GetByID(ctx.Request().Context(), vote.UserID) - if err == nil { - ctx.Data["Owner"] = owner - } - // 选项 / 表单字段 if vote.Type == db.VoteTypeForm { fields, _ := db.VoteFormFields.ListByVoteID(ctx.Request().Context(), vote.ID) @@ -680,14 +696,24 @@ type formFieldInput struct { // Submit 提交投票。 func Submit(ctx context.Context, recaptcha recaptcha.RecaptchaV3) { - lang := ctxLang(ctx) vote, err := db.Votes.GetByToken(ctx.Request().Context(), ctx.Param("token")) if err != nil { - ctx.SetErrorFlash(i18n.T(lang, "vote.flash.notFound")) + ctx.SetErrorFlash(i18n.T(ctxLang(ctx), "vote.flash.notFound")) ctx.Redirect("/") return } + // 发布者的语言与主题:提交成功/失败都回跳投票页,提示文案必须与页面语言一致。 + owner, err := db.Users.GetByID(ctx.Request().Context(), vote.UserID) + if err != nil { + logrus.WithContext(ctx.Request().Context()).WithError(err).Error("Failed to get vote owner") + ctx.SetInternalErrorFlash() + ctx.Redirect("/vote/" + vote.LinkToken) + return + } + applyOwnerLocale(ctx.Data, owner) + lang := ctxLang(ctx) + // 箱主关闭投票功能(开关 / 定时 / 定量)时拒绝参与,避免绕过页面上的禁用态直接 POST。 if voteState := db.FeatureStateOf(ctx.Request().Context(), vote.UserID, db.FeatureVote); !voteState.Open { ctx.SetErrorFlash(i18n.FeatureClosedMessage(lang, db.FeatureVote, voteState.Reason)) @@ -766,17 +792,8 @@ func Submit(ctx context.Context, recaptcha recaptcha.RecaptchaV3) { voterToken = &token } - // 解析提交内容。 - // 先取投票发起人的全局屏蔽词表(users.block_words):投票提交中的开放文本 - // (自定义补充选项、表单“其他”文本、文本/长文本字段)需要与提问/私聊一样 - // 命中即拒,因此把 owner 一并传入 parseSubmission 做本地词表检查。 - owner, err := db.Users.GetByID(ctx.Request().Context(), vote.UserID) - if err != nil { - logrus.WithContext(ctx.Request().Context()).WithError(err).Error("Failed to get vote owner") - ctx.SetInternalErrorFlash() - ctx.Redirect("/vote/" + vote.LinkToken) - return - } + // 解析提交内容。投票发起人的全局屏蔽词表(users.block_words)用于校验开放文本: + // 自定义补充选项、表单“其他”文本、文本/长文本字段命中即拒(与提问/私聊一致)。 submission, err := parseSubmission(ctx, vote, owner) if err != nil { ctx.SetErrorFlash(err.Error()) @@ -822,19 +839,16 @@ func Submit(ctx context.Context, recaptcha recaptcha.RecaptchaV3) { } } - // 表单类型投票:异步通知箱主查看新回复(开启邮件通知时) - if vote.Type == db.VoteTypeForm { - owner, err := db.Users.GetByID(ctx.Request().Context(), vote.UserID) - if err == nil && owner.Notify == db.NotifyTypeEmail { - email := owner.Email - title := vote.Title - voteID := vote.ID - shared.SafeGo(func() { - if err := mail.SendNewVoteSubmissionMail(email, title, voteID); err != nil { - logrus.WithContext(stdctx.Background()).WithError(err).Error("Failed to send new vote submission mail") - } - }) - } + // 表单类型投票:异步通知箱主查看新回复(开启邮件通知时)。owner 在上方已取过,直接复用。 + if vote.Type == db.VoteTypeForm && owner.Notify == db.NotifyTypeEmail { + email := owner.Email + title := vote.Title + voteID := vote.ID + shared.SafeGo(func() { + if err := mail.SendNewVoteSubmissionMail(email, title, voteID); err != nil { + logrus.WithContext(stdctx.Background()).WithError(err).Error("Failed to send new vote submission mail") + } + }) } ctx.SetSuccessFlash(i18n.T(lang, "vote.flash.success")) diff --git a/static/css/base.css b/static/css/base.css index a18b14e..4e7d1fe 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -410,6 +410,12 @@ a:hover, .uk-link:hover { background-color: var(--tb-primary); border-color: transparent; } +/* 胶囊按钮/状态标签(提问管理里的「置顶」「未回答」,投票的「已参与」)也走主色。 + danger / warning / success / secondary 是语义色,必须用 :not() 排除—— + 同特异性下 base.css 在 UIkit 之后加载,不排除会把它们一并刷成主色。 */ +.uk-label:not(.uk-label-danger):not(.uk-label-warning):not(.uk-label-success):not(.uk-label-secondary) { + background-color: var(--tb-primary); +} .uk-input:focus, .uk-select:focus, .uk-textarea:focus { diff --git a/static/tamabox.changelogs.json b/static/tamabox.changelogs.json index dedecb0..85d2a74 100644 --- a/static/tamabox.changelogs.json +++ b/static/tamabox.changelogs.json @@ -7,7 +7,7 @@ { "collectionId": "tamabox", "collectionName": "tamabox-changelogs", - "content": "新增:箱主主题功能。设置页可为主题选择配色、图案与整页背景(不影响布局);提问箱页面(箱主页 / 单题页 / 密码页 / 分享卡)与箱主本人后台页面都会跟随所选主题,首页、探索等公开页保持站点默认外观;未选择时跟随站点默认主题。
\n新增:主题底稿 static/css/themes/_scaffold.css,复制改名为 <slug>.css 并在 [theme] available 登记即可新增一套主题,无需改模板;示例主题「樱花」。", + "content": "新增:箱主主题功能。设置页可为主题选择配色、图案与整页背景(不影响布局);提问箱页面(箱主页 / 单题页 / 密码页 / 分享卡)、你发布的投票页与箱主本人后台都会跟随所选主题,首页、探索等公开页保持站点默认外观;未选择时跟随站点默认主题。
\n新增:主题底稿 static/css/themes/_scaffold.css,复制改名为 <slug>.css 并在 [theme] available 登记即可新增一套主题,无需改模板;示例主题「樱花」。", "created": "2026-10-19 15:10:00.000Z", "id": "none", "published_at": "2026-10-19 15:10:00.000Z",