时间框跳段 + 弹窗不再脏地址栏 + 统计脚本做成后台开关
时间框(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:
@@ -1026,6 +1026,55 @@ label.f > span.span-actions { display: flex; align-items: center; justify-conten
|
||||
328~388px(2026-09-19 真机上就是这么炸的,比改之前还糟)。现在宽表卡片已被要求不放 g2
|
||||
(见 wide_table_layout_test.go),卡片宽度只由视口决定,两种判据在这些页面上等价,
|
||||
那就用兼容性好的那个。若页面/卡片的内边距变了,这里的 800 要跟着重算。 */
|
||||
/* ---------- 轻量输入弹层(替代 window.prompt) ----------
|
||||
*
|
||||
* 为什么不用 prompt():它会把提示文本与默认值写进地址栏(Chrome 会把
|
||||
* 「?新的活动名称=吃饭」这类内容拼到 URL 上并留进历史),刷新即丢、
|
||||
* 分享出去还带着原文。confirm/alert 不污染地址栏,只有 prompt 有这个毛病。
|
||||
*
|
||||
* 定位:原生 <dialog> 居中、带 ::backdrop 遮罩、按 Esc 关闭、焦点自动进输入框,
|
||||
* 这几件事正好是 prompt 给的,手写一遍不划算;旧浏览器不支持时降级成页面顶部一条。
|
||||
* showModal() 会让页面其余部分 inert,天然挡住「弹层开着还能点底层」。
|
||||
*/
|
||||
dialog.prompt-box {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
padding: 0;
|
||||
min-width: 0; width: min(420px, calc(100vw - 32px));
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
box-shadow: 0 8px 28px rgba(16, 24, 40, .18);
|
||||
}
|
||||
dialog.prompt-box::backdrop { background: rgba(16, 24, 40, .38); }
|
||||
.prompt-box .pb-body { padding: 14px 16px 12px; }
|
||||
.prompt-box .pb-title { font-size: 14.5px; font-weight: 500; margin-bottom: 8px; }
|
||||
/* 输入框字号必须 ≥16px:iOS Safari 聚焦时会自动放大页面,16px 以下会把布局顶歪 */
|
||||
.prompt-box .pb-input {
|
||||
width: 100%; font-size: 16px; font-family: inherit; color: var(--text);
|
||||
background: var(--surface); border: 1px solid var(--line);
|
||||
border-radius: 7px; padding: 7px 10px; min-height: 40px;
|
||||
}
|
||||
.prompt-box .pb-input:focus { outline: 2px solid var(--accent); outline-offset: -1px; border-color: var(--accent); }
|
||||
.prompt-box .pb-acts { display: flex; gap: 8px; justify-content: flex-end; padding: 0 16px 14px; }
|
||||
/* 触控目标下限 44px(窄屏见文件末尾的覆盖块) */
|
||||
.prompt-box .pb-acts .btn { min-height: 38px; }
|
||||
@media (max-width: 700px) {
|
||||
.prompt-box .pb-acts .btn { min-height: 44px; }
|
||||
}
|
||||
|
||||
/* 不支持 <dialog> 的旧浏览器:降级成页面顶部一条提示,
|
||||
至少不丢内容、也不会把地址栏弄脏。 */
|
||||
.prompt-fallback {
|
||||
position: fixed; inset: 8px 8px auto 8px; z-index: 2000;
|
||||
display: flex; gap: 8px; align-items: center;
|
||||
padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius);
|
||||
background: var(--surface); box-shadow: 0 8px 28px rgba(16, 24, 40, .18);
|
||||
}
|
||||
.prompt-fallback input {
|
||||
flex: 1; min-width: 0; font-size: 16px; font-family: inherit;
|
||||
border: 1px solid var(--line); border-radius: 7px; padding: 7px 10px; min-height: 40px;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.tw > table.tb.tb-stack { min-width: 0; }
|
||||
.tw > table.tb.tb-stack thead { display: none; }
|
||||
|
||||
Reference in New Issue
Block a user