@one2x/medeo-rte 0.1.1 → 0.3.0
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.
- package/README.md +92 -12
- package/dist/chunks/{types-I0yc5X7i.js → types-Hb-uu8xL.js} +2 -2
- package/dist/chunks/{types-I0yc5X7i.js.map → types-Hb-uu8xL.js.map} +1 -1
- package/dist/formula.js +1 -1
- package/dist/index.js +2305 -1644
- package/dist/index.js.map +1 -1
- package/dist/style.css +32 -3
- package/dist/types/commands/shortcuts.d.ts +14 -0
- package/dist/types/commands/shortcuts.d.ts.map +1 -1
- package/dist/types/commands/types.d.ts +6 -0
- package/dist/types/commands/types.d.ts.map +1 -1
- package/dist/types/editor.d.ts +67 -0
- package/dist/types/editor.d.ts.map +1 -1
- package/dist/types/input/caretMotion.d.ts +23 -0
- package/dist/types/input/caretMotion.d.ts.map +1 -0
- package/dist/types/input/caretMotion.selfcheck.d.ts +2 -0
- package/dist/types/input/caretMotion.selfcheck.d.ts.map +1 -0
- package/dist/types/input/ime.d.ts +9 -0
- package/dist/types/input/ime.d.ts.map +1 -1
- package/dist/types/input/keyboard.d.ts.map +1 -1
- package/dist/types/layout/engine.d.ts.map +1 -1
- package/dist/types/layout/harness.d.ts.map +1 -1
- package/dist/types/layout/measure.d.ts +11 -0
- package/dist/types/layout/measure.d.ts.map +1 -1
- package/dist/types/layout/types.d.ts +8 -0
- package/dist/types/layout/types.d.ts.map +1 -1
- package/dist/types/model/blocks.d.ts.map +1 -1
- package/dist/types/model/types.d.ts +14 -5
- package/dist/types/model/types.d.ts.map +1 -1
- package/dist/types/operations/slice.d.ts.map +1 -1
- package/dist/types/operations/text.d.ts.map +1 -1
- package/dist/types/operations/virtualBlock.d.ts +6 -0
- package/dist/types/operations/virtualBlock.d.ts.map +1 -1
- package/dist/types/paint/canvasPainter.d.ts +27 -1
- package/dist/types/paint/canvasPainter.d.ts.map +1 -1
- package/dist/types/paint/htmlPainter.d.ts +3 -0
- package/dist/types/paint/htmlPainter.d.ts.map +1 -1
- package/dist/types/paint/types.d.ts +2 -0
- package/dist/types/paint/types.d.ts.map +1 -1
- package/dist/types/perf.d.ts +55 -0
- package/dist/types/perf.d.ts.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -54,17 +54,47 @@ new Editor(el, { plugins: [...builtinEmbedPlugins, formulaPlugin] });
|
|
|
54
54
|
|
|
55
55
|
文字与段落样式、列表、表格、链接、图文绕排、Markdown 边打边转、撤销重做、中文 IME。
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
嵌入对象走同一套插件接口:图片、视频、代码块、公式、同步块引用、以及下面三类「槽 / 胶囊」。**名字都带 slot,用途完全不同,不要混用。**
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
- **host 胶囊** —— `*` 占位,内部 DOM 由宿主渲染(下拉、开关、上传、`@` 芯片)。整颗换行
|
|
61
|
-
- **虚拟块** —— 盖在字符流上的假块(region 皮 + 文本槽)。槽里的字跟外层句子一起折行,不是实体
|
|
59
|
+
### 三类 slot 对照(接入必读)
|
|
62
60
|
|
|
63
|
-
|
|
61
|
+
| | `internalSlot` | `hostSlot` | `virtualSlot`(+ `region`) |
|
|
62
|
+
| --- | --- | --- | --- |
|
|
63
|
+
| 是什么 | SDK 内置芯片(`slotPlugin`) | 宿主自绘芯片 | 虚拟块文本槽;外层皮是 `region` |
|
|
64
|
+
| 落在哪 | `EmbedEntity`,流上一个 `*` | 同上 | `virtualBlocks` 区间,**不占**额外字符 |
|
|
65
|
+
| 内容 | 实体字段(label / slotValue 等) | `hostType` + `data`,DOM 由宿主画 | 就是那段 `spacers` 文字 |
|
|
66
|
+
| 折行 | 整颗盒子,放不下换行 | 同左 | 槽里的字跟外层句子一起按字折 |
|
|
67
|
+
| 谁用 | demo / 简单内置控件 | **产品业务胶囊**(下拉、`@`、上传等) | **模板里可填灰字**、外层底色分组 |
|
|
68
|
+
| 插入 | `commands.insertSlot('text'\|'image'\|'select')` | `commands.insertHostEmbed(...)` 或 `insertSlice` 的 `entities` | `insertSlice` 的 `virtualBlocks`,或 `insertVirtualBlock` |
|
|
64
69
|
|
|
65
|
-
|
|
70
|
+
另外还有 **同步块**(fragment / ref):选区提成可复用内容,改一处全变——和上面三类无关。
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
产品模板句里「可填写的那截灰字」用 **`virtualSlot`**,不要用 `internalSlot` / `hostSlot`。后两者是另一颗盒子,点进去才填,不能按字切开。
|
|
73
|
+
|
|
74
|
+
## 虚拟块怎么用(业务侧)
|
|
75
|
+
|
|
76
|
+
虚拟块是盖在字符流上的假块:负责皮(背景、圆角)和「整段选中」交互。区间里的字仍是文档正文。用户打字**不会**新建虚拟块,只能由业务 API 插入。
|
|
77
|
+
|
|
78
|
+
三种 kind:
|
|
79
|
+
|
|
80
|
+
- **`mark`**:纯样式叠层(高亮底色、边框、文字缺省色等)。**交互与普通文本流完全一致**:点击落光标、方向键逐字走、能选中、能编辑;不进选中/进出状态机,不参与嵌套(`parentId` 恒为 `null`),可与其他块任意叠放。右边缘继续输入算区间内(背景跟着延伸),遇到换行就断;区间内文字删光后自动移除。样式只需配 `chrome.background`、`chrome.radius`(圆角,默认 3;折行时只有首行的上两角和末行的下两角是圆角,其余角为直角,单行自然四角全圆)、`chrome.paddingX`(水平内边距)和 `chrome.hoverBackground`(鼠标悬停时整段含折行各行换成这个底色):
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
editor.insertVirtualBlock({
|
|
84
|
+
kind: 'mark',
|
|
85
|
+
chrome: { background: '#fff3bf', hoverBackground: '#ffe066', radius: 4, paddingX: 5 }
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`paddingX` 只外扩底色/边框,**不参与排版**(字形位置、折行、行高都不变),行首/行尾也会外扩,所以相邻文字和相邻 mark 的底色可能互相重叠;行首行尾的外扩部分画在内容区外:SDK 默认让画布和 `.mini-page-wrap` 左右各多出 5px(负 margin 抵消,文字位置不变),所以 `paddingX` 在 5px 以内开箱即用,更大就要靠编辑器 `padding.x` 补足。**宿主若覆盖 `.mini-page-wrap` 的 `padding`,请保留 `padding-inline: 5px; margin-inline: -5px;`,否则文字会左移 5px、外扩被裁**。不支持垂直内边距。
|
|
90
|
+
- **`region`**:一层皮 / 分组。整段选中时打字、删除会被忽略(保住模板壳)。没有 `editing` 模式。
|
|
91
|
+
- **`virtualSlot`**:最内层文本槽。可进 `editing`;空着时显示 `placeholder`,填了就是用户字;整段删回 placeholder,槽本身留下。
|
|
92
|
+
|
|
93
|
+
嵌套规则(针对 `region` / `virtualSlot`):只允许包含、不允许交叉;`virtualSlot` 必须是叶子。`mark` 不受此限。日常编辑里业务**不用维护** `[start, end)`,插删字时 SDK 自动挪区间。
|
|
94
|
+
|
|
95
|
+
### 路径 A:整段模板一次插入(推荐)
|
|
96
|
+
|
|
97
|
+
文案 + 槽 + 胶囊一起走 `insertSlice`。`virtualBlocks` 里的下标是**相对这段 `text`** 的半开区间:
|
|
68
98
|
|
|
69
99
|
```ts
|
|
70
100
|
editor.insertSlice({
|
|
@@ -83,7 +113,7 @@ editor.insertSlice({
|
|
|
83
113
|
{
|
|
84
114
|
id: 'slot',
|
|
85
115
|
parentId: 'region',
|
|
86
|
-
kind: '
|
|
116
|
+
kind: 'virtualSlot',
|
|
87
117
|
start: 19,
|
|
88
118
|
end: 32,
|
|
89
119
|
placeholder: 'Input a theme',
|
|
@@ -94,15 +124,50 @@ editor.insertSlice({
|
|
|
94
124
|
});
|
|
95
125
|
```
|
|
96
126
|
|
|
97
|
-
|
|
127
|
+
完整样例见 `demo/vblocks.ts`。
|
|
128
|
+
|
|
129
|
+
### 路径 B:给已有字套皮
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
// 不传 start/end → 用当前选区
|
|
133
|
+
editor.insertVirtualBlock({
|
|
134
|
+
kind: 'region',
|
|
135
|
+
chrome: { background: 'rgba(43,108,255,0.08)', radius: 8 }
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
editor.insertVirtualBlock({
|
|
139
|
+
kind: 'virtualSlot',
|
|
140
|
+
placeholder: 'Input a theme',
|
|
141
|
+
empty: true,
|
|
142
|
+
chrome: { background: 'rgba(16,185,129,0.18)', placeholderColor: '#98a2b3' }
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// 也可显式下标
|
|
146
|
+
editor.insertVirtualBlock({ kind: 'region', start: 0, end: 20, chrome: { ... } });
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
可配的 `chrome` 字段:`background`、`hoverBackground`(仅 mark)、`radius`、`borderColor` / `borderWidth`、`paddingX`(仅水平)、文字缺省(`color`、`fontSize`、`fontFamily`、`fontWeight`、`bold`、`italic`)、`placeholderColor`(仅 virtualSlot)。
|
|
150
|
+
|
|
151
|
+
改皮 / 拆皮:`updateVirtualBlock(id, chrome)`、`removeVirtualBlock(id)`(只拆皮,字和胶囊留下)。
|
|
152
|
+
|
|
153
|
+
### 业务浮层(Sample / AI 等)
|
|
154
|
+
|
|
155
|
+
SDK 不画业务菜单。宿主自己:
|
|
98
156
|
|
|
99
|
-
|
|
157
|
+
1. 听 `statechange`,看 `getState().vblockStack` 栈顶是否是某槽的 `editing`(点击或键盘进入都会触发)
|
|
158
|
+
2. `editor.getVirtualBlockRect(id)` 拿屏幕坐标(clientX/Y,含滚动)定位浮层:`left/top/width/height` 是外接矩形,折行的块还有 `lines`;下拉放在 `left` / `top + height` 即可。滚动或重排后(`layoutchange`)重新取
|
|
159
|
+
3. 写入:槽正处于 `editing` 时直接 `editor.insertText(text)`——空槽会整段覆盖 placeholder,不会进段落
|
|
160
|
+
4. 关闭浮层:栈顶不再是该槽,或点到编辑器外(宿主自己监听)
|
|
161
|
+
|
|
162
|
+
### 和 host 胶囊一起用
|
|
163
|
+
|
|
164
|
+
胶囊是流上的 `*`,可以落在 region 覆盖范围内,但**不是**虚拟块的子节点。自定义芯片:
|
|
100
165
|
|
|
101
166
|
```ts
|
|
102
167
|
const editor = new Editor(el, {
|
|
103
168
|
hostEmbed: {
|
|
104
169
|
mount(container, ctx) {
|
|
105
|
-
// 按 ctx.entity.hostType
|
|
170
|
+
// 按 ctx.entity.hostType 画自己的组件;实体 kind 为 'hostSlot'
|
|
106
171
|
return { update(next) {}, destroy() {} };
|
|
107
172
|
}
|
|
108
173
|
}
|
|
@@ -110,7 +175,7 @@ const editor = new Editor(el, {
|
|
|
110
175
|
editor.commands.insertHostEmbed('Upscale', { label: '@Upscale' }, { width: 88, height: 24 });
|
|
111
176
|
```
|
|
112
177
|
|
|
113
|
-
|
|
178
|
+
更细的模型与选区栈见 [virtual-blocks.md](./docs/virtual-blocks.md)。
|
|
114
179
|
|
|
115
180
|
### API 一览
|
|
116
181
|
|
|
@@ -137,6 +202,19 @@ editor.commands.insertHostEmbed('Upscale', { label: '@Upscale' }, { width: 88, h
|
|
|
137
202
|
|
|
138
203
|
脚本化改文档也可 `import { operations, runOperation } from '@one2x/medeo-rte'`,不要直接改 `DocumentModel` 的内部表。
|
|
139
204
|
|
|
205
|
+
## 性能探针(开发用)
|
|
206
|
+
|
|
207
|
+
默认关闭、零开销。在浏览器控制台里:
|
|
208
|
+
|
|
209
|
+
```js
|
|
210
|
+
editor.perf.start() // demo 里 window.editor 已暴露;业务里用自己的 editor 实例
|
|
211
|
+
// 连续打字 / 按住方向键 / 中文输入法连续上屏 / 按快捷键……
|
|
212
|
+
editor.perf.report() // 输出各阶段耗时(平均 / p95 / 最大 ms)和帧率
|
|
213
|
+
editor.perf.stop()
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
详细读法与历次优化记录见 [docs/performance.md](./docs/performance.md)。输出包含:`insertText`、`handleComposition`、`moveCaret` 等入口的同步耗时;`relayout`、`paintDoc`、`paintChrome`、`scrollCaret` 等阶段耗时;`xxx→下一帧`(处理开始到下一帧开始,近似输入延迟);以及帧率、帧间隔 p95、掉帧数。120Hz 屏的帧预算是 8.3ms。
|
|
217
|
+
|
|
140
218
|
## 设计文档
|
|
141
219
|
|
|
142
220
|
`docs/` 下是分层架构与技术方案:
|
|
@@ -149,6 +227,8 @@ editor.commands.insertHostEmbed('Upscale', { label: '@Upscale' }, { width: 88, h
|
|
|
149
227
|
| [edits.md](./docs/edits.md) | 事务边界、撤销栈、片段坐标空间、协同预留 |
|
|
150
228
|
| [rendering.md](./docs/rendering.md) | 块级增量排版、脏区带、绘制后端 |
|
|
151
229
|
| [virtual-blocks.md](./docs/virtual-blocks.md) | 虚拟块:模板混排、文本槽、按层选区 |
|
|
230
|
+
| [agent-segment-mapping.md](./docs/agent-segment-mapping.md) | 面向 Agent:用户的 segment 概念如何对应到真实结构与 API |
|
|
231
|
+
| [performance.md](./docs/performance.md) | 性能目标、探针用法、长文档优化记录与待办 |
|
|
152
232
|
|
|
153
233
|
改动模型前先读 architecture.md 的不变式那一节。
|
|
154
234
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const s = "mini-embed-change", n = "mini-embed-commit", a = "mini-embed-exit-edit", E = "mini-embed-measure";
|
|
2
2
|
function d(t, e) {
|
|
3
|
-
t.style.left = `${e.x}px`, t.style.top = `${e.y}px`, e.entity.kind === "
|
|
3
|
+
t.style.left = `${e.x}px`, t.style.top = `${e.y}px`, e.entity.kind === "hostSlot" ? (t.style.width = "auto", t.style.height = "auto") : (t.style.width = `${e.w}px`, t.classList.contains("is-editing") || (t.style.height = `${e.h}px`)), t.dataset.index = String(e.index), t.dataset.entity = e.entity.id;
|
|
4
4
|
}
|
|
5
5
|
function m(t, e, i = !1) {
|
|
6
6
|
t.dispatchEvent(
|
|
@@ -15,4 +15,4 @@ export {
|
|
|
15
15
|
m as e,
|
|
16
16
|
d as p
|
|
17
17
|
};
|
|
18
|
-
//# sourceMappingURL=types-
|
|
18
|
+
//# sourceMappingURL=types-Hb-uu8xL.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types-
|
|
1
|
+
{"version":3,"file":"types-Hb-uu8xL.js","sources":["../../src/embed/types.ts"],"sourcesContent":["import { DocumentModel } from '../model/document';\nimport { EmbedEntity, EmbedKind } from '../model/types';\nimport type { DocLayout } from '../layout/types';\n\nexport interface EmbedBox {\n index: number;\n x: number;\n y: number;\n w: number;\n h: number;\n entity: EmbedEntity;\n inner?: DocLayout;\n}\n\nexport const EMBED_CHANGE = 'mini-embed-change';\nexport const EMBED_COMMIT = 'mini-embed-commit';\nexport const EMBED_EXIT_EDIT = 'mini-embed-exit-edit';\n/**\n * 宿主渲染块回报自身布局尺寸。这是渲染副产物、不是用户意图,编辑器只据此重排,\n * 既不进撤销栈、也不开启编辑事务(区别于表示用户改动的 CHANGE / COMMIT)。\n */\nexport const EMBED_MEASURE = 'mini-embed-measure';\n\nexport interface EmbedMeasureDetail {\n id: string;\n width: number;\n height: number;\n}\n\nexport interface EmbedExitDetail {\n id: string;\n side: 'before' | 'after';\n}\n\nexport interface EmbedChangeDetail {\n id: string;\n patch: Partial<EmbedEntity>;\n before?: Partial<EmbedEntity>;\n}\n\nexport interface EmbedPlugin {\n kind: EmbedKind;\n keepAspectRatio: boolean;\n interactive: boolean;\n /** Canvas 上是否叠 DOM(视频/代码/公式);图片只画到 canvas */\n overlayOnCanvas: boolean;\n measure(entity: EmbedEntity, contentWidth: number, host?: DocumentModel): { width: number; height: number };\n create(box: EmbedBox): HTMLElement;\n update(el: HTMLElement, box: EmbedBox): void;\n /** 节点从文档移除前调用,供插件清理副作用(观察器、外部组件等)。可选。 */\n destroy?(el: HTMLElement): void;\n paintCanvasBackdrop(ctx: CanvasRenderingContext2D, box: EmbedBox, imageOf: (src: string) => HTMLImageElement): void;\n}\n\nexport function placeEmbed(el: HTMLElement, box: EmbedBox) {\n el.style.left = `${box.x}px`;\n el.style.top = `${box.y}px`;\n // host 外层若按尚未测到的 0×0 锁宽高,内层会被挤成竖排文字,ResizeObserver 再把竖排尺寸写回,恶性循环。\n if (box.entity.kind === 'hostSlot') {\n el.style.width = 'auto';\n el.style.height = 'auto';\n } else {\n el.style.width = `${box.w}px`;\n if (!el.classList.contains('is-editing')) el.style.height = `${box.h}px`;\n }\n el.dataset.index = String(box.index);\n el.dataset.entity = box.entity.id;\n}\n\nexport function emitEmbedChange(el: HTMLElement, detail: EmbedChangeDetail, commit = false) {\n el.dispatchEvent(\n new CustomEvent(commit ? EMBED_COMMIT : EMBED_CHANGE, { bubbles: true, detail })\n );\n}\n"],"names":["EMBED_CHANGE","EMBED_COMMIT","EMBED_EXIT_EDIT","EMBED_MEASURE","placeEmbed","el","box","emitEmbedChange","detail","commit"],"mappings":"AAcO,MAAMA,IAAe,qBACfC,IAAe,qBACfC,IAAkB,wBAKlBC,IAAgB;AAiCtB,SAASC,EAAWC,GAAiBC,GAAe;AACzD,EAAAD,EAAG,MAAM,OAAO,GAAGC,EAAI,CAAC,MACxBD,EAAG,MAAM,MAAM,GAAGC,EAAI,CAAC,MAEnBA,EAAI,OAAO,SAAS,cACtBD,EAAG,MAAM,QAAQ,QACjBA,EAAG,MAAM,SAAS,WAElBA,EAAG,MAAM,QAAQ,GAAGC,EAAI,CAAC,MACpBD,EAAG,UAAU,SAAS,YAAY,MAAGA,EAAG,MAAM,SAAS,GAAGC,EAAI,CAAC,QAEtED,EAAG,QAAQ,QAAQ,OAAOC,EAAI,KAAK,GACnCD,EAAG,QAAQ,SAASC,EAAI,OAAO;AACjC;AAEO,SAASC,EAAgBF,GAAiBG,GAA2BC,IAAS,IAAO;AAC1F,EAAAJ,EAAG;AAAA,IACD,IAAI,YAAYI,IAASR,IAAeD,GAAc,EAAE,SAAS,IAAM,QAAAQ,EAAA,CAAQ;AAAA,EAAA;AAEnF;"}
|
package/dist/formula.js
CHANGED
|
@@ -2,7 +2,7 @@ import M from "katex";
|
|
|
2
2
|
import "katex/dist/katex.min.css";
|
|
3
3
|
import { MathfieldElement as E } from "mathlive";
|
|
4
4
|
import "mathlive/fonts.css";
|
|
5
|
-
import { e as w, E as L } from "./chunks/types-
|
|
5
|
+
import { e as w, E as L } from "./chunks/types-Hb-uu8xL.js";
|
|
6
6
|
const C = {
|
|
7
7
|
"\\wideparen": "\\overgroup{#1}",
|
|
8
8
|
"\\overarc": "\\overgroup{#1}",
|