UI Enhancements
dsh-mdbox
chi-hong22/dsh-mdbox · ★ 1
Markdown editing assistant for the DeepSeek Harness Web composer.
README body is shown in Chinese.
安装
dsh plugin --profile web add github:chi-hong22/dsh-mdboxGitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试
For install and environment setup, plus plugin development tutorials, see DSH community docs ↗
README
Table of contents
DeepSeek Harness (DSH) Web 输入框的 Markdown 编辑辅助插件。

Overview
DSH 的输入框是一个富文本编辑器(引用 chip 是原子节点):写列表要自己敲标记,改编号要自己数。这个插件补上几个编辑器里常见的小动作:Shift+Enter 在列表行续行(有序列表自动递增),Tab 缩进列表项(有序行编号重置为 1.),Shift+Tab 反缩进(编号自动并入外层继续)。另外在卡片右上角加了一个展开按钮,点一下把输入区纵向拉成一块长文本编辑区,再点收回。DSH 其实自带一个对话宽度拖拽手柄,但它平时完全透明、没人发现——插件把这条手柄显形并补上双击复位,窄窗口下也不再溢出。发出去的内容还是纯文本,Enter 还是发送,什么都不变。
如果你经常在对话里写清单、步骤和带缩进的提纲,或者嫌全屏时对话两侧留白太多,会用到它。
Compatibility
| 插件版本 | DSH 版本 | 输入面 | 状态 |
|---|---|---|---|
| 0.4.x | @deepseek-ai/dsh-* 0.2.1-rc.3 |
Lexical contenteditable | 当前,最后验证 2026-10-27 |
| 0.3.x | @deepseek-ai/dsh-* 0.2.1-rc.3 |
Lexical contenteditable | 仅空输入卡展开 |
| 0.2.x | @deepseek-ai/dsh-* 0.2.0-rc.2 |
Lexical contenteditable | 旧版,未在 0.2.1-rc.3 上验证 |
| 0.1.x | @deepseek-ai/dsh-* 0.1.0-rc.6 |
<textarea> |
已不再适配 |
DSH 0.2.0 把输入框从 <textarea> 换成了 Lexical 富文本编辑器(引用 chip 是原子 decorator 节点)。0.1.x 按 TEXTAREA 匹配输入框,在这套输入面上匹配不到任何元素,插件会静默失效(装在插件列表里但毫无反应)。0.2.0 起插件跟着换了对接面。
插件不 import DSH 内部模块,只依赖三个对外可见的锚点:
- DOM 锚点:输入框根元素
[data-composer-input](位于卡片[data-composer-card]内,role="textbox"、aria-multiline="true"); - 编辑器实例:输入框根元素上的
__lexicalEditor(Lexical 自己在contentEditable切换时挂载/删除); - 宽度手柄:会话 body 里的
[data-width-handle](DSH 自带的拖拽手柄;插件只给它加握把样式与提示,不接管拖拽)。
展开按钮另外用到卡片内的滚动区 [data-input-scroll](收起态高度由 DSH 的 --dsh-composer-text-max-height 决定,展开态插件直接改写该元素的高度)。
草稿文本按 DSH 自己的 detect 投影读:顶层段落之间算一个 \n,原子节点(引用 chip 等)折叠成一个 U+FFFC。编辑只落到需要改的片段上——不重建段落、不切开 chip,每次按键是一次 editor.update()(单次撤销事务)。
纯浏览器端插件,没有宿主半部,没有服务注入。插件不触碰 DSH 的 backdrop 渲染层(输入框可见文字由该层渲染),只做纯文本按键行为。
DSH 大版本如果再次调整输入面,需要跟着改,改动集中在 lib/client.js 的「文档投影」和「接入」两段:锚点变了改 composerInput(),编辑器实例位置变了改 getEditor(),段落/原子节点模型变了改 nodeText() / projectDraft()。
Install
装进官方 web profile(自带 dsh-base + dsh-web-app 两层):
dsh plugin --profile web add github:chi-hong22/dsh-mdbox
# 或本地 checkout:
dsh plugin --profile web add D:/path/to/dsh-mdbox
仓库包含构建产物(lib/ 已提交,纯 JS 没有构建步骤),安装后无需另外构建。dsh plugin add 会写入依赖并把本包加进 profile 的 bundles 列表,挂载行来自本包自带的 cordis.patch.yml,不需要手动改任何 profile 文件。
验证安装:
dsh --profile web --dump-config | Select-String ui-mdbox
输出里有下面几行就是装上了:
# == @chi-hong22/dsh-mdbox
- id: ui-mdbox
name: '@chi-hong22/dsh-mdbox'
重启 DSH,插件出现在 设置 → 插件 里。
手动安装只留给旧版本或调试:把本包作为 workspace/link 依赖装上,再在 profile 的 cordis.patch.yml 里手写一行 insert: [{ id: ui-mdbox, name: '@chi-hong22/dsh-mdbox' }]。别和 bundle 通道并用,同一行挂两次会双重加载。
Uninstall
dsh plugin --profile web remove @chi-hong22/dsh-mdbox
执行后重启 DSH 生效。
如果你当时是手动安装的(旧路径):删掉 profile cordis.patch.yml 里的 ui-mdbox 行、package.json 里的依赖、node_modules 里的链接,然后重启。
插件不写磁盘,删掉就是删干净了。升级:link: 安装的直接更新仓库目录内容,重启生效;npm 安装的用 dsh plugin --profile web add @chi-hong22/dsh-mdbox@<version>。
Quick start
没有插件级配置项。
- 第一项 # Shift+Enter → 续行 "- "
1. 子步骤 # Tab 缩进(编号重置为 1.);Shift+Enter → "2. ";Shift+Tab 反缩进(并入外层续号)
自动重编号:已有 1. a / 2. b 两行,在 1. a 行尾按 Shift+Enter 插入新行:
1. a
2. ← 新插入的空列表项
3. b ← 原 2. b 自动变为 3. b
空列表项上再按 Shift+Enter 退出列表(删掉行首标记)。普通行 Enter 发送,Shift+Enter 换行,Ctrl+Enter 强制发送。
缩进与反缩进(光标在列表行内按 Tab / Shift+Tab):
Tab:整行缩进 2 空格;有序行编号重置为1.(保留./)风格),同层后续行自动递增;Shift+Tab:删掉最多 2 空格缩进;有序行编号自动并入外层块继续(外层最大编号 +1);若被移出的是块首,其原缩进层剩余块从1.重新起号;- 无序行缩进/反缩进只动缩进,标记不变。
放大输入框
输入卡右上角有一个圆形展开按钮(22×22,对角线箭头图标):
- 点一下展开:输入区高度先到
clamp(240px, 52vh, 560px)(240ms 缓动过渡,不是瞬变),超出部分在输入区内滚动,列表续行/缩进/重编号的键位行为不变; - 展开后卡片上沿出现一条拖拽条:上下拖动可把高度改成任意值(下限 120px,上限视口高度的 85%),双击复位回
clamp(...);拖动只写卡片上的--mdbox-expand-height; - 再点一下收回:高度回到 DSH 自己的
--dsh-composer-text-max-height(336px); - 按钮在卡片内部,按下不会把焦点从输入面抢走,松手后光标仍在原处;
- 展开后整片输入区都是可编辑面:点空白处也能落光标(展开前那一片空白属于滚动容器,点上去不聚焦);
- 状态不持久化:切换会话或重启 DSH 后回到收起态,手动拖出来的高度也不保留;
- 工作区选择触发器(欢迎页里那个没有编辑器的假输入框)不挂按钮。
调整对话宽度
DSH 0.2.1 本来就带对话宽度拖拽手柄:消息列两侧各有一个透明热区,左右拖动可改内容宽度,松手后写进 localStorage(键 dsh.conversation.contentWidth)。问题是手柄平时完全透明,只有悬停时才出现一条 2px 竖线,几乎没人会发现。插件做了三件事:
- 显形:手柄中间常显一段 56px 高、3px 宽的圆角竖条(半透明,取主题的 label-tertiary 色),悬停或拖拽时变成主题蓝并轻微放大;同时关掉 DSH 自带的悬停跟随线(它跟着鼠标 Y 走,和居中竖条叠在一起会看着像两段线错位);
- 提示:鼠标移上去显示「左右拖动调整对话宽度,双击恢复默认」;
- 复位:双击手柄清掉偏好(
localStorage里的键 + DSH 写在祖先元素上的--dsh-chat-user-width内联变量,从手柄往上逐级清),内容宽度立刻回到默认的clamp(680px, 列宽 × 0.64, 920px); - 窄窗兜底:窗口比设定宽度还窄时,内容宽度取
min(设定值, 100%),不再横向溢出(内嵌会话自己那套宽度公式不动)。
宽度值本身仍由 DSH 管理:插件不新增滑块、面板或自己的持久化,只加样式与两个事件。默认宽度(未拖过)就是 DSH 的 clamp(680px, 64% 列宽, 920px),全屏 1200px 列宽下是 768px。
想更宽就拖到接近窗口边缘——但两侧各要留约 88px(手柄热区 min(10px, (100% - 内容宽)/2 - 48px) 加 24px 间隙才可点),DSH 把宽度夹在 max(640px, 列宽 − 176px),所以既做不到严格贴边,列宽不到约 816px 时宽度也不会小于 640px。窗口窄到内容已经贴满时,手柄宽度按同一公式收成 0,握把跟着隐藏(这时也没得可拖了)。
Configuration
没有配置项,也没有环境变量。行为常量在 lib/client.js 顶部:
- 列表标记:
-*+、1.1)。有序块内编号强制连续,块首保持你写的起始值;各缩进层的块独立记账,嵌套列表结束后回到外层继续递增; - 缩进宽度:2 空格(Tab 缩进时有序行编号重置为
1.,Shift+Tab 反缩进后编号并入外层续号); MAX_RENUMBER = 20000:草稿超过 20000 字符后跳过兜底重排,按键行为不受影响;- 展开高度:
clamp(240px, 52vh, 560px)(过渡 240ms);拖拽可调范围EXPAND_MIN_HEIGHT = 120到视口高度 ×EXPAND_MAX_VIEWPORT_RATIO = 0.85;输入面与占位符右侧各让出 36px 给按钮; - 对话宽度:不另设可调常量,宽度值全部沿用 DSH(
WIDTH_PREF_KEY = "dsh.conversation.contentWidth"是 DSH 的偏好键,下限 640px、两侧安全区 176px 都由 DSH 决定);插件只有两处自己的值——WIDTH_PREF_KEY(复位时删这个键)和提示文案WIDTH_HINT; - 自动重编号跳过代码围栏(``` / ~~~)里的行。
Permissions & data
不碰文件、网络和凭据;没有宿主半部,不上传也不存储任何内容。只读写当前会话输入框的草稿(浏览器内存内):按键行为之外,只在输入卡里挂一个展开按钮、一条拖拽条和一条插件样式(style[data-plugin],停用时一并移除);宽度功能只给 DSH 自己的 [data-width-handle] 加一段握把样式和一个 title 提示(title 在卸载时摘掉),唯一的写操作是你主动双击手柄时删掉 DSH 自己写进 localStorage 的 dsh.conversation.contentWidth,以及 DSH 写在祖先元素上的 --dsh-chat-user-width 内联变量(只清这一个变量,不新建任何键,也不动别的条目)。副作用都挂在 Cordis fiber 上,插件停用即还原。
Troubleshooting
| 现象 | 排查 |
|---|---|
| 重启后插件列表没有 dsh-mdbox | 检查 patch 行 name 与包 name 是否一致、node_modules 链接是否指向有效目录、YAML 缩进是否正确 |
| 插件在列表但功能不生效 | 确认焦点在输入框;IME 组合输入期间插件会主动让位(先确认候选词上屏);打开浏览器控制台看 dsh-mdbox 相关报错 |
| 升级 DSH 后整个插件失效(毫无反应) | 多半是输入面对不上:在浏览器控制台执行 document.querySelector("[data-composer-input]"),返回 null 说明锚点变了,按上面 Compatibility 一节改 lib/client.js |
| 想回滚 | 删除 patch 条目并重启;改动都在 profile 目录,插件没有持久化数据 |
日志:插件不写日志文件;运行期错误在浏览器开发者工具 Console 里。
Development
没有构建步骤:直接改 lib/client.js(浏览器半部)和 lib/index.js(宿主半部空实现),重启 DSH 生效。
├── package.json # 包声明(dsh.bundle.patch + dsh.client;peerDeps 仅 cordis)
├── cordis.patch.yml # bundle patch:insert ui-mdbox 行(官方挂载通道)
├── LICENSE
└── lib
├── index.js # 宿主半部(空实现,纯 UI 插件的 Loader 约定)
└── client.js # 浏览器半部(__ModuleLoader__ 模块格式)
lib/client.js 分四层,改行为时按层定位:常量 → 纯文本层(列表标记、缩进、重编号,只吃字符串)→ 文档投影层(Lexical 节点树 ↔ detect 文本坐标)→ 写入层(editor.update() 内落地 edits)→ 接入层(DOM 锚点、编辑器实例、键盘监听)。
手动验证清单(需要浏览器):列表续行/递增、中间插入后自动重编号、空项退出、Tab 缩进(编号重置为 1.)/ Shift+Tab 反缩进(并入外层续号)、IME 组合输入让位、Ctrl+Z 撤销、行内引用 chip 不被编辑切开、展开按钮(位置、展开/收起过渡、点击后光标仍在输入面、展开后点空白也能落光标)、拖拽条(上下拖动改高度、越界夹取、双击复位)、宽度手柄(握把可见、悬停提示、拖动改宽、双击复位、窄窗口不溢出)。
纯函数回归验证(Node,不需要浏览器,覆盖纯文本层与文档投影层):
node .scratch/editor-fixes/verify.mjs
node .scratch/expand/verify-expand.mjs # 展开按钮:挂载/翻转/焦点/幂等/拖拽调高/卸载清理
node .scratch/expand/verify-dom.mjs # 真实 Edge(headless + CDP):展开、点击聚焦、宽度手柄、窄窗兜底
verify-dom.mjs 会自己起一个 headless Edge(走系统安装的 Edge + CDP,不需要 Playwright),把 lib/client.js 内联进页面后真实注入插件、派发真实鼠标事件;加 --shot 会存一张 .scratch/expand/shot-dom.png。
这些脚本在仓库里不进版本控制(.scratch/ 是本地工作目录,被 .gitignore 排除),它在维护者的工作副本里,不是发布内容的一部分。
脚本直接读 lib/client.js、切出 factory 体求值,再对内部函数断言,所以不需要构建,也不需要测试框架。Lexical 一侧(editor.update() 与节点树改动)没有自动化测试,靠上面的浏览器清单人工验证。
发布到 npm 时,把 name 换成你自己有权控制的 scope,不要占用 @deepseek-ai/* 官方保留命名空间;并在 package.json 补上 repository 字段。
License & security
许可证:MIT(见 LICENSE)。
安全问题走 GitHub Issue;涉及未公开细节的不要写进公开 issue,先通过仓库所有者的 GitHub 主页私信联系。