@one2x/medeo-rte 0.1.1 → 0.2.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 CHANGED
@@ -54,17 +54,37 @@ new Editor(el, { plugins: [...builtinEmbedPlugins, formulaPlugin] });
54
54
 
55
55
  文字与段落样式、列表、表格、链接、图文绕排、Markdown 边打边转、撤销重做、中文 IME。
56
56
 
57
- 嵌入对象走同一套插件接口:图片、视频、代码块、公式、行内胶囊插槽、同步块引用、**host**(宿主自绘)。其中:
57
+ 嵌入对象走同一套插件接口:图片、视频、代码块、公式、同步块引用、以及下面三类「槽 / 胶囊」。**名字都带 slot,用途完全不同,不要混用。**
58
58
 
59
- - **同步块** —— 任意选区提成一份内容,多处平等复用,改一处全变
60
- - **host 胶囊** —— `*` 占位,内部 DOM 由宿主渲染(下拉、开关、上传、`@` 芯片)。整颗换行
61
- - **虚拟块** —— 盖在字符流上的假块(region 皮 + 文本槽)。槽里的字跟外层句子一起折行,不是实体
59
+ ### 三类 slot 对照(接入必读)
62
60
 
63
- 模板句里「可填写的那截灰字」用虚拟块文本槽,不要用胶囊 `slotPlugin`。胶囊插槽是另一颗盒子,点进去才填,不能按字切开。
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
- 插入一份模板(文案 + 文本槽 + 胶囊)走 `insertSlice`,不要再开子文档。
72
+ 产品模板句里「可填写的那截灰字」用 **`virtualSlot`**,不要用 `internalSlot` / `hostSlot`。后两者是另一颗盒子,点进去才填,不能按字切开。
73
+
74
+ ## 虚拟块怎么用(业务侧)
75
+
76
+ 虚拟块是盖在字符流上的假块:负责皮(背景、圆角)和「整段选中」交互。区间里的字仍是文档正文。用户打字**不会**新建虚拟块,只能由业务 API 插入。
77
+
78
+ 两种 kind:
79
+
80
+ - **`region`**:一层皮 / 分组。整段选中时打字、删除会被忽略(保住模板壳)。没有 `editing` 模式。
81
+ - **`virtualSlot`**:最内层文本槽。可进 `editing`;空着时显示 `placeholder`,填了就是用户字;整段删回 placeholder,槽本身留下。
82
+
83
+ 嵌套规则:只允许包含、不允许交叉;`virtualSlot` 必须是叶子。日常编辑里业务**不用维护** `[start, end)`,插删字时 SDK 自动挪区间。
84
+
85
+ ### 路径 A:整段模板一次插入(推荐)
86
+
87
+ 文案 + 槽 + 胶囊一起走 `insertSlice`。`virtualBlocks` 里的下标是**相对这段 `text`** 的半开区间:
68
88
 
69
89
  ```ts
70
90
  editor.insertSlice({
@@ -83,7 +103,7 @@ editor.insertSlice({
83
103
  {
84
104
  id: 'slot',
85
105
  parentId: 'region',
86
- kind: 'slot',
106
+ kind: 'virtualSlot',
87
107
  start: 19,
88
108
  end: 32,
89
109
  placeholder: 'Input a theme',
@@ -94,15 +114,47 @@ editor.insertSlice({
94
114
  });
95
115
  ```
96
116
 
97
- 槽上的 Sample / AI 菜单是宿主浮层:看 `getState().vblockStack` 是否进了 editing,用 `getLayout()` 里该槽的 `vblocks` 矩形定位,写入走打字或再 `insertSlice` 一段纯文本。SDK 不认识业务菜单。
117
+ 完整样例见 `demo/vblocks.ts`。
118
+
119
+ ### 路径 B:给已有字套皮
120
+
121
+ ```ts
122
+ // 不传 start/end → 用当前选区
123
+ editor.insertVirtualBlock({
124
+ kind: 'region',
125
+ chrome: { background: 'rgba(43,108,255,0.08)', radius: 8 }
126
+ });
127
+
128
+ editor.insertVirtualBlock({
129
+ kind: 'virtualSlot',
130
+ placeholder: 'Input a theme',
131
+ empty: true,
132
+ chrome: { background: 'rgba(16,185,129,0.18)', placeholderColor: '#98a2b3' }
133
+ });
134
+
135
+ // 也可显式下标
136
+ editor.insertVirtualBlock({ kind: 'region', start: 0, end: 20, chrome: { ... } });
137
+ ```
138
+
139
+ 改皮 / 拆皮:`updateVirtualBlock(id, chrome)`、`removeVirtualBlock(id)`(只拆皮,字和胶囊留下)。
140
+
141
+ ### 业务浮层(Sample / AI 等)
142
+
143
+ SDK 不画业务菜单。宿主自己:
144
+
145
+ 1. 听 `statechange`,看 `getState().vblockStack` 是否进了某槽的 `editing`
146
+ 2. 用 `getLayout()` 里该槽的 `vblocks` 矩形定位浮层
147
+ 3. 写入:让用户打字,或再 `insertSlice` 一段纯文本
148
+
149
+ ### 和 host 胶囊一起用
98
150
 
99
- 自定义芯片:
151
+ 胶囊是流上的 `*`,可以落在 region 覆盖范围内,但**不是**虚拟块的子节点。自定义芯片:
100
152
 
101
153
  ```ts
102
154
  const editor = new Editor(el, {
103
155
  hostEmbed: {
104
156
  mount(container, ctx) {
105
- // 按 ctx.entity.hostType 画自己的组件
157
+ // 按 ctx.entity.hostType 画自己的组件;实体 kind 为 'hostSlot'
106
158
  return { update(next) {}, destroy() {} };
107
159
  }
108
160
  }
@@ -110,7 +162,7 @@ const editor = new Editor(el, {
110
162
  editor.commands.insertHostEmbed('Upscale', { label: '@Upscale' }, { width: 88, height: 24 });
111
163
  ```
112
164
 
113
- 口径和交互见 [virtual-blocks.md](./docs/virtual-blocks.md)。
165
+ 更细的模型与选区栈见 [virtual-blocks.md](./docs/virtual-blocks.md)。
114
166
 
115
167
  ### API 一览
116
168
 
@@ -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 === "host" ? (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;
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-I0yc5X7i.js.map
18
+ //# sourceMappingURL=types-Hb-uu8xL.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types-I0yc5X7i.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 === 'host') {\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,UACtBD,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;"}
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-I0yc5X7i.js";
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}",