联想词键盘选词彻底修复:上下键 / 逗号句号 / 输入法组合上屏四条路径全通

背景:用户反馈「上下键和逗号句号都无法选择候选词,只能用鼠标」。逐条查下去
发现是**三个各自独立的原因**叠在一起,任何一个没修都会让人觉得「还是坏的」。

① 单组按键模式让另一组键静默失效
   线上 settings.suggest_keys 停在 'punct'(只认 , 和 .)—— 是该模式的正确
   行为,但用户不知道那是设置项在起作用,只当坏了。
   改法:默认值改为 'both'(↓/↑ 与 , . 都认)。'both' 是 'arrows' 与 'punct'
   各自行为的**严格超集**,升级不会让任何人的习惯失效,只是多了一组。
   老库用**一次性迁移**(哨兵键 mig_suggest_keys_both 把关)把已知单组值并成
   'both'。⚠️ 迁移必须只跑一次:db_init() 每次请求都执行,无条件 UPDATE 会把
   用户在后台特意选回的 'arrows' 于下一个请求改回去 —— 设置项直接失效。
   同时同步三处默认值:index.php / admin/index.php / admin/actions.php。

② 【元凶】beforeinput 只认 insertText,漏了 insertCompositionText
   输入法把标点当**组合内容**提交时,beforeinput 的 inputType 是
   insertCompositionText,旧代码直接 return → 这条路径完全失效。
   修法:两种 inputType 都认。

③ 【真机实测的硬限制】insertCompositionText 的 beforeinput cancelable = false
   Chromium 里这条事件**不允许** preventDefault(CDP 实测记录:
   beforeinput it:insertCompositionText comp:true data:"," pd:false cc:false),
   所以「命中就拦」对这条路根本无效 —— 标点会被真的插进搜索框。
   修法:加「事后收口」——beforeinput 记下快照与该移到的目标下标,
   等这一帧事件洪峰过去(setTimeout 0)再把标点从输入框里撤掉、并补上选词移动。
   目标下标必须**当场算好**:收口跑在宏任务里,那时 input 的 `active = -1`
   已经把选中清掉了,再从 active 起算会每次都回到第一条(不累加)。
   顺带把标点判定抽成共用函数 punctDir/keyPunctDir(e.key 与 e.code 互为兜底),
   并修掉「方向键也无差别置去重闸门」——方向键不产生 beforeinput,置了纯属副作用,
   反而会误挡紧随其后的真实标点。

测试与验证(全部实跑,非推断):
- jsdom 交互测试 89 项全过,新增「真实浏览器形态」场景(cancelable=false +
  组合上屏),并**纠正了旧版一条在断言 bug 本身的断言**(原写「组合上屏不该被拦」)。
- 反向验证 6 变体全部精确挂掉对应断言(含新增的「只认 insertText」与
  「方向键置闸门」两个变体);旧变体引用的 isComma/isPeriod 已随重构更新。
- **真实浏览器**(Playwright + msedge + CDP IME)21/21:上下键 / 半角 / 全角
  insertText / IME 组合上屏四条路径全部能选词。
- 真实浏览器反向验证:基准全绿,删掉「事后收口」或改回只认 insertText 都精确报红。
- 迁移语义测试 16 项全过(真实 db_init,含「用户改回 arrows 后不再被覆盖」)。
- 静态断言空跑 155 条全过;线上部署 0 FAIL,诊断页 57 条断言全过。
- 线上真机直连 n.yangyq.net 21/21。
This commit is contained in:
2026-09-29 08:10:22 +08:00
parent 5f38df80b7
commit aac6d3e0f4
5 changed files with 174 additions and 56 deletions
+4 -4
View File
@@ -33,10 +33,10 @@ $openTarget = open_target();
$openMode = $openTarget === '_blank' ? 'blank' : 'self';
// 联想词的选中按键(后台「设置 → 联想词按键」):
// arrows = 只认 ↓/↑(默认,等同改造前) / punct = 只认 , 和 . (输入法手感)/ both = 都认
// 白名单回退:任何非法值都回到 arrows,别让手改库/被篡改的表单把交互搞坏。
$suggestKeys = setting('suggest_keys', 'arrows');
if (!in_array($suggestKeys, ['arrows', 'punct', 'both'], true)) { $suggestKeys = 'arrows'; }
// both = ↓/↑ 与 , . 都认(默认) / arrows = 只认 ↓/↑ / punct = 只认 , 和 .
// 白名单回退:任何非法值都回到 both(默认值,与 db.php 一致),别让手改库/被篡改的表单把交互搞坏。
$suggestKeys = setting('suggest_keys', 'both');
if (!in_array($suggestKeys, ['arrows', 'punct', 'both'], true)) { $suggestKeys = 'both'; }
// 默认引擎
$defSlug = setting('default_engine', 'bing');