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

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

① 单组按键模式让另一组键静默失效
   线上 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
+22 -3
View File
@@ -87,6 +87,22 @@ SQL);
$pdo->prepare('UPDATE engines SET suggest_url=?, suggest_type=? WHERE slug=? AND suggest_url=?')
->execute(['', '', 'sogou', 'https://www.sogou.com/suggnew/ajajjson?type=web&key={q}']);
// 老库升级:把「联想词按键」的单组设置(arrows / punct)并成 'both'(两组都认)。
// 为什么:用户反馈「上下键和逗号句号都选不了词」,根因之一就是线上停在单组 'punct'
// —— 单组模式会让另一组键**静默失效**,用户不知道那是设置项在起作用,只当成坏了。
// 'both' 是 'arrows' 与 'punct' 各自行为的**严格超集**:升级不会让任何人的习惯失效
// (原来能用的键,升级后照样能用,只是多了一组)。
// ⚠️ 只改这两个已知单组值;值不在白名单里的行不动,避免误伤将来的新取值。
// ⚠️⚠️ 必须**只跑一次**:db_init() 是**每次请求**都会执行的,若无条件 UPDATE,
// 用户在后台特意选回「只认方向键」,下一个请求就被改回去 —— 设置项等于失效。
// 用 settings 里一个哨兵键把关,跑过就永不再跑。
$migDone = (int)$pdo->query("SELECT COUNT(*) FROM settings WHERE k='mig_suggest_keys_both'")->fetchColumn() > 0;
if (!$migDone) {
$pdo->exec("UPDATE settings SET v='both' WHERE k='suggest_keys' AND v IN ('arrows','punct')");
$pdo->exec("INSERT INTO settings (k,v) VALUES ('mig_suggest_keys_both','1')
ON CONFLICT(k) DO UPDATE SET v='1'");
}
// 是否为空库(用于判断是否写入默认数据)
$isEmpty = (int)$pdo->query('SELECT COUNT(*) FROM groups')->fetchColumn() === 0;
@@ -107,9 +123,12 @@ function seed_settings(PDO $pdo): void
// '1' = 顶部搜索框与所有站点/合集链接都在**新标签页**打开;'0' = 都在**当前页面**打开。
// 默认 '1' 与引入该设置前的旧行为一致;老库若已有这一行不会被覆盖(INSERT OR IGNORE)。
'open_blank' => '1',
// 'arrows' = ↓/↑ 选词(默认,与引入该设置前的旧行为一致);
// 'punct' = 用 , 和 . 选词(输入法手感);'both' = 两组都认。
'suggest_keys' => 'arrows',
// 'both' = ↓/↑ 与 , . **两组都认**(默认);
// 'arrows' = 只认 ↓/↑;'punct' = 只认 , 和 .。
// ⚠️ 默认从 'arrows' 改为 'both'(2026-09-29):用户反馈「上下键和逗号句号都选不了词」,
// 线上设置恰好停在单组 'punct' —— 单组模式会让**另一组键静默失效**,极易被当成故障。
// 'both' 是两组各自行为的**严格超集**(想用哪组都行),没有任何损失,故取为默认。
'suggest_keys' => 'both',
];
$stmt = $pdo->prepare('INSERT OR IGNORE INTO settings (k, v) VALUES (?, ?)');
foreach ($defaults as $k => $v) {