# @mars.liu/dsh-rich-editor

[English](README.md) | 中文

![打开 Markdown 笔记本后的输入区](docs/img/notebook-panel.png)

[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 的第三方 Web 插件：输入区的富 Markdown 笔记本。浏览器半边向 `dsh-client-ui-conversation` 拥有的输入区贡献两个条目：工具行开关（`conversation.input.left`）与输入区上下文栈中的编辑卡片（`conversation.input.dock`）。两个条目共享同一个按会话作用域的 store 句柄，因此进行中的草稿在关闭/重开面板、表面重挂载后仍然保留；引擎按会话 id 切分 session store，每个会话各有自己的笔记本。

编辑器是运行 Markdown 语言扩展的 CodeMirror 6 实例：草稿语法高亮（含 GFM 表格）、原生 undo/redo 与选区，以及 Codex 风格的 Enter 列表编辑——在非空列表项上按 Enter 打开下一项（有序标记 `1.` → `2.` 递增，复选项重新变为未勾选，缩进保留，光标在行中时拆分该项），在空列表项上按 Enter 删除标记、回到普通文本编辑。非列表行的 Enter 落回普通换行。Tab 与 Shift-Tab 调整列表深度：列表项上按 Tab 缩进一级、按 Shift-Tab 升一级，最上一级列表项上按 Shift-Tab 退出列表（去掉标记与复选框、保留文字）；非列表行上两者均放行编辑器默认行为。`Mod+Enter` 提交。

卡片顶边有一对**编辑 / 预览**标签。预览通过 `marked` 实时渲染草稿，并经 DOMPurify 消毒后才接触 DOM——它只是格式化 Markdown（GFM 表格、任务列表、围栏代码、引用块）的只读视图，绝不改动提交内容：草稿文本原样走发送通道。切到预览时 CodeMirror 宿主只是隐藏而非卸载，undo 历史与光标在往返后原样保留。

![Enter 列表编辑：打开下一项、复选框重置、空项去标记](docs/img/enter-list-editing.png)

提交走按作用域寻址的 `conversation` 服务的 `send` 动词——与普通输入框的提交同一条路径——因此裁决、排队与 prompt 错误报告的行为与手打 prompt 完全一致。发送失败时会话的 composer 通知通道会浮现错误，草稿保留在面板中。

笔记本与原生输入框是同一份草稿的两个编辑面：打开面板时，原生编辑区已有的内容进入笔记本（原生为空时则把笔记本保留的草稿推下去）；面板打开期间，任一侧的每次编辑都实时镜像到另一侧——笔记本经会话 input facade 的 `setDraft` 单一写路径写入原生草稿，原生侧则订阅 facade 的 InputState store，以最小差异替换回流笔记本（相等即不写的守卫阻断回环，也保住编辑面外的光标）；关闭面板时最终文本留在原生编辑区，成功提交则同时清空两侧。

![组件关系：两个输入区插槽、一个会话 store、一个 send 动词](docs/img/architecture.png)

## 安装

```sh
dsh plugin --profile web add @mars.liu/dsh-rich-editor
```

包自带 bundle patch（`cordis.patch.yml` 添加 `ui-rich-editor` 行）；手动编排 profile 时把本 bundle 列进 `dsh.profile.bundles`。从源码构建：

```sh
pnpm install && pnpm run build && pnpm test
```

要求 dsh 家族 `^0.1.2-alpha.5`（已发布到 npm；0.1.2 线移除了 `dsh-client-runtime` / `dsh-client-web-react`，其 API 现由 `dsh-client-store`、`dsh-client-ui-renderer`、`dsh-api-session-controller`、`dsh-session` 提供），以及挂载了 `dsh-client-ui-conversation` 的 dsh web 组合。

### 开发说明

npm 上的 `0.0.1-rc.1` dsh 快照只发布了浏览器 loader bundle——node 半边几乎无导出，且若干改名前的依赖名（`dsh-compact`、`dsh-user-interaction`、`dsh-type-meta`、`dsh-client-ui-slash`）从未发布。本仓库用两个手段绕开：

- `package.json > overrides` 把缺失名指向 `vendor/stubs/` 下的空 stub（本插件的任何代码路径都不 import 它们）；
- `vitest.config.ts` 通过本地 harness checkout 的 `tsconfig.base.json` 中的 `paths` 映射解析全部 `@deepseek-ai/*` 说明符（`DSH_CHECKOUT` 环境变量，默认 `../deepseek-harness`），并 inline 全部 `@deepseek-ai/dsh-client-*` 包——与 dsh 树内测试获取 client API 的方式一致。

待 dsh 家族重新发布完整可安装的闭包后，移除这两个绕行。

## 已知限制与暂缓事项

- **有序列表重编号暂缓** — 续写有序列表会递增新项标记，但编辑或删除前项不会自动重编后续项。
- **暂未接入 `/` 与 `@` 触发** — 笔记本不参与斜杠命令与文件提及管线；这些手势属于其下方的普通输入框。
- **草稿仅会话生命周期** — store 让草稿跨重挂载与标签切换保留，但整页刷新会丢弃（无持久化键）。
- **无附件入口** — 笔记本未接入会话的图片附件路径，粘贴/拖入图片不生效。
- **浏览器 bundle 内联了约 268 kB gzip 的 CodeMirror** — 即便关闭嵌入代码高亮（`codeLanguages: []`），`@codemirror/lang-markdown` 仍静态依赖 `@codemirror/lang-html`（连带 JavaScript 与 CSS 解析器），客户端打包器会把整条链内联进来；后续瘦身方向是把编辑器挂载拆到动态导入之后。

## 许可

MIT
