xiaodao-editor 0.1.20 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.ZH.md +191 -68
  2. package/README.md +230 -78
  3. package/dist/block-editor.js +10444 -20908
  4. package/dist/block-editor.umd.cjs +30 -285
  5. package/dist/core/Editor.d.ts +32 -0
  6. package/dist/core/command/primitiveCommands.d.ts +1 -1
  7. package/dist/core/plugin/Plugin.d.ts +45 -5
  8. package/dist/core/state/EditorState.d.ts +5 -2
  9. package/dist/core/state/invert.d.ts +2 -2
  10. package/dist/core/types.d.ts +1 -1
  11. package/dist/extensions/Equation.d.ts +73 -8
  12. package/dist/extensions/Image.d.ts +54 -17
  13. package/dist/extensions/OrderedList.d.ts +1 -1
  14. package/dist/extensions/Paragraph.d.ts +1 -1
  15. package/dist/extensions/Table.d.ts +1 -1
  16. package/dist/extensions/math/ast.d.ts +140 -0
  17. package/dist/extensions/math/index.d.ts +18 -0
  18. package/dist/extensions/math/parser.d.ts +5 -0
  19. package/dist/extensions/math/renderHtml.d.ts +5 -0
  20. package/dist/extensions/math/renderTree.d.ts +16 -0
  21. package/dist/extensions/math/renderVNode.d.ts +4 -0
  22. package/dist/extensions/math/symbols.d.ts +37 -0
  23. package/dist/extensions/math/tokens.d.ts +22 -0
  24. package/dist/extensions/tableModel.d.ts +6 -6
  25. package/dist/i18n.d.ts +2 -2
  26. package/dist/index.d.ts +6 -3
  27. package/dist/style.css +1 -1
  28. package/dist/view/BlockEditor.vue.d.ts +0 -30
  29. package/dist/view/BlockList.vue.d.ts +2 -2
  30. package/dist/view/context.d.ts +11 -2
  31. package/dist/view/imageUpload.d.ts +20 -14
  32. package/dist/view/ui/icons.d.ts +22 -22
  33. package/dist/view/ui/inputRulesEngine.d.ts +2 -2
  34. package/dist/view/ui/popup.d.ts +1 -1
  35. package/dist/view/urlUtils.d.ts +2 -2
  36. package/package.json +2 -3
package/README.ZH.md CHANGED
@@ -14,23 +14,23 @@
14
14
 
15
15
  ## 功能特性
16
16
 
17
- - **12 种内置块类型** — 段落、h1–h6(标题)、无序列表、有序列表、待办事项、引用、代码块、**图片**、**公式**(LaTeX 数学公式)、**分割线**、**表格**、**目录**(共 **14 个扩展**,另含 Keymap 与 History 两个行为扩展)
18
- - **公式(LaTeX 数学)块** 通过 KaTeX 渲染居中的展示型公式。文档中**只保存原始 `expression` 字符串**——KaTeX 输出在渲染时即时计算、永不持久化,因此序列化保持精简。通过 `/公式` 斜杠命令或 `+` 菜单插入;空块会直接进入编辑态。点击块即可选中;右上角的浮动 ✎ 按钮(或点击空块)打开源码编辑器并带实时预览。支持块级选中,也**可作为子块嵌套**(按嵌套深度自动缩进)。Markdown 导出使用 `$$$ … $$$` 围栏块。
19
- - **表格块** — 基于 `attrs` 的 N×M 网格;新建表格默认列宽 120 px、默认启用标题行;行/列选择条 + 左上角角部全选手柄;行/列之间插入点;浮动操作栏提供合并/拆分单元格、**切换标题行**(设置 `attrs.headerRow`)、删除行/列/整个表;单元格使用独立的 `contenteditable`,支持段落/标题/代码块类型、富行内标记、单元格背景色与对齐;Tab 在单元格间导航,Enter 退出编辑(代码块单元格按 Enter 插入换行),Escape 失焦;仿 Arco Design 的内部水平滚动条;矩形选区遇到合并单元格时会自动扩展以保证永远不会只选中合并单元格的一半。
20
- - **行内样式标记** — 粗体、斜体、下划线、删除线、行内代码、**链接**(`Mod-K` 快捷键、粘贴 URL、自动识别、浮层查看/编辑/复制/删除、href 净化阻断 `javascript:` / XSS),以及按选区设置的文字颜色与背景色
21
- - **块级属性** — 对齐方式(左/中/右/两端)、文字颜色、背景色、缩进(0–10 级);图片额外携带 `src`、`alt`、`title`、`width`、`height`、`caption`、`fileId`
22
- - **斜杠菜单** — 输入 `/` 打开可搜索的命令面板;输入规则(`# `、`> `、`[] `、```` ``` ````)可即时转换块类型;`/image` 打开文件选择器
23
- - **块操作** — 拖拽手柄、悬浮工具栏、`+` 插入按钮,含「复制 / 剪切 / 上移 / 下移 / 删除」的操作菜单;**真实嵌套**(Tab / Shift-Tab 缩进/反缩进构建父子树;拖拽支持兄弟节点的上/下插入 + **"拖入块内"** 模式 — 在块中心停顿一下即可作为第一个子块嵌套进去);复制会克隆整个子树;图片额外提供替换 / 删除、角部等比缩放手柄、可编辑 caption
24
- - **固定工具栏(FixedToolbar)** — 常驻操作栏,在工具栏内部内嵌了上下文相关的 **HoverToolbar**(点击格式化按钮时可以保持文本选区不丢失)。通过 `toolbarPosition` prop 控制四种位置:`'auto'`(默认,桌面端顶、移动端底)、`'top'`(强制顶部)、`'bottom'`(强制底部),或 `'float'`(仅桌面端——隐藏 FixedToolbar,改用跟随文本选区浮现的浮动工具栏;移动端自动回退为 FixedToolbar)。工具栏在顶部时,PlusMenu / 手柄菜单会改为向下弹出。
25
- - **尺寸控制与内部滚动** — 通过 `width` 和 `height` prop 约束编辑器(数字按 px 解析)。内容区域会**在编辑器内部纵向滚动**,而不是无限向下生长,外部布局无需自行管理 overflow。
26
- - **剪贴板** — HTML 与纯文本的干净复制 / 剪切 / 粘贴;多块选区覆盖层;**粘贴 HTML `<img>` / 图片文件 + 拖拽文件到编辑器内会自动创建图片块并发起上传**;选中文本后粘贴 URL 会包裹为链接
27
- - **移动端支持** — 长按后开始选中文本,然后拖动手指即可**跨多个独立 `contenteditable` 块**进行选择(通过 hit-testing + overlay 实现,因为原生 Selection API 不支持跨块边界)。固定工具栏会自动落到屏幕底部,位于虚拟键盘之上。
28
- - **历史记录** — 按输入分组的撤销 / 重做(`Mod-Z` / `Mod-Shift-Z`);撤销只会恢复块本身,不会"复活"临时的上传状态
29
- - **国际化 i18n** — 通过 `locale` prop 切换 `zh-CN`(默认)与 `en-US`;零依赖翻译模块(不需要 `vue-i18n`)
30
- - **主题** — 通过 `theme` prop 切换 `light`(默认)与 `dark`;所有设计令牌均以 CSS 变量暴露
31
- - **可访问性** — 全程键盘导航,菜单具备 ARIA 角色
32
- - **目录(Table of Contents)** — 不可编辑的动态块,实时渲染文档中所有标题的层级列表;标题增删改时自动同步;点击条目可跳转到对应标题;通过斜杠菜单 `/目录` 插入
33
- - **Markdown 原生导入 / 导出** — `Editor` 实例暴露了 `toMarkdown()` 和 `setDocFromMarkdown(string)` 方法。往返转换直接基于实时 `DocState`(不经过中间的 `BlockData` 或外部转换器),标题/列表的嵌套层级、行内代码标记、块间空行分隔均能稳定保持。
17
+ - **12 种内置块类型**:段落、h1–h6(标题)、无序列表、有序列表、待办事项、引用、代码块、**图片**、**公式**(LaTeX 数学公式)、**分割线**、**表格**、**目录**(共 **14 个扩展**,另含 Keymap 与 History 两个行为扩展)
18
+ - **公式(LaTeX 数学)块**:通过**内置零依赖数学渲染器**渲染居中的展示型公式(轻量 LaTeX 数学子集:分数、根号、上下标、希腊字母、常用函数、大型运算符、矩阵、aligned 多行对齐)。文档中**只保存原始 `expression` 字符串**:渲染输出在渲染时即时计算、永不持久化,因此序列化保持精简。渲染器**可插拔**:通过 `createEquationExtension({ renderer })` 追加在 `BuiltinExtensions` 之后(name-based 去重,后排赢出)即可注入 KaTeX、MathJax 或任何自定义引擎获得完整 LaTeX 支持;**`<BlockEditor>` 没有 `equationRenderer` 这个 prop**。通过 `/公式` 斜杠命令或 `+` 菜单插入;空块会直接进入编辑态。点击块即可选中;右上角的浮动 ✎ 按钮(或点击空块)打开源码编辑器并带实时预览。支持块级选中,也**可作为子块嵌套**(按嵌套深度自动缩进)。Markdown 导出使用 `$$$ … $$$` 围栏块。
19
+ - **表格块**:基于 `attrs` 的 N×M 网格;新建表格默认列宽 120 px、默认启用标题行;行/列选择条 + 左上角角部全选手柄;行/列之间插入点;浮动操作栏提供合并/拆分单元格、**切换标题行**(设置 `attrs.headerRow`)、删除行/列/整个表;单元格使用独立的 `contenteditable`,支持段落/标题/代码块类型、富行内标记、单元格背景色与对齐;Tab 在单元格间导航,Enter 退出编辑(代码块单元格按 Enter 插入换行),Escape 失焦;仿 Arco Design 的内部水平滚动条;矩形选区遇到合并单元格时会自动扩展以保证永远不会只选中合并单元格的一半。
20
+ - **行内样式标记**:粗体、斜体、下划线、删除线、行内代码、**链接**(`Mod-K` 快捷键、粘贴 URL、自动识别、浮层查看/编辑/复制/删除、href 净化阻断 `javascript:` / XSS),以及按选区设置的文字颜色与背景色
21
+ - **块级属性**:对齐方式(左/中/右/两端)、文字颜色、背景色、缩进(0–10 级);图片额外携带 `src`、`alt`、`title`、`width`、`height`、`caption`、`fileId`
22
+ - **斜杠菜单**:输入 `/` 打开可搜索的命令面板;输入规则(`# `、`> `、`[] `、```` ``` ````)可即时转换块类型;`/image` 打开文件选择器
23
+ - **块操作**:拖拽手柄、悬浮工具栏、`+` 插入按钮,含「复制 / 剪切 / 上移 / 下移 / 删除」的操作菜单;**真实嵌套**(Tab / Shift-Tab 缩进/反缩进构建父子树;拖拽支持兄弟节点的上/下插入 + **"拖入块内"** 模式:在块中心停顿一下即可作为第一个子块嵌套进去);复制会克隆整个子树;图片额外提供替换 / 删除、角部等比缩放手柄、可编辑 caption
24
+ - **固定工具栏(FixedToolbar)**:常驻操作栏,在工具栏内部内嵌了上下文相关的 **HoverToolbar**(点击格式化按钮时可以保持文本选区不丢失)。通过 `toolbarPosition` prop 控制四种位置:`'auto'`(默认,桌面端顶、移动端底)、`'top'`(强制顶部)、`'bottom'`(强制底部),或 `'float'`(仅桌面端:隐藏 FixedToolbar,改用跟随文本选区浮现的浮动工具栏;移动端自动回退为 FixedToolbar)。工具栏在顶部时,PlusMenu / 手柄菜单会改为向下弹出。
25
+ - **尺寸控制与内部滚动**:通过 `width` 和 `height` prop 约束编辑器(数字按 px 解析)。内容区域会**在编辑器内部纵向滚动**,而不是无限向下生长,外部布局无需自行管理 overflow。
26
+ - **剪贴板**:HTML 与纯文本的干净复制 / 剪切 / 粘贴;多块选区覆盖层;**粘贴 HTML `<img>` / 图片文件 + 拖拽文件到编辑器内会自动创建图片块并发起上传**;选中文本后粘贴 URL 会包裹为链接
27
+ - **移动端支持**:长按后开始选中文本,然后拖动手指即可**跨多个独立 `contenteditable` 块**进行选择(通过 hit-testing + overlay 实现,因为原生 Selection API 不支持跨块边界)。固定工具栏会自动落到屏幕底部,位于虚拟键盘之上。
28
+ - **历史记录**:按输入分组的撤销 / 重做(`Mod-Z` / `Mod-Shift-Z`);撤销只会恢复块本身,不会"复活"临时的上传状态
29
+ - **国际化 i18n**:通过 `locale` prop 切换 `zh-CN`(默认)与 `en-US`;零依赖翻译模块(不需要 `vue-i18n`)
30
+ - **主题**:通过 `theme` prop 切换 `light`(默认)与 `dark`;所有设计令牌均以 CSS 变量暴露
31
+ - **可访问性**:全程键盘导航,菜单具备 ARIA 角色
32
+ - **目录(Table of Contents)**:不可编辑的动态块,实时渲染文档中所有标题的层级列表;标题增删改时自动同步;点击条目可跳转到对应标题;通过斜杠菜单 `/目录` 插入
33
+ - **Markdown 原生导入 / 导出**:`Editor` 实例暴露了 `toMarkdown()` 和 `setDocFromMarkdown(string)` 方法。往返转换直接基于实时 `DocState`(不经过中间的 `BlockData` 或外部转换器),标题/列表的嵌套层级、行内代码标记、块间空行分隔均能稳定保持。
34
34
 
35
35
  ## 快速开始
36
36
 
@@ -41,12 +41,12 @@ npm install xiaodao-editor
41
41
 
42
42
  ```vue
43
43
  <script setup lang="ts">
44
- import { ref } from 'vue'
45
- import { BlockEditor } from 'xiaodao-editor'
46
- import type { DocumentData } from 'xiaodao-editor'
47
- import 'xiaodao-editor/style.css'
44
+ import { ref } from 'vue';
45
+ import { BlockEditor } from 'xiaodao-editor';
46
+ import type { DocumentData } from 'xiaodao-editor';
47
+ import 'xiaodao-editor/style.css';
48
48
 
49
- const doc = ref<DocumentData>({ blocks: [] })
49
+ const doc = ref<DocumentData>({ blocks: [] });
50
50
  </script>
51
51
 
52
52
  <template>
@@ -54,35 +54,158 @@ const doc = ref<DocumentData>({ blocks: [] })
54
54
  </template>
55
55
  ```
56
56
 
57
- 编辑器默认内置全部 14 种扩展 — 除非你需要自定义集合,否则无需传入 `extensions`。
57
+ 编辑器默认内置全部 14 种扩展:除非你需要自定义集合,否则无需传入 `extensions`。
58
+
59
+ ## 可插拔公式渲染器
60
+
61
+ 公式块只保存原始 LaTeX `expression` 字符串,字符串如何变成像素由可插拔的渲染器决定:
62
+
63
+ ```ts
64
+ interface EquationRenderer {
65
+ render(expression: string, options?: { displayMode?: boolean }): EquationRenderResult;
66
+ }
67
+
68
+ interface EquationRenderResult {
69
+ /** 安全 HTML 字符串,始终可用(导出 / SSR / 非 Vue 消费方)。 */
70
+ html: string;
71
+ /** 可选的 Vue VNode 树;存在时视图直接渲染它(不走 innerHTML)。 */
72
+ vnode: VNode | VNode[] | null;
73
+ /** 表达式存在 error 级诊断时为 true。 */
74
+ error: boolean;
75
+ diagnostics: readonly EquationDiagnostic[];
76
+ }
77
+ ```
78
+
79
+ 默认使用**内置数学渲染器**,即零第三方依赖的轻量 LaTeX 数学子集实现
80
+ (tokenizer → parser → AST → DOM):数字/标识符、运算符(`\pm \times \div \cdot \le \ge \neq` 等)、
81
+ 上下标、`\frac`、`\sqrt` / `\sqrt[n]`、希腊字母、`\sin \cos \tan \log \ln \exp \lim \min \max`、
82
+ 大型运算符(`\sum \prod \int`,display 模式下上下标置于符号上下方)、
83
+ `\begin{matrix}` 与 `\begin{aligned}`。未知命令会优雅降级(原样显示),
84
+ 语法错误显示内联警告而不会让编辑器崩溃:源码始终保留,修复后自动重新解析。
85
+ 它**不是**完整 TeX 引擎;需要完整支持时注入外部渲染器:
86
+
87
+ ```vue
88
+ <script setup lang="ts">
89
+ // KaTeX 本身不是 xiaodao-editor 的依赖,需自行安装:
90
+ // pnpm add katex
91
+ import katex from 'katex';
92
+ // ★ KaTeX 的 CSS 必须显式引入 ★
93
+ // KaTeX 渲染出的是平铺的 HTML 树,「上下标、积分限、分式、∫ 上下限」等
94
+ // 一切排版都由 .katex / .strut / <sup> / <sub> 等类名驱动。没引 CSS 时,
95
+ // 所有 span 会按行内文本平铺,「∫ab」「αx3」「e−λx」之类错位就是这症状。
96
+ // 在 app 入口加载一次即可;这里引入只是让 demo 自洽。
97
+ import 'katex/dist/katex.min.css';
98
+ import { createEquationExtension, BuiltinExtensions, type EquationRenderer } from 'xiaodao-editor';
99
+
100
+ const katexRenderer: EquationRenderer = {
101
+ render(expression, options) {
102
+ const src = expression ?? '';
103
+ try {
104
+ // `output: 'htmlAndMathml'` 与项目 449885a 之前的版本一致:视觉上
105
+ // 与 HTML 无异,同时内联一段 <math>,对读屏 / SSR 友好。改 `html`
106
+ // 也可以,但会丢掉 MathML 分支。
107
+ const html = katex.renderToString(src, {
108
+ displayMode: options?.displayMode ?? true,
109
+ throwOnError: false, // 不抛错:出错片段包成 <span class="katex-error">…</span>
110
+ trust: false, // 必选:trust: true 会放开 \href / \url 的原始 HTML 注入(XSS)
111
+ strict: false, // 宽松:未知命令不报警
112
+ output: 'htmlAndMathml',
113
+ });
114
+ // KaTeX 标记错误的 class 是 `katex-error`(不是 merror;merror 是更新版)。
115
+ // 命中后让 Equation 块显示 ⚠ 徽章。
116
+ const error = /class="katex-error"/.test(html);
117
+ return { html, vnode: null, error, diagnostics: [] };
118
+ } catch {
119
+ return { html: '', vnode: null, error: true, diagnostics: [] };
120
+ }
121
+ },
122
+ };
123
+
124
+ // 把带自定义渲染器的扩展 append 到 BuiltinExtensions 之后,
125
+ // 注册器按 name 去重(后写赢),所以最终生效的是这个。
126
+ // 不要并列塞两个 createEquationExtension,二选一。
127
+ const extensions = [
128
+ ...BuiltinExtensions,
129
+ createEquationExtension({ renderer: katexRenderer }),
130
+ ];
131
+ </script>
132
+
133
+ <template>
134
+ <BlockEditor v-model="doc" :extensions="extensions" />
135
+ </template>
136
+ ```
137
+
138
+ `createEquationExtension({ renderer })` 把带自定义渲染器的公式扩展追加到 `BuiltinExtensions` 之后(name-based 去重,后排赢出)即可覆盖内置版本;`<BlockEditor>` 没有 `equationRenderer` prop。也可以从 `xiaodao-editor` 导入内置引擎的基础件(`parseMath`、`SUPPORTED_COMMANDS` 等),在 AST 之上构建自定义渲染器。
58
139
 
59
140
  ## Props 属性
60
141
 
61
- | 属性名 | 类型 | 默认值 | 说明 |
62
- | ----------------- | -------------------------------------- | ---------------------- | ----------------------------------------------------------------------------- |
63
- | `modelValue` | `DocumentData` | `{ blocks: [] }` | 文档 JSON(通过 `v-model` 双向绑定)。 |
64
- | `extensions` | `readonly Extension[]` | `BuiltinExtensions` | 要注册的扩展。可覆盖此值以添加自定义块或移除内置块。 |
65
- | `editable` | `boolean` | `true` | 为 `false` 时进入只读模式。 |
66
- | `placeholder` | `string` | 跟随 locale | 首个空块的占位符,默认使用本地化字符串。 |
67
- | `theme` | `'light' \| 'dark'` | `'light'` | 颜色主题。对应类名会应用到 `.block-editor` 并同步到 `<body>`。 |
68
- | `locale` | `'zh-CN' \| 'en-US'` | `'zh-CN'` | UI 语言。非 `'zh-CN'` 的任何非空值都会落到 `'en-US'`。 |
69
- | `uploadImage` | `UploadImageHandler` | 内存内 mock | 图片上传钩子;签名:`(name, file, controller, onProgress) => Promise<ImageUploadResult>`。要持久化文档**必须**提供此 prop(默认 mock 使用不可序列化的 `blob:` URL)。 |
70
- | `width` | `string \| number` | `undefined` | 可选:编辑器宽度。数字按 CSS px 解析;字符串直接使用(如 `'800px'`、`'100%'`)。未设置时默认撑满容器(`width: 100%`)。 |
71
- | `height` | `string \| number` | `undefined` | 可选:编辑器高度。设置后内容区域会在编辑器**内部**滚动,不再无限向下生长;未设置时编辑器随内容扩展,由宿主页面接管滚动。 |
142
+ | 属性名 | 类型 | 默认值 | 说明 |
143
+ | ----------------- | ---------------------------------------- | ---------------------- | ----------------------------------------------------------------------------- |
144
+ | `modelValue` | `DocumentData` | `{ blocks: [] }` | 文档 JSON(通过 `v-model` 双向绑定)。 |
145
+ | `extensions` | `readonly Extension[]` | `BuiltinExtensions` | 要注册的扩展。可覆盖此值以添加自定义块、覆盖公式渲染器(`createEquationExtension({ renderer })`)、或替换图片上传管线(`createImageExtension({ upload, onFileCleanup })`)。 |
146
+ | `editable` | `boolean` | `true` | 为 `false` 时进入只读模式。 |
147
+ | `placeholder` | `string` | 跟随 locale | 首个空块的占位符,默认使用本地化字符串。 |
148
+ | `theme` | `'light' \| 'dark'` | `'light'` | 颜色主题。对应类名会应用到 `.block-editor` 并同步到 `<body>`。 |
149
+ | `locale` | `'zh-CN' \| 'en-US'` | `'zh-CN'` | UI 语言。非 `'zh-CN'` 的任何非空值都会落到 `'en-US'`。 |
150
+ | `width` | `string \| number` | `undefined` | 可选:编辑器宽度。数字按 CSS px 解析;字符串直接使用(如 `'800px'`、`'100%'`)。未设置时默认撑满容器(`width: 100%`)。 |
151
+ | `height` | `string \| number` | `undefined` | 可选:编辑器高度。设置后内容区域会在编辑器**内部**滚动,不再无限向下生长;未设置时编辑器随内容扩展,由宿主页面接管滚动。 |
72
152
  | `toolbarPosition` | `'auto' \| 'top' \| 'bottom' \| 'float'` | `'auto'` | 工具栏 / 操作栏的位置。`'auto'` = 桌面端自动顶栏、移动端自动底栏(位于虚拟键盘上方)。`'float'`(仅桌面端)隐藏 `FixedToolbar`,改用跟随文本/表格选区的浮动工具栏(HoverToolbar);移动端自动回退为 `FixedToolbar`。 |
73
153
 
154
+ ### 可插拔图片上传
155
+
156
+ 图片上传由 `createImageExtension({ upload, onFileCleanup })` 在 `BuiltinExtensions`
157
+ 之后组成来注入。这会替换默认的 mock 上传(默认 mock 会产生 `blob:` URL,
158
+ 页面刷新后失效),由宿主完全控制上传管线。
159
+
160
+ ```ts
161
+ import {
162
+ BuiltinExtensions,
163
+ createImageExtension,
164
+ type UploadImageHandler,
165
+ } from 'xiaodao-editor';
166
+
167
+ const upload: UploadImageHandler = async (name, file, controller, onProgress) => {
168
+ // 1. 向你的后端请求签名 URL
169
+ const { url, fields } = await api.presign(name);
170
+
171
+ // 2. PUT 文件(带 abort + 进度上报)
172
+ const xhr = new XMLHttpRequest();
173
+ xhr.upload.addEventListener('progress', (e) => {
174
+ if (e.lengthComputable) onProgress(Math.round((e.loaded / e.total) * 100));
175
+ });
176
+ // ...把 controller.signal.abort 接到 xhr.abort() ...
177
+
178
+ // 3. resolve 出公开 URL + 一个稳定的 fileId,便于清理回调触发
179
+ return {
180
+ url: `${CDN}/${name}`,
181
+ width: 0, height: 0,
182
+ fileId: hashOf(name + size), // 宿主自选的稳定 id;传 0 会禁掉清理回调
183
+ };
184
+ };
185
+
186
+ const extensions = [
187
+ ...BuiltinExtensions.filter((e) => e.name !== 'image'),
188
+ createImageExtension({
189
+ upload,
190
+ onFileCleanup: (fileId) => api.deleteCloudFile(fileId),
191
+ }),
192
+ ];
193
+ ```
194
+
195
+ `BuiltinExtensions` 默认携带的 `ImageExtension` 使用内存内 mock 上传(带随机
196
+ 失败率),仅作为演示;**不可用于持久化文档**。
197
+
74
198
  ### Emits 事件
75
199
 
76
200
  | 事件名 | 载荷 | 触发时机 |
77
201
  | ------------------------- | -------------- | ------------------------------------------------------------------------------------- |
78
202
  | `update:modelValue` | `DocumentData` | 文档变更(失焦时做防抖处理)。 |
79
- | `cleanup:image-file` | `number` | `fileId` 引用计数归零(最后引用该 fileId 的图片块被删除或 src 被替换)。载荷为该 `fileId`;0 不会触发。消费方可据此回收云存储。 |
80
203
 
81
204
  ### Expose 暴露成员
82
205
 
83
206
  | 成员名 | 类型 | 说明 |
84
207
  | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
85
- | `editor` | `Editor` | 与框架无关的 `Editor` 实例。常用方法:<br>`toData(): DocumentData` — 导出 JSON。<br>`setDocument(json: DocumentData)` — 用新 JSON 替换整个文档。<br>`toMarkdown(): string` — 原生导出 Markdown。<br>`setDocFromMarkdown(md: string)` — 原生导入 Markdown(会重置历史)。 |
208
+ | `editor` | `Editor` | 与框架无关的 `Editor` 实例。常用方法:<br>`toData(): DocumentData`:导出 JSON。<br>`setDocument(json: DocumentData)`:用新 JSON 替换整个文档。<br>`toMarkdown(): string`:原生导出 Markdown。<br>`setDocFromMarkdown(md: string)`:原生导入 Markdown(会重置历史)。 |
86
209
 
87
210
  ## 主题化
88
211
 
@@ -96,7 +219,7 @@ const doc = ref<DocumentData>({ blocks: [] })
96
219
  }
97
220
  ```
98
221
 
99
- `.block-editor` 元素**故意不设置背景** — 由宿主页面控制编辑器的背景,以便自然融入周围 UI。如需显式设置:
222
+ `.block-editor` 元素**故意不设置背景**:由宿主页面控制编辑器的背景,以便自然融入周围 UI。如需显式设置:
100
223
 
101
224
  ```css
102
225
  .block-editor {
@@ -116,14 +239,14 @@ const doc = ref<DocumentData>({ blocks: [] })
116
239
  | `OrderedListExtension` | `orderedList` | 自动编号;可通过 `attrs.startNumber` 显式覆盖起始序号。 |
117
240
  | `TodoListExtension` | `todoList` | 通过 `attrs.checked` 控制复选框状态。 |
118
241
  | `QuoteExtension` | `quote` | 引用块。schema 禁用了行内斜体。 |
119
- | `CodeBlockExtension` | `codeBlock` | `attrs.language` 设置语言;隔离模式 — Enter 插入换行。 |
120
- | `ImageExtension` | `image` | `content: 'none'`;属性:`src/alt/title/width/height/caption/fileId`;序列化:HTML `<figure>`/`<img>` + Markdown `![alt](url "title")`;提供替换 / 删除 / 等比缩放手柄 + 可编辑 caption;通过 `uploadImage` prop `cleanup:image-file` 事件走上传侧信道。 |
121
- | `EquationExtension` | `equation` | `content: 'none'`;隔离型块——只保存 `attrs.expression`(原始 LaTeX)。KaTeX 在渲染时即时计算居中展示公式(输出永不持久化)。通过 `/公式` 或 `+` 插入;空块自动进入编辑态;浮动 ✎ 按钮打开带实时预览的源码编辑器。支持块级选中与嵌套(作为子块时随深度缩进,`attrs.indent` 即为深度镜像)。Markdown 导出使用 `$$$ … $$$` 围栏块。 |
242
+ | `CodeBlockExtension` | `codeBlock` | `attrs.language` 设置语言;隔离模式:Enter 插入换行。 |
243
+ | `ImageExtension` | `image` | `content: 'none'`;属性:`src/alt/title/width/height/caption/fileId`;序列化:HTML `<figure>`/`<img>` + Markdown `![alt](url "title")`;提供替换 / 删除 / 等比缩放手柄 + 可编辑 caption;通过 `createImageExtension({ upload, onFileCleanup })` 注入上传侧信道,详见「可插拔图片上传」。`BuiltinExtensions` 默认携带的 `ImageExtension` 使用内存内 mock 上传(`blob:` URL 无法跨刷新存活),不会触发任何 `onFileCleanup` 回调。 |
244
+ | `EquationExtension` | `equation` | `content: 'none'`;隔离型块:只保存 `attrs.expression`(原始 LaTeX)。**默认使用零依赖的内置数学渲染器**(轻量 LaTeX 子集,见「可插拔公式渲染器」),通过 `createEquationExtension({ renderer })` 追加在 `BuiltinExtensions` 之后(name-based 去重,后排赢出)注入 KaTeX/MathJax 即可覆盖。**`<BlockEditor>` 没有 `equationRenderer` prop**。通过 `/公式` 或 `+` 插入;空块自动进入编辑态;浮动 ✎ 按钮打开带实时预览的源码编辑器。支持块级选中与嵌套(作为子块时随深度缩进,`attrs.indent` 即为深度镜像)。Markdown 导出使用 `$$$ … $$$` 围栏块。 |
122
245
  | `TableExtension` | `table` | `content: 'none'`;属性:`rows/cols/cells/colWidths/headerRow`;单元格 InlineSeq 含 cellType/align/bgColor/rowspan/colspan;行/列选择条 + 角部全选手柄;浮动操作栏提供合并/拆分、**切换标题行**、删除行/列/表格;行/列插入点;合并单元格选区自动扩展为完整矩形。默认列宽 120 px;新建表格默认 `headerRow: true`。 |
123
246
  | `DividerExtension` | `divider` | 隔离型水平分割线。 |
124
- | `TableOfContentsExtension` | `tableOfContents` | `content: 'none'`;空 attrs — 标题列表是每次渲染时从编辑器状态计算的**动态视图**。不可编辑块(`editable: false`);按文档顺序收集所有 `heading` 块(表格单元格内的标题自动排除);点击条目滚动到对应标题。序列化输出空字符串(真正的标题由各自的块导出)。 |
125
- | `KeymapExtension` | | 绑定 Enter / Backspace / ↑ / ↓。 |
126
- | `HistoryExtension` | | `Mod-Z` / `Mod-Shift-Z` / `Mod-Y` 撤销 / 重做快捷键。 |
247
+ | `TableOfContentsExtension` | `tableOfContents` | `content: 'none'`;空 attrs:标题列表是每次渲染时从编辑器状态计算的**动态视图**。不可编辑块(`editable: false`);按文档顺序收集所有 `heading` 块(表格单元格内的标题自动排除);点击条目滚动到对应标题。序列化输出空字符串(真正的标题由各自的块导出)。 |
248
+ | `KeymapExtension` | | 绑定 Enter / Backspace / ↑ / ↓。 |
249
+ | `HistoryExtension` | | `Mod-Z` / `Mod-Shift-Z` / `Mod-Y` 撤销 / 重做快捷键。 |
127
250
 
128
251
  要使用**自定义子集**,请显式传入 `extensions`:
129
252
 
@@ -131,30 +254,30 @@ const doc = ref<DocumentData>({ blocks: [] })
131
254
  import {
132
255
  ParagraphExtension, HeadingExtension,
133
256
  KeymapExtension, HistoryExtension,
134
- } from 'xiaodao-editor'
257
+ } from 'xiaodao-editor';
135
258
 
136
259
  const extensions = [
137
260
  ParagraphExtension, HeadingExtension,
138
261
  KeymapExtension, HistoryExtension,
139
- ]
262
+ ];
140
263
  ```
141
264
 
142
265
  ## 文档模型
143
266
 
144
267
  ```ts
145
268
  interface Block {
146
- id: BlockId
147
- type: BlockType
148
- attrs: Attrs // 例如 { level: 2, align: 'center', color: 'red' }
149
- content: InlineSeq // 带可选标记的文本片段
150
- children: BlockId[] // 子块 id — 真实嵌套:paragraph/heading +
269
+ id: BlockId;
270
+ type: BlockType;
271
+ attrs: Attrs; // 例如 { level: 2, align: 'center', color: 'red' }
272
+ content: InlineSeq; // 带可选标记的文本片段
273
+ children: BlockId[]; // 子块 id,真实嵌套:paragraph/heading +
151
274
  // 3 种列表块可以做父;任何块类型都能做子。`attrs.indent`
152
275
  // 是嵌套深度的衍生镜像。
153
276
  }
154
277
 
155
278
  interface DocumentData {
156
- id?: string
157
- blocks: BlockData[] // 嵌套 JSON;导入时会做规范化
279
+ id?: string;
280
+ blocks: BlockData[]; // 嵌套 JSON;导入时会做规范化
158
281
  }
159
282
  ```
160
283
 
@@ -174,7 +297,7 @@ const doc: DocumentData = {
174
297
  { type: 'codeBlock', attrs: { language: 'ts' }, content: [{ type: 'text', text: 'const x = 1' }] },
175
298
  { type: 'image', attrs: {
176
299
  src: 'https://cdn.example.com/hero.png', alt: '主图',
177
- width: 1200, height: 630, caption: '图 1 — 架构总览', fileId: 42,
300
+ width: 1200, height: 630, caption: '图 1:架构总览', fileId: 42,
178
301
  }, content: [] },
179
302
  { type: 'divider' },
180
303
  { type: 'table', attrs: {
@@ -192,7 +315,7 @@ const doc: DocumentData = {
192
315
  }, content: [] },
193
316
  { type: 'equation', attrs: { expression: 'E = mc^2' }, content: [] },
194
317
  ],
195
- }
318
+ };
196
319
  ```
197
320
 
198
321
  ## 自定义扩展
@@ -200,9 +323,9 @@ const doc: DocumentData = {
200
323
  一个块类型扩展需要提供 `name`(名称)、`schema`(块类型、内容类型、带默认值与校验器的属性)和 `renderer`(接收 `block` 与 `placeholder` props 的 Vue 组件)。扩展还可以贡献输入规则、斜杠命令、键位映射绑定以及 Markdown/HTML 序列化。最小化的块类型扩展只需提供 schema 和 Vue 渲染器:
201
324
 
202
325
  ```ts
203
- import { defineComponent, h } from 'vue'
204
- import type { Extension } from 'xiaodao-editor'
205
- import { BlockContent } from 'xiaodao-editor'
326
+ import { defineComponent, h } from 'vue';
327
+ import type { Extension } from 'xiaodao-editor';
328
+ import { BlockContent } from 'xiaodao-editor';
206
329
 
207
330
  const CalloutBlock = defineComponent({
208
331
  props: ['block', 'placeholder'],
@@ -211,9 +334,9 @@ const CalloutBlock = defineComponent({
211
334
  block: props.block,
212
335
  placeholder: props.placeholder,
213
336
  class: 'block-callout',
214
- })
337
+ });
215
338
  },
216
- })
339
+ });
217
340
 
218
341
  export const CalloutExtension: Extension = {
219
342
  name: 'callout',
@@ -226,25 +349,25 @@ export const CalloutExtension: Extension = {
226
349
  },
227
350
  },
228
351
  renderer: { component: CalloutBlock },
229
- }
352
+ };
230
353
  ```
231
354
 
232
355
  将其与内置扩展一起注册:
233
356
 
234
357
  ```ts
235
- import { BuiltinExtensions, BlockEditor } from 'xiaodao-editor'
236
- import { CalloutExtension } from './callout'
358
+ import { BuiltinExtensions, BlockEditor } from 'xiaodao-editor';
359
+ import { CalloutExtension } from './callout';
237
360
 
238
- const extensions = [...BuiltinExtensions, CalloutExtension]
361
+ const extensions = [...BuiltinExtensions, CalloutExtension];
239
362
  ```
240
363
 
241
364
  ## 架构
242
365
 
243
- - **`src/core/`** — 与框架无关的引擎(零 Vue 导入,由 ESLint 强制约束)。负责文档模型、事务、历史记录、命令、schema、扩展注册表,以及 **Markdown 原生导入/导出**
244
- (`Editor.toMarkdown()` / `Editor.setDocFromMarkdown()` — 直接操作 `DocState`,不经过中间的 `BlockData`)。
245
- - **`src/view/`** — Vue 桥接层:`BlockEditor.vue`(根组件)、`BlockList`、`BlockHost`、`BlockContent`(每个块的 `contenteditable`),以及 UI 组件(`BlockHandle`、`BlockSettingsMenu`、`HoverToolbar`、`PlusMenu`、`OrderedListMenu`、`NumberPicker`、`CodeLangPicker`、`LinkPopover`、`FixedToolbar`)。
246
- - **`src/extensions/`** — 13 种内置扩展,以及 `_commonAttrs.ts`(共享的 align / color / bgColor / indent 规格与颜色预设,`ImageExtension` 还在此层实现了上传侧信道的渲染逻辑)。**表格** 位于 `Table.ts`(Vue 渲染器 + 命令注册)与 `tableModel.ts`(纯函数式结构操作:插入/删除行/列、合并/拆分单元格、合并选区完整矩形扩展、切换标题行、列宽辅助、HTML/Markdown 序列化、attrs 校验/规整)。**分割线** 位于 `Divider.ts`。**目录** 位于 `TableOfContents.ts`(不可编辑的动态块,实时渲染文档标题列表)。
247
- - **`src/i18n.ts`** — locale + 主题模块;通过 Vue 的 provide/inject 提供 `t(key)`,让 `<Teleport>` 渲染的浮层也保持响应式。
366
+ - **`src/core/`**:与框架无关的引擎(零 Vue 导入,由 ESLint 强制约束)。负责文档模型、事务、历史记录、命令、schema、扩展注册表,以及 **Markdown 原生导入/导出**
367
+ (`Editor.toMarkdown()` / `Editor.setDocFromMarkdown()`,直接操作 `DocState`,不经过中间的 `BlockData`)。
368
+ - **`src/view/`**:Vue 桥接层:`BlockEditor.vue`(根组件)、`BlockList`、`BlockHost`、`BlockContent`(每个块的 `contenteditable`),以及 UI 组件(`BlockHandle`、`BlockSettingsMenu`、`HoverToolbar`、`PlusMenu`、`OrderedListMenu`、`NumberPicker`、`CodeLangPicker`、`LinkPopover`、`FixedToolbar`)。
369
+ - **`src/extensions/`**:14 种内置扩展,以及 `_commonAttrs.ts`(共享的 align / color / bgColor / indent 规格与颜色预设,`ImageExtension` 还在此层实现了上传侧信道的渲染逻辑)。**表格** 位于 `Table.ts`(Vue 渲染器 + 命令注册)与 `tableModel.ts`(纯函数式结构操作:插入/删除行/列、合并/拆分单元格、合并选区完整矩形扩展、切换标题行、列宽辅助、HTML/Markdown 序列化、attrs 校验/规整)。**分割线** 位于 `Divider.ts`。**目录** 位于 `TableOfContents.ts`(不可编辑的动态块,实时渲染文档标题列表)。**公式** 位于 `Equation.ts`(LaTeX 数学块;只保存 `attrs.expression`,渲染居中展示公式;`attrs.indent` 镜像嵌套深度,作为子块时随深度缩进)。内置数学引擎位于 `extensions/math/`(tokenizer → parser → AST → 渲染树 → DOM/HTML),零第三方依赖。
370
+ - **`src/i18n.ts`**:locale + 主题模块;通过 Vue 的 provide/inject 提供 `t(key)`,让 `<Teleport>` 渲染的浮层也保持响应式。
248
371
 
249
372
  ## 开发
250
373