dsh-keys-setting
在 DSH 设置窗口 → 通用 里配置输入框的快捷键:Enter / Shift+Enter / Ctrl+Enter(macOS 为 ⌘) 各自设成「发送消息」还是「换行」。
设置 → 通用
┌────────────────────────────────────────────────────────────┐
│ 按键设置 │
│ 设置 Enter / Shift+Enter / Ctrl+Enter 是发送消息还是换行 │
│ Enter [发送消息 ▾] │
│ Shift+Enter [换行 ▾] │
│ Ctrl+Enter [发送消息 ▾] │
└────────────────────────────────────────────────────────────┘
装上就能用
用 DSH Ready GUI(推荐):插件随 GUI 内置。打开 GUI → 设置窗口 → 第三方插件 → 勾选 按键设置,装完按提示重启引擎、按提示刷新页面(它带页面部分),设置窗口「通用」页最下方就会出现 那一行。
其它 DSH 宿主(
dsh web/ CLI):git clone https://github.com/itchenshi/dsh-keys-setting.git dsh plugin --profile web add file:<clone 出来的绝对路径>装到的是磁盘上的真实目录,所以以后
git pull更新的就是同一份代码;反过来,目录被移动或删掉 会让这条依赖失效(重新 add 一次即可)。
npm 上暂时没有这个包:注册账号那一环走不通(
www.npmjs.com返回 Cloudflare 托管挑战),包发不 出去。所以现在只能按上面两种方式装。等注册通了会照常发布。
不配就是引擎原生行为,装着它不会改变你现有习惯:
| 手势 | 引擎原生 | 本插件默认 |
|---|---|---|
Enter |
发送 | 发送 |
Shift+Enter |
换行 | 换行 |
Ctrl/Cmd+Enter |
发送 | 发送 |
只在「你的选择 ≠ 引擎原生」时才拦截,所以未改动时干预次数为 0。
(Ctrl+Shift+Enter 归入 Shift 手势 —— 引擎的键位表里 Shift 规则先匹配。)
它是怎么做的(要点)
- 只重映射,不碰内容:捕获阶段监听 composer 的
keydown,命中时preventDefault并重新派发 引擎自己的另一种手势(合成Shift+Enter或合成Enter),换行/发送的语义仍由引擎决定。 - 识别 composer 用引擎的语义属性
data-composer-input="true"(不是哈希类名,也不是「任意 contenteditable」),所以不会误伤侧边栏插件自己的编辑器。 - 绝不干扰输入法:
isComposing或keyCode === 229的事件直接放行。 - Alt+Enter 完全放行(那是引擎自己的加速手势)。
合成的(
isTrusted: false)键盘事件确实能驱动引擎的 composer —— 在真实页面上用 CDP 对照验证过: 可信的 Shift+Enter 与合成的 Shift+Enter 产生了完全相同的 DOM 变化 (插入<br data-lexical-managed-linebreak="true">),合成的 Enter 也真实提交了消息。
配置
偏好存在 $DSH_HOME/settings.yaml 的 composer-keys 段,可以直接手改:
composer-keys:
enter: newline # Enter 换行
shiftEnter: newline # Shift+Enter 换行(默认)
ctrlEnter: send # Ctrl+Enter 发送(默认)
手改文件后切回 Harness 窗口即生效(插件在窗口重新获焦时重读),不用刷新页面;在设置窗口里改则 立即生效。
插件行的开关(写在 cordis.patch.yml 的行 config 里):
- insert:
- id: composer-keys # 行 id 与包名解耦,刻意保持不变
name: dsh-keys-setting
config:
enabled: true # false 时完全不加载(既不注册路由也不加设置行)
包名换过(
dsh-composer-keys→dsh-keys-setting,因为原名在 npm 上被别人占用),但 行 id 与设置命名空间仍是composer-keys,这是刻意的:补丁层的禁用行、市场state.json的开关、settings.yaml里你保存的键位都记在那个名字下,改包名不该让你已有的设置失效。
权限与边界(给会静态扫描的商城看的)
- 运行依赖:
schemastery(纯 JS 的 schema 校验库,无原生制品、无安装脚本)—— 宿主半边用它向 引擎的设置服务注册本插件命名空间的 schema。这是唯一的dependencies。 - 文件:本插件不直接读写任何文件。 偏好值通过引擎自己的设置服务落进
$DSH_HOME/settings.yaml, 源码里没有node:fs。扫描器报的「files 信号」来自这个间接写入(以及注释里对settings.yaml的说明),不是直接的磁盘操作。 - 本机路由:一条。 宿主注册
GET/POST /composer-keys供页面读写偏好 —— 页面拿不到插件私有的设置 命名空间(设置 RPC 域只服务固定命名空间),这是唯一通道。路由走引擎自己的信任围栏(Host 白名单 + 浏览器会话 cookie),拿不到围栏时失败即关闭;不带 cookie 的裸curl会得到401。 - 出站网络:无。 页面半边的
fetch只打上面那条同源路由。 - 凭据 / 命令 / 原生制品 / 生命周期脚本:无。
- 失败边界: 路由或 settings 服务不可用时,键盘引擎仍然工作(只是失去持久化或设置入口)——
属于降级而不是崩溃。卸载后
settings.yaml里的composer-keys段会留下,重建插件即恢复原设置。
两部分构成
| 半边 | 文件 | 运行在 | 职责 |
|---|---|---|---|
| 宿主 | lib/index.js |
Node | 用引擎设置服务注册 composer-keys 命名空间(值落在 settings.yaml,随数据目录走),并开放 GET/POST /composer-keys |
| 页面 | client/client.js |
浏览器 | 注册「设置 → 通用」那一行 + 捕获阶段做键位重映射 |
为什么需要宿主半边:页面来自每次启动都变化的随机端口(外壳用 dsh web --port 0),浏览器存储
等于每次换源、存不住;而引擎的设置文档才是持久且随数据目录迁移的地方。页面半边拿不到插件自有
命名空间,所以由宿主路由转交 —— 与第三方侧边栏插件为自身偏好所用的方式一致。
设置行的样式逐条沿用通用页其它行:字体、颜色、间距,以及选择框(同样是 <button> 胶囊 + 箭头
SVG + 弹层,而不是原生 <select>),颜色全部走引擎主题变量,深浅色主题自动跟随。实测在真实设置弹窗
里取计算样式,胶囊的高度 / 圆角 / 背景 / 内边距 / 字号 / 行高 / 间距 / 颜色 8 项与「对话显示」的
控件逐项相等。
开发
node --check lib/index.js
node --check client/client.js
npm test # 本地行为测试(无网络、无引擎、无 DOM)
tests/test.mjs 覆盖:手势识别(含 Ctrl+Shift 折叠、Alt 放行)、三种手势 × 两种动作的干预判定、
输入法放行、非法偏好值不干预,以及「默认值不干预」这一关键性质。
许可
MIT
No comments yet. Be the first to write one.