Files
tamakyi 4015ddf373 时间框跳段 + 弹窗不再脏地址栏 + 统计脚本做成后台开关
时间框(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 干净。
2026-10-08 18:27:59 +08:00

92 lines
6.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 2026-10-08
## 排查:模板字段报「can't evaluate field」= 二进制比源码旧
现象:`渲染 admin/index 失败: template: index.html:116:31: executing "content" at <.ImgRows>: can't evaluate field ImgRows in type web.AdminData`
结论:不是代码 bug,是跑的二进制比模板旧。取证链:
- 字段在 `view.go:669` + `handlers_admin.go:177` 都有,引入于 `4c3032c`(2026-10-01 01:15)
- `go version -m rond.exe` → `vcs.revision=d99c63f`,`2026-09-30T09:35:59Z`
- `strings rond.exe | grep -c ImgRows` = 0(同批 `FogMasks` 有)
- `git show d99c63f:internal/web/view.go | grep -c ImgRows` = 0
**机制**:`conf/app.ini` 的 `debug=true` 让 `server.go:488` 的 `pageSet()` 每次请求都
`ParseFiles` 重读磁盘 ⇒ **模板是磁盘新版、结构体是二进制旧版**,改 Go 不重启就错配。
(与「debug=false 下模板走 go:embed,改 html 必须重编」是同一根轴的两面。)
处置:`go build -o rond.exe .` 重启即可,无需改代码。
后续可加固:`pageSet` 命中字段解析失败时提示「模板比二进制新,请重新编译」。
## 排查:时间框手动输入不跳段(flatpickr allowInput)
现象:想输 `2026/6/6`,实际要打到 `202666` 才进月份段。
实测取证(最小复现页 + 真派发按键,见下「无头测不到原生分段」):
- flatpickr 把原 `datetime-local` **就地改成 `type="text"`**(实测 `el.type === 'text'`、
`altInput` 未生成、`sameNode=true`)⇒ 原生分段编辑器被彻底替换掉,谈不上自动跳段
- 项目三处配置一致:`admin/edit.html:807`、`admin/photos.html:512`、`admin/topics.html:99`
- `inst.parseDate(s,'Y-m-dTH:i')` 实测:
- `2026` → y=2026 mo=**1**;`20266` → 同上;**`202666` → mo=6**(用户看到的就是这个)
- `2026/6/6` → 正常 y/mo/d=2026/6/6(**带分隔符能解析**);`20260606` → y=**2030** mo=**12**(错)
- `setDate` 回写:`2026` → `2026-01-01T00:00`;`202666` → `2026-06-01T00:00`;`2026-6-6` → `2026-06-06T00:00`
**根因**:`allowInput` 是「整串解析」语义,不是「逐段消费」语义。年份要满 4 位才被认,
之后月份位还要再吃 2 位才出结果 ⇒ 表现为「年份很长才跳下一位」。
**无头测不到原生分段**:`--headless=new` 下原生 `datetime-local` 的
`el.shadowRoot` 是 `null`(`shadow:false`),`sr.querySelectorAll('input.edit')` 直接抛
TypeError;且真派发按键后 `input.value` 恒为 `''`、`selectionStart` 为 `null`
—— UA 内部 shadow 段落在无头下不暴露。**要对比原生行为必须用有头 Chrome,
本机 CDP 脚本测不了,别把「原生 value 恒空」误当成原生没有分段。**
修复方向(未落地,等批准):`parseDate` 换成自定义解析器,接受
`2026/6/6`、`2026-6-6`、`20260606`、`2026` 等写法并做**歧义拒绝**
(`20260606` 必须判非法,不能悄悄解析成 2030-12-6)。服务端格式约束是
`2006-01-02T15:04`(`handlers_edit.go:1717`、`handlers_topics.go:26`),解析器须产出同一形状。
## 落地:时间框跳段 + 无歧义解析(方案 B,已完成)
用户拍板:**只做严格标准格式**(`xxxx年xx月xx日` 的数字形态,月/日/时/分一律两位、
不自动补零、不做前滚)。理由:少一套猜测逻辑、行为完全可预测。
**新增** `internal/web/static/js/datetime-input.js`(+ 同目录 `.test.js`,34 断言纯 node 跑):
- `parseDateStrict(dateStr)`:flatpickr 的 `parseDate` 钩子。接受 `2026`/`2026/6/6`/
`2026-6-6`/`20260606`/`2026-06-06T09:30`/`202606060930` 等;
**纯数字只收 4/6/8/12/14 位**(5/7/9/10/11 位一律拒——`2026060630` 到底是
「日=30」还是「时06分30」说不清)。也拒 `2026/2/30`、`2026/13/6`、尾部残留。
- `layout(raw)`:把框里值按 [年4][月2][日2][时2][分2] 切分排版,**段满即补分隔符**,
于是敲完第 4 位年份立刻变 `2026-` 且光标跳到月份位(= 原版做不到的那件事)。
逐位取数时**必须跳过分隔符**(`2026-0` 的数字是 `20260` 不是 `2020`,否则整体错位一格)。
- `placeCaret(pos)`:**同步设一次 + 微任务再补一次**。flatpickr 自己监听 input/setDate
会在我们之后改 `selectionStart`,只设一次会被它抢走 ⇒ 下次按键插到分隔符**前面**
(实测退化成 `2026-6`)。
- `attachPicker(el)`:三处页面共用的挂载入口,`_rondPickerReady` 守卫防重复
(照片页 `initPickers` 会被反复调)。配置与原先完全一致,只多 `parseDate` + 引导。
- 越界(月 66、日 45、时 25)**原样保留交解析器报错,绝不静默改成合法值**——
把 13 月悄悄改成 1 月比报错坏得多。
**改动面**:`layout.html` 加一行 script(放 `.Flatpickr` 块内、flatpickr 之后);
`admin/edit.html`、`admin/photos.html`、`admin/topics.html` 三处配置收敛成
`window.rondDatetime.attachPicker(el)`(模板净减 5 行)。
**验证**(真实站点 `localhost:8080`,CDP 派发真按键):
逐跳 `2→20→202→2026-→2026-06-→2026-06-06T` 正确;
`20260606`/`202606060930`/`2026/6/6`/`2026-6-6` 四种写法解析全 OK,
`2026666`/`20260230` 正确拒绝,`errs=0`;照片页动态框连调三次 attachPicker
(二三次返 null)+ 跳段正常。Go 测试 14 包全绿,`gofmt` 干净。
变异验证两次都被抓到:① `sep=''` ⇒ 跳段退化成 `2026`;② 放行 7 位纯数字。
## 环境坑(下次直接照抄)
- ⚠️ **`site_cdp.py` 的会话签名算法已过期**,签出来的 cookie 服务一律 303。
`internal/auth/auth.go` 现在是 `hmac(secret, payload+"\x00"+**完整** password_hash)`,
且 **secret 用裸字符串不是 hex 解码**(`server.go:118` 是 `[]byte(cfg.SecretKey)`)。
过期脚本还在用「前 16 位摘要」⇒ 本地验后台页必须自己签。
- Git Bash 下传 `--base http://...` 给 python 会被吃掉 `//`(`Cannot navigate to invalid URL`),
须加 `MSYS_NO_PATHCONV=1`。
- flatpickr 挂载后 input **`type` 被改成 `text`** ⇒ CDP 探针里
`querySelectorAll('input[type=datetime-local]')` 一律数到 0,必须用
`input.flatpickr-input`。
- 自定义 `parseDate` 返回 `false` 时 flatpickr 会走
`errorHandler(new Error("Invalid date provided: ..."))` 并清空 `selectedDates`
——不是静默忽略,所以「拒绝」在页面上表现为框被清空。