From b19f27ab69e14d9d0808232e162695059ea83908 Mon Sep 17 00:00:00 2001 From: tamakyi Date: Fri, 9 Oct 2026 19:09:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=97=A5=E6=9C=9F=E6=A1=86=E8=A1=A5=E4=B8=8A?= =?UTF-8?q?=E6=97=A5=E5=8E=86=E5=BC=B9=E5=B1=82=EF=BC=9Aflatpickr=20?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E6=97=A0=E6=9D=A1=E4=BB=B6=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户实测:筛选区日期框换成文本框后手机上没了日历弹层、不好操作;多 18KB gzip 可以接受。 - layout.html 里 flatpickr(js + css + l10n)与 datetime-input.js 都改为**无条件加载**, 不再用 {{if .Flatpickr}} 按页开关 —— 「按页开开关」正是 2026-10-09 日期框 bug 的根源 (只给 type=datetime-local 挂了控件,后来加的 type=date 没人管)。 - 顺手删掉 `Page.Flatpickr` 字段与三处 `p.Flatpickr = true`(开关没了就不可能再漏设)。 - 守卫 TestDateInputsGoThroughAttachAll 相应改成:钉住「layout 无条件引入 flatpickr + datetime-input + attachAll」、并禁止再出现 {{if .Flatpickr}}。 实测(展开筛选面板后):390/320 下 /stats /map /places 的日历都在最上层(z-index 99999)、 完全在视口内、elementFromPoint 命中日历本身;打字 20260606 → 2026-06-06 不变。 回归:./scripts/verify.sh PASS=349 FAIL=0 SKIP=0;JS 单测 45/45;320/390/1440 溢出与改前一致。 --- .workbuddy/memory/2026-10-09.md | 15 +++++++++++++++ .workbuddy/memory/MEMORY-details.md | 10 ++++++---- internal/web/frontend_fixes_test.go | 22 +++++++++++++--------- internal/web/handlers_edit.go | 1 - internal/web/handlers_photos.go | 1 - internal/web/handlers_topics.go | 1 - internal/web/templates/layout.html | 17 +++++++++-------- internal/web/view.go | 3 --- 8 files changed, 43 insertions(+), 27 deletions(-) diff --git a/.workbuddy/memory/2026-10-09.md b/.workbuddy/memory/2026-10-09.md index dbedf27..2866df9 100644 --- a/.workbuddy/memory/2026-10-09.md +++ b/.workbuddy/memory/2026-10-09.md @@ -255,3 +255,18 @@ CDP 打字脚本(每字符 keyDown(text) + keyUp)。用它复现: **回归**:verify.sh 348 例 0 skip、JS 单测 **45/45**(新增 11 条逐键模拟)、移动端 390 / 桌面 1440 审计溢出与改前一致。 ⚠️ **纪律(又踩一次)**:验收跑着的时候别并发跑别的(我并发跑了变异测试,verify 当场把注入的旧写法抓成 FAIL —— 守卫是对的,但那轮结果得作废重跑)。这条上次已写进 details 三十二,这次是实锤。 + +--- + +## 补:日历弹层也要(用户确认手机上好操作、多 18KB 可以接受) + +前面把筛选区的 `type=date` 换成了纯手输文本框、没有日历弹层;用户要日历。最终决定: +**flatpickr 与 datetime-input.js 都改成无条件加载**(js+css+l10n 约 18KB gzip), +`Page.Flatpickr` 字段与三处 `p.Flatpickr = true` 一并删掉 —— 不再用「按页开开关」, +因为那正是这次日期框 bug 的根源(只挂了 datetime-local、type=date 没人管)。 + +实测(展开筛选面板后):390/320 下 /stats /map /places 的日历都在最上层(z-index 99999)、 +完全在视口内、`elementFromPoint` 命中日历本身;打字 20260606 → 2026-06-06 不变。 +守卫 `TestDateInputsGoThroughAttachAll` 相应改成钉「无条件加载 + 不许再出现 {{if .Flatpickr}}」。 + +回归:verify.sh **PASS=349 FAIL=0 SKIP=0**、JS 45/45、320/390/1440 溢出与改前一致。 diff --git a/.workbuddy/memory/MEMORY-details.md b/.workbuddy/memory/MEMORY-details.md index 3f80d08..dba5255 100644 --- a/.workbuddy/memory/MEMORY-details.md +++ b/.workbuddy/memory/MEMORY-details.md @@ -1044,11 +1044,13 @@ HTML,一行里必然带 ``,一拒就把正常用法挡死了(我 自动分派只日期(输出 `Y-m-d`)或带时间(输出 `Y-m-dTH:i`)。 ⚠️ **不要手写 `querySelector('input[type=...]')`** —— 2026-10-09 的 bug 就是 「只挂了 datetime-local,后来加的 type=date 没人管」。 -- `datetime-input.js` **无条件引入**(公共页筛选区也要用;flatpickr 仍按需加载 —— - 没有它时只挂输入引导、不给日历)。挂载要在 body 末尾**立刻**跑一次, - 别只等 `DOMContentLoaded`,否则用户会先看到一个原生日期框再变文本框。 +- flatpickr 与 `datetime-input.js` **都无条件加载**(js+css+l10n 约 18KB gzip): + 筛选区在公共页上也有日期框,而且「按页开开关」已经被证明会漏,所以干脆**不设开关** + (`Page.Flatpickr` 字段也删了)。用户明确接受这点资源增量(手机上有日历弹层比省这 18KB 重要)。 + 挂载要在 body 末尾**立刻**跑一次,别只等 `DOMContentLoaded`,否则先露出原生框再变文本框。 - 门禁 `TestDateInputsGoThroughAttachAll`:模板里出现 `input[type=date]` / - `input[type=datetime-local]` 这种选择器即红,并钉住「layout 无条件引入 + 四个页面都调 attachAll」。 + `input[type=datetime-local]` 这种选择器即红;并钉住「layout 无条件引入 flatpickr + datetime-input + + attachAll」、以及「不许再出现 {{if .Flatpickr}} 这种开关」。 - JS 单测:`layoutTo(spec, raw)` 是纯函数,逐键模拟敲 `20260606` 必须得 `2026-06-06` (`datetime-input.test.js`,node 直跑,45 条)。 diff --git a/internal/web/frontend_fixes_test.go b/internal/web/frontend_fixes_test.go index 5a78388..0abc53a 100644 --- a/internal/web/frontend_fixes_test.go +++ b/internal/web/frontend_fixes_test.go @@ -216,17 +216,21 @@ func TestDateInputsGoThroughAttachAll(t *testing.T) { t.Fatalf("读取 layout.html 失败: %v", err) } lay := string(layout) - // flatpickr 那段是「按需加载」的,datetime-input.js 不能跟着一起被关进去 - if i := strings.Index(lay, "{{if .Flatpickr}}"); i >= 0 { - if j := strings.Index(lay[i:], "{{end}}"); j > 0 { - if strings.Contains(lay[i:i+j], "datetime-input.js") { - t.Error("datetime-input.js 被关进 {{if .Flatpickr}} 了 —— 公共页不加载 flatpickr," + - "筛选区的 就没人改造成文本框,原生那个吞位的老毛病会原样回来") - } + // 日期控件与它的日历弹层必须**无条件**加载:筛选区在公共页上也有日期框,而 + // 「按页开开关」这种写法漏过一次(2026-10-09 的日期框 bug 就是只挂了 datetime-local)。 + for _, needle := range []string{ + "/static/vendor/flatpickr/flatpickr.min.js", + "/static/vendor/flatpickr/flatpickr.min.css", + "/static/js/datetime-input.js", + "rondDatetime.attachAll(", + } { + if !strings.Contains(lay, needle) { + t.Errorf("layout.html 里缺 %s —— 那一页的日期框会退回原生控件(没有日历弹层)", needle) } } - if !strings.Contains(lay, "rondDatetime.attachAll(") { - t.Error("layout.html 没有调用 attachAll —— 日期框不会在页面加载时被改造") + if strings.Contains(lay, "{{if .Flatpickr}}") { + t.Error("layout.html 里又出现了 {{if .Flatpickr}} 这种按页开关 —— 日期控件已改成无条件加载," + + "开关一旦漏设,那一页的日期框就没有日历弹层(别再引入会漏的东西)") } names, err := fs.Glob(tmplFS, "templates/*.html") diff --git a/internal/web/handlers_edit.go b/internal/web/handlers_edit.go index 71605bf..ec902ba 100644 --- a/internal/web/handlers_edit.go +++ b/internal/web/handlers_edit.go @@ -31,7 +31,6 @@ func (s *Server) editPage(w http.ResponseWriter, r *http.Request) { return } p.HasMap = true - p.Flatpickr = true q := r.URL.Query() d := EditData{Page: p, Query: strings.TrimSpace(q.Get("q")), SourceCounts: map[string]int{}} d.VisitQuery = strings.TrimSpace(q.Get("vq")) diff --git a/internal/web/handlers_photos.go b/internal/web/handlers_photos.go index de7e537..6be2fa0 100644 --- a/internal/web/handlers_photos.go +++ b/internal/web/handlers_photos.go @@ -48,7 +48,6 @@ func (s *Server) adminPhotosPage(w http.ResponseWriter, r *http.Request) { s.serverError(w, r, err) return } - p.Flatpickr = true p.HasMap = true d := PhotoData{Page: p, GeocodeReady: s.cfg.GeocodeKey != ""} catNames := s.categoryLabels(r.Context()) diff --git a/internal/web/handlers_topics.go b/internal/web/handlers_topics.go index c4f612e..dfd8fd6 100644 --- a/internal/web/handlers_topics.go +++ b/internal/web/handlers_topics.go @@ -283,7 +283,6 @@ func (s *Server) topicsAdminPage(w http.ResponseWriter, r *http.Request) { s.serverError(w, r, err) return } - p.Flatpickr = true d := TopicAdminData{Page: p} d.Flash, d.Err = r.URL.Query().Get("ok"), r.URL.Query().Get("err") u := userFrom(ctx) diff --git a/internal/web/templates/layout.html b/internal/web/templates/layout.html index 16015d6..8f8b4d6 100644 --- a/internal/web/templates/layout.html +++ b/internal/web/templates/layout.html @@ -220,9 +220,9 @@ {{end}} {{end}} -{{if .Flatpickr}} +{{/* 日期选择器的样式。**无条件加载**:筛选区(公共页)也有日期框,按页开关会被漏掉 + (见下面 datetime-input 那段的长注释)。 */}} -{{end}} {{/* accent-base 是站长选的主色原值:暗色模式会用 color-mix 把它提亮,否则深色底上的链接会看不清(原来的深绿在深底上只有 3.3:1)。 */}} @@ -243,14 +243,15 @@ {{if or (eq .Nav "map") (eq .Nav "topics")}}{{end}} {{end}} -{{if .Flatpickr}} +{{/* 日期选择器 + 手输引导。**无条件加载**(js+css+l10n 约 18KB gzip),两个理由: + ① 筛选区在公共页上也有日期框(/stats /places /timeline 的起止日期、/map 自己的), + 「按页开开关」这种写法已经被证明会漏 —— 2026-10-09 的日期框 bug 就是只给 + type=datetime-local 挂了控件,后来加的 type=date 没人管; + ② 原生控件本身不可靠:Chrome 的年段能吞 6 位数字,敲 20260606 会变成 202606/06 + 且 el.value 为空(筛选条件静默丢掉)。 + datetime-input.js 必须在 flatpickr 之后、页面内联脚本之前。 */}} -{{end}} -{{/* 日期输入的解析器与手输引导。**无条件引入**:公共页的筛选区也有 , - 那边不加载 flatpickr(省 60 多 KB),走纯输入引导 —— 关键是原生 date 自己也不可靠 - (Chrome 的年段能吞 6 位数字,敲 20260606 会变成 202606/06 且 value 为空)。 - 必须在 flatpickr 之后、页面内联脚本之前。 */}}