时间框跳段 + 弹窗不再脏地址栏 + 统计脚本做成后台开关

时间框(flatpickr allowInput 的手输体验)
- flatpickr 把 <input type=datetime-local> 就地改成 type=text,原生分段编辑器被替换掉;
  allowInput 又是「整串解析」语义:实测 parseDate('2026') 只出年份、'202666' 才出月份,
  于是「年份很长才跳下一位」;'20260606' 还会被静默错解成 2030-12-06 且不报错。
- 新增 static/js/datetime-input.js:自定义 parseDate(纯数字只收 4/6/8/12/14 位,
  歧义与非法日期一律拒)+ 输入引导(段满即补分隔符,敲完 4 位年份立刻跳月份位)。
  按标准格式不做自动补零、不前滚;越界值原样保留交报错,不静默改成合法值。
- 三处页面(编辑/照片/专题)共用 attachPicker,配置与原先一致,模板净减 5 行。

弹窗
- window.prompt 会把提示文本与默认值写进地址栏并留进历史(confirm/alert 无此问题)。
- 新增 static/js/prompt-box.js 替代:原生 <dialog>.showModal(),遮罩/Esc/焦点齐全,
  旧浏览器降级成页面顶部一条。已换 report.html 复制链接兜底与 edit.html 活动改名。
- 门禁 no_prompt_test.go:静态扫模板禁止 prompt(,并钉住 JS 与 CSS 都在。

统计脚本
- 原来只能改 conf/app.ini(要重启),现在后台「站点设置」可开关 + 填写,默认关闭。
- cleanTrackScript 挡住注入面:开闭 <script> 标签数不配平(提前闭合会让页面散架)、
  src 的 javascript:/data: 伪协议、超长截断。注意不能用「出现 </script 就拒」当规则——
  百度统计、Umami 贴的就是完整 HTML,正常用法会被挡死。

门禁
- datetime-input.test.js:34 条断言(node 直跑,无依赖)。
- TestCleanTrackScript:逐条边界 + 定义性不变量(任何输入都不得产出含 </script 的非空结果)。
- 三项均做过变异验证,改坏实现都会被对应测试抓到。

实测:真实站点逐键 2→20→202→2026-→2026-06-→2026-06-06T;
统计脚本六个场景(默认关/只填不开/开+URL/后台与登录页不挂/危险输入/关掉恢复)全部符合预期。
全量 internal/web 204 例 0 skip,其余包全绿,gofmt 干净。
This commit is contained in:
tamakyi
2026-10-08 18:27:59 +08:00
parent 5c7cf1a0e1
commit 4015ddf373
17 changed files with 1142 additions and 18 deletions
+18
View File
@@ -41,6 +41,13 @@ type Settings struct {
// MediaBackend 是地点图片存哪:local(服务器磁盘)或 s3(S3 兼容对象存储,如 R2)。
// 对象存储的凭据在 conf/app.ini 里(s3_*),不进数据库。
MediaBackend string `json:"media_backend"`
// TrackEnabled 决定是否注入 TrackScript。默认关闭(false)——
// 统计脚本会把自己掌握的访问信息发到第三方,默认不该开。
TrackEnabled bool `json:"track_enabled"`
// TrackScript 是第三方统计 / 追踪脚本;填 URL 会包成 <script src>,
// 需要初始化代码的服务(百度统计、Umami 等)可以把整段 <script> 贴进来。
// 仅在 TrackEnabled 为真时注入,且只注入公开页面(后台与登录页不挂)。
TrackScript string `json:"track_script"`
}
// 地点图片的两种存储后端。
@@ -117,6 +124,9 @@ func Default() Settings {
VisitorHome: VisitorHomeMain,
// 图片默认存服务器本地磁盘;配好 R2 之后再在后台切过去
MediaBackend: MediaBackendLocal,
// 统计脚本默认关闭:要站长明确打开、并自己填上脚本,才会注入公开页面
TrackEnabled: false,
TrackScript: "",
}
}
@@ -249,6 +259,14 @@ func (st *Store) Load(ctx context.Context) (Settings, error) {
if saved.AccentColor != "" {
cur.AccentColor = saved.AccentColor
}
// 统计脚本两个键都用 present 判:track_enabled 是后加的键,
// 历史配置里根本没有它,若直接套 saved.TrackEnabled(零值 false)倒也正好是
// 「默认关闭」;但显式判一下能让意图写在代码里,不依赖零值恰好对。
if _, ok := present["track_enabled"]; ok {
cur.TrackEnabled = saved.TrackEnabled
}
// 脚本内容可能存过又清空(关掉开关但想留着),所以空串也是合法值,直接赋值
cur.TrackScript = saved.TrackScript
return cur, nil
}