后台新增「联想词按键」设置:可用 , 和 . 代替 ↑/↓ 选联想词
输入法手感 —— 习惯中文输入法的话,手不用离开主键区就能在联想下拉里选词。
- 设置项 suggest_keys:arrows(默认,等同改造前)/ punct(, 和 .)/ both。
后台为三选一下拉,保存走白名单回退(非白名单一律 arrows),默认值 = 旧行为。
- app.js 认**物理键**(e.code 的 Comma / Period)而不是字符 —— 中文输入法下按逗号
打出来的是全角「,」,e.key 会变成「,」,但物理键不变;「跟输入法一样」就该这么认。
- ⚠️ 逗号和句号同时是搜索框的合法输入字符(如「python, pandas」)—— 所以只在
**联想下拉打开时**才把它们当选词键;没有下拉时放行,照常输入。这条边界写进了
后台说明(想输入逗号本身先按 Esc 关掉下拉)。
- 「,」= 上一条(同 ↑)、「.」= 下一条(同 ↓),首尾循环、与方向键行为完全一致。
- 前台输出两处同源标记:#search-form 的 data-keys(供断言/排查)与
window.SIMPLENAV.suggestKeys(JS 实际读它决定用哪组键)。
验证:
- jsdom 交互测试 54/54 —— arrows / punct / both 三种模式各跑一遍;覆盖全角「,」「。」
(中文输入法场景)、「没有下拉时 ,/. 不被拦截」(关键回归)、
以及上一轮的核心回归「直接回车用原文」在三种模式下都成立。
- 反向验证两个变体:把回车改回旧行为 → 「直接回车不改动输入框」精确失败(27 项);
把「没下拉放行」改坏 → 恰好只失败「没有下拉时按 ./, 不被拦截」那两条(2 项),
其余全过 —— 两条核心断言各自有效、定位精准。
- e2e 128/128(新增联想按键场景:保存 → data-keys / suggestKeys 同步、
非法值回退 arrows、保存不动其它设置)。
- 线上部署 0 FAIL(77 条静态断言、38 文件哈希逐字节一致);线上几何 25/25;
直连线上核验 data-keys / suggestKeys / app.js 内容均已就位。
This commit is contained in:
@@ -216,12 +216,16 @@ try {
|
|||||||
// 打开方式:'0' = 当前页面;其余(含字段缺失、被篡改成怪值)一律回到 '1'(新标签页)
|
// 打开方式:'0' = 当前页面;其余(含字段缺失、被篡改成怪值)一律回到 '1'(新标签页)
|
||||||
// ——默认必须等于改造前的旧行为,这样老库里已有的值上线后不会突然变样。
|
// ——默认必须等于改造前的旧行为,这样老库里已有的值上线后不会突然变样。
|
||||||
$open = (string)($_POST['open_blank'] ?? '1') === '0' ? '0' : '1';
|
$open = (string)($_POST['open_blank'] ?? '1') === '0' ? '0' : '1';
|
||||||
|
// 联想词按键:arrows(↓/↑)/ punct(, 和 .)/ both;非白名单一律回到 arrows
|
||||||
|
$keys = (string)($_POST['suggest_keys'] ?? 'arrows');
|
||||||
|
if (!in_array($keys, ['arrows', 'punct', 'both'], true)) { $keys = 'arrows'; }
|
||||||
$map = [
|
$map = [
|
||||||
'site_name' => trim((string)($_POST['site_name'] ?? '')),
|
'site_name' => trim((string)($_POST['site_name'] ?? '')),
|
||||||
'subtitle' => trim((string)($_POST['subtitle'] ?? '')),
|
'subtitle' => trim((string)($_POST['subtitle'] ?? '')),
|
||||||
'default_engine' => preg_replace('/[^a-z0-9\-_]/i', '', (string)($_POST['default_engine'] ?? '')),
|
'default_engine' => preg_replace('/[^a-z0-9\-_]/i', '', (string)($_POST['default_engine'] ?? '')),
|
||||||
'primary_color' => preg_match('/^#[0-9a-fA-F]{3,8}$/', (string)($_POST['primary_color'] ?? '')) ? $_POST['primary_color'] : '#2F6BF7',
|
'primary_color' => preg_match('/^#[0-9a-fA-F]{3,8}$/', (string)($_POST['primary_color'] ?? '')) ? $_POST['primary_color'] : '#2F6BF7',
|
||||||
'open_blank' => $open,
|
'open_blank' => $open,
|
||||||
|
'suggest_keys' => $keys,
|
||||||
'icon_services' => isset($_POST['icon_services']) ? '1' : '0',
|
'icon_services' => isset($_POST['icon_services']) ? '1' : '0',
|
||||||
'suggest_source' => $src,
|
'suggest_source' => $src,
|
||||||
'collections_layout' => $coll,
|
'collections_layout' => $coll,
|
||||||
|
|||||||
@@ -321,6 +321,20 @@ else: // settings ?>
|
|||||||
所以默认「统一用百度」——这样切到任何引擎下拉都有词。选「各搜索引擎自己的接口」则恢复成
|
所以默认「统一用百度」——这样切到任何引擎下拉都有词。选「各搜索引擎自己的接口」则恢复成
|
||||||
各用各的(没恢复好的引擎会自动回退到百度的建议)。
|
各用各的(没恢复好的引擎会自动回退到百度的建议)。
|
||||||
</p>
|
</p>
|
||||||
|
<label>联想词按键
|
||||||
|
<?php $sk = setting('suggest_keys', 'arrows'); ?>
|
||||||
|
<select name="suggest_keys">
|
||||||
|
<option value="arrows" <?= $sk === 'arrows' ? 'selected' : '' ?>>↑ / ↓ 方向键(默认)</option>
|
||||||
|
<option value="punct" <?= $sk === 'punct' ? 'selected' : '' ?>>, 和 .(输入法手感)</option>
|
||||||
|
<option value="both" <?= $sk === 'both' ? 'selected' : '' ?>>两者都可以</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<p class="hint span2">
|
||||||
|
决定用哪组键在联想下拉里<b>选词</b>:选中某条后回车才按该词搜索;不选就直接回车 = 用你输入的原文。
|
||||||
|
「, 和 .」跟输入法一样,手不用离开主键区,习惯中文输入法的话更顺手。
|
||||||
|
⚠️ 选它之后,<b>联想下拉打开时</b>逗号与句号会当作选词键,不再直接输入 ——
|
||||||
|
想搜「python, pandas」这类带逗号的词,先按 <b>Esc</b> 关掉下拉再输。
|
||||||
|
</p>
|
||||||
<label>合集显示方式
|
<label>合集显示方式
|
||||||
<?php $cl = setting('collections_layout', 'tabs'); ?>
|
<?php $cl = setting('collections_layout', 'tabs'); ?>
|
||||||
<select name="collections_layout">
|
<select name="collections_layout">
|
||||||
|
|||||||
+26
-5
@@ -31,7 +31,17 @@
|
|||||||
var lastQ = null;
|
var lastQ = null;
|
||||||
var seq = 0; // 请求序号:防止先发的慢响应覆盖后发的快响应
|
var seq = 0; // 请求序号:防止先发的慢响应覆盖后发的快响应
|
||||||
var items = []; // 当前展示的建议词
|
var items = []; // 当前展示的建议词
|
||||||
var active = -1; // 选中项下标(键盘 ↓/↑ 与鼠标划过共用);-1 = 没选中
|
var active = -1; // 选中项下标(键盘 ↓/↑、,/. 与鼠标划过共用);-1 = 没选中
|
||||||
|
|
||||||
|
/* 联想词的选中按键(后台「设置 → 联想词按键」):
|
||||||
|
arrows(默认,等同改造前)/ punct / both */
|
||||||
|
var keyMode = cfg.suggestKeys || 'arrows';
|
||||||
|
function acceptsArrows() { return keyMode === 'arrows' || keyMode === 'both'; }
|
||||||
|
function acceptsPunct() { return keyMode === 'punct' || keyMode === 'both'; }
|
||||||
|
/* ⚠️ 认**物理键**而不是字符:中文输入法下按逗号打出的是全角「,」,e.key 会变成「,」,
|
||||||
|
但 e.code 仍然是 'Comma' —— 「跟输入法一样」就该按物理键认,否则中文用户按不出来。 */
|
||||||
|
function isComma(e) { return e.code === 'Comma' || e.key === ','; }
|
||||||
|
function isPeriod(e) { return e.code === 'Period' || e.key === '.'; }
|
||||||
|
|
||||||
function hideWord() {
|
function hideWord() {
|
||||||
if (word) { word.innerHTML = ''; word.style.display = 'none'; }
|
if (word) { word.innerHTML = ''; word.style.display = 'none'; }
|
||||||
@@ -85,14 +95,25 @@
|
|||||||
input.addEventListener('keydown', function (e) {
|
input.addEventListener('keydown', function (e) {
|
||||||
var open = !!word && word.style.display === 'block' && items.length > 0;
|
var open = !!word && word.style.display === 'block' && items.length > 0;
|
||||||
|
|
||||||
// ↓ / ↑:在下拉里移动选中项(只动高亮,不动输入框;回车时才落到输入框)
|
// 移动选中项:只动高亮、不动输入框(回车时才落到输入框)。
|
||||||
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
// 用哪组键由后台「联想词按键」决定:arrows = ↓/↑ / punct = , 和 . / both = 都行。
|
||||||
|
var dir = 0;
|
||||||
|
if (acceptsArrows() && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) {
|
||||||
|
dir = e.key === 'ArrowDown' ? 1 : -1;
|
||||||
|
} else if (acceptsPunct() && isComma(e)) {
|
||||||
|
dir = -1; // , = 上一条(同 ↑)
|
||||||
|
} else if (acceptsPunct() && isPeriod(e)) {
|
||||||
|
dir = 1; // . = 下一条(同 ↓)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dir !== 0) {
|
||||||
|
// ⚠️ 没有下拉时**必须放行** —— 这时 , 和 . 是正常输入字符(如搜「python, pandas」)。
|
||||||
if (!open) { return; }
|
if (!open) { return; }
|
||||||
e.preventDefault(); // 别让光标跳到文本首尾、也别让页面跟着滚
|
e.preventDefault(); // 别让光标跳到文本首尾、也别让页面跟着滚
|
||||||
if (active < 0) {
|
if (active < 0) {
|
||||||
active = e.key === 'ArrowDown' ? 0 : items.length - 1;
|
active = dir > 0 ? 0 : items.length - 1;
|
||||||
} else {
|
} else {
|
||||||
active = (active + (e.key === 'ArrowDown' ? 1 : -1) + items.length) % items.length;
|
active = (active + dir + items.length) % items.length;
|
||||||
}
|
}
|
||||||
paintWord();
|
paintWord();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -107,6 +107,9 @@ function seed_settings(PDO $pdo): void
|
|||||||
// '1' = 顶部搜索框与所有站点/合集链接都在**新标签页**打开;'0' = 都在**当前页面**打开。
|
// '1' = 顶部搜索框与所有站点/合集链接都在**新标签页**打开;'0' = 都在**当前页面**打开。
|
||||||
// 默认 '1' 与引入该设置前的旧行为一致;老库若已有这一行不会被覆盖(INSERT OR IGNORE)。
|
// 默认 '1' 与引入该设置前的旧行为一致;老库若已有这一行不会被覆盖(INSERT OR IGNORE)。
|
||||||
'open_blank' => '1',
|
'open_blank' => '1',
|
||||||
|
// 'arrows' = ↓/↑ 选词(默认,与引入该设置前的旧行为一致);
|
||||||
|
// 'punct' = 用 , 和 . 选词(输入法手感);'both' = 两组都认。
|
||||||
|
'suggest_keys' => 'arrows',
|
||||||
];
|
];
|
||||||
$stmt = $pdo->prepare('INSERT OR IGNORE INTO settings (k, v) VALUES (?, ?)');
|
$stmt = $pdo->prepare('INSERT OR IGNORE INTO settings (k, v) VALUES (?, ?)');
|
||||||
foreach ($defaults as $k => $v) {
|
foreach ($defaults as $k => $v) {
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ function open_target(): string
|
|||||||
$openTarget = open_target();
|
$openTarget = open_target();
|
||||||
$openMode = $openTarget === '_blank' ? 'blank' : 'self';
|
$openMode = $openTarget === '_blank' ? 'blank' : 'self';
|
||||||
|
|
||||||
|
// 联想词的选中按键(后台「设置 → 联想词按键」):
|
||||||
|
// arrows = 只认 ↓/↑(默认,等同改造前) / punct = 只认 , 和 . (输入法手感)/ both = 都认
|
||||||
|
// 白名单回退:任何非法值都回到 arrows,别让手改库/被篡改的表单把交互搞坏。
|
||||||
|
$suggestKeys = setting('suggest_keys', 'arrows');
|
||||||
|
if (!in_array($suggestKeys, ['arrows', 'punct', 'both'], true)) { $suggestKeys = 'arrows'; }
|
||||||
|
|
||||||
// 默认引擎
|
// 默认引擎
|
||||||
$defSlug = setting('default_engine', 'bing');
|
$defSlug = setting('default_engine', 'bing');
|
||||||
$default = null;
|
$default = null;
|
||||||
@@ -122,7 +128,7 @@ function site_tile(array $s): void
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="search-form" action="<?= e($default['search_url'] ?? 'https://cn.bing.com/search') ?>" method="get" target="<?= e($openTarget) ?>"<?= $openTarget === '_blank' ? ' rel="noopener"' : '' ?>
|
<form id="search-form" action="<?= e($default['search_url'] ?? 'https://cn.bing.com/search') ?>" method="get" target="<?= e($openTarget) ?>"<?= $openTarget === '_blank' ? ' rel="noopener"' : '' ?>
|
||||||
data-slug="<?= e($default['slug'] ?? '') ?>">
|
data-slug="<?= e($default['slug'] ?? '') ?>" data-keys="<?= e($suggestKeys) ?>">
|
||||||
<input type="text" id="search-text" name="<?= e($default['param'] ?? 'q') ?>"
|
<input type="text" id="search-text" name="<?= e($default['param'] ?? 'q') ?>"
|
||||||
placeholder="<?= e(($default['name'] ?? '搜索') . '搜索') ?>" autocomplete="off">
|
placeholder="<?= e(($default['name'] ?? '搜索') . '搜索') ?>" autocomplete="off">
|
||||||
<button class="submit" type="submit" aria-label="搜索">
|
<button class="submit" type="submit" aria-label="搜索">
|
||||||
@@ -188,7 +194,7 @@ function site_tile(array $s): void
|
|||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>window.SIMPLENAV = { suggest: "<?= e(url('suggest.php')) ?>" };</script>
|
<script>window.SIMPLENAV = { suggest: "<?= e(url('suggest.php')) ?>", suggestKeys: "<?= e($suggestKeys) ?>" };</script>
|
||||||
<script src="<?= e(asset('assets/js/app.js')) ?>"></script>
|
<script src="<?= e(asset('assets/js/app.js')) ?>"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user