bridgerte 0.9.0 → 0.9.2
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 +246 -134
- package/dist/bridge.d.ts +15 -7
- package/dist/core.d.ts +14 -6
- package/dist/dom.cjs +1 -1
- package/dist/dom.d.ts +64 -6
- package/dist/dom.js +4 -3
- package/dist/dom.js.map +1 -1
- package/dist/index-CkgUKPh3.cjs +3 -0
- package/dist/index-CkgUKPh3.cjs.map +1 -0
- package/dist/index-CqOH1_5N.cjs +2 -0
- package/dist/index-CqOH1_5N.cjs.map +1 -0
- package/dist/index-DRWIM218.js +262 -0
- package/dist/index-DRWIM218.js.map +1 -0
- package/dist/{index-C7IVE5Bd.js → index-KRuLtGv9.js} +1696 -1818
- package/dist/index-KRuLtGv9.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +113 -7
- package/dist/index.js +15 -14
- package/dist/index.js.map +1 -1
- package/dist/native-spec.d.ts +32 -6
- package/dist/style.css +1 -1
- package/dist/webview.cjs +1 -1
- package/dist/webview.d.ts +45 -7
- package/dist/webview.js +1 -1
- package/package.json +8 -8
- package/dist/index-BDgKCpty.cjs +0 -3
- package/dist/index-BDgKCpty.cjs.map +0 -1
- package/dist/index-C7IVE5Bd.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
BridgeRTE 是面向 Web、H5、
|
|
6
|
-
|
|
5
|
+
BridgeRTE 是面向 Web、PC、H5、React Native WebView 和 Flutter WebView 的跨端富文本编辑器。
|
|
6
|
+
业务项目只需要安装 `bridgerte`,不需要额外安装 Lexical 相关包。
|
|
7
|
+
|
|
8
|
+
它提供三类接入方式:
|
|
9
|
+
|
|
10
|
+
- DOM 编辑器:直接在 Web、PC、H5 页面里创建富文本编辑器。
|
|
11
|
+
- 编辑器和菜单分离:editor 负责内容,toolbar/tabbar 由业务按布局单独挂载。
|
|
12
|
+
- WebView / Native:WebView 内运行编辑器,RN/Flutter 原生侧自己渲染菜单并通过 bridge 发命令。
|
|
7
13
|
|
|
8
14
|
## 安装
|
|
9
15
|
|
|
@@ -11,123 +17,140 @@ BridgeRTE 是面向 Web、H5、PC、React Native WebView 和 Flutter WebView 的
|
|
|
11
17
|
pnpm add bridgerte
|
|
12
18
|
```
|
|
13
19
|
|
|
14
|
-
|
|
20
|
+
使用 npm:
|
|
15
21
|
|
|
16
22
|
```bash
|
|
17
23
|
npm install bridgerte
|
|
18
24
|
```
|
|
19
25
|
|
|
20
|
-
DOM
|
|
26
|
+
DOM 编辑器必须显式导入样式:
|
|
21
27
|
|
|
22
28
|
```ts
|
|
23
29
|
import 'bridgerte/style.css';
|
|
24
30
|
```
|
|
25
31
|
|
|
26
|
-
##
|
|
32
|
+
## 推荐导入方式
|
|
33
|
+
|
|
34
|
+
推荐按能力从 subpath 导入,方便业务打包器按使用边界 tree-shaking:
|
|
27
35
|
|
|
28
36
|
```ts
|
|
29
|
-
import { createRichTextEditor } from 'bridgerte';
|
|
37
|
+
import { createRichTextEditor, createRichTextToolbar } from 'bridgerte/dom';
|
|
38
|
+
import { createWebViewBridgeRuntime } from 'bridgerte/webview';
|
|
39
|
+
import { BRIDGERTE_CONTENT_VERSION } from 'bridgerte/core';
|
|
40
|
+
import { isBridgeMessage } from 'bridgerte/bridge';
|
|
41
|
+
import { defaultMenuSchema, resolveToolbarMenu } from 'bridgerte/native-spec';
|
|
30
42
|
import 'bridgerte/style.css';
|
|
43
|
+
```
|
|
31
44
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (!container) throw new Error('editor container not found');
|
|
45
|
+
入口说明:
|
|
35
46
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
- `bridgerte/dom`:DOM 编辑器、独立 toolbar/tabbar、WebView runtime 的 DOM 实现。
|
|
48
|
+
- `bridgerte/webview`:WebView 页面内的 bridge runtime。
|
|
49
|
+
- `bridgerte/core`:内容模型、命令、上传、菜单、参数面板和 `EditorAPI` 类型。
|
|
50
|
+
- `bridgerte/bridge`:WebView 双向消息类型、默认事件节流配置和消息判断工具。
|
|
51
|
+
- `bridgerte/native-spec`:RN/Flutter 原生菜单 schema、toolbar 解析和命令状态匹配工具。
|
|
52
|
+
- `bridgerte/style.css`:DOM 默认样式。
|
|
53
|
+
- `bridgerte`:聚合入口,适合迁移期或小型项目;生产示例优先使用 subpath。
|
|
42
54
|
|
|
43
|
-
|
|
44
|
-
editor.destroy();
|
|
45
|
-
});
|
|
46
|
-
```
|
|
55
|
+
## 快速开始
|
|
47
56
|
|
|
48
|
-
|
|
57
|
+
页面准备两个容器:一个给 toolbar/tabbar,一个给编辑器内容区。`createRichTextEditor()`
|
|
58
|
+
只创建 editor;菜单必须用 `createRichTextToolbar()` 单独挂载,并绑定同一个 `EditorAPI`。
|
|
49
59
|
|
|
50
60
|
```html
|
|
61
|
+
<div id="toolbar"></div>
|
|
51
62
|
<div id="editor"></div>
|
|
52
63
|
```
|
|
53
64
|
|
|
54
|
-
|
|
65
|
+
```ts
|
|
66
|
+
import { createRichTextEditor, createRichTextToolbar } from 'bridgerte/dom';
|
|
67
|
+
import 'bridgerte/style.css';
|
|
55
68
|
|
|
56
|
-
|
|
69
|
+
const editorContainer = document.querySelector<HTMLElement>('#editor');
|
|
70
|
+
const toolbarContainer = document.querySelector<HTMLElement>('#toolbar');
|
|
57
71
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
createRichTextEditor,
|
|
61
|
-
createRichTextToolbar,
|
|
62
|
-
createWebViewBridgeRuntime
|
|
63
|
-
} from 'bridgerte';
|
|
72
|
+
if (!editorContainer) throw new Error('editor container not found');
|
|
73
|
+
if (!toolbarContainer) throw new Error('toolbar container not found');
|
|
64
74
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
```
|
|
75
|
+
const editor = createRichTextEditor(editorContainer, {
|
|
76
|
+
placeholder: '开始输入',
|
|
77
|
+
onContentChange(change) {
|
|
78
|
+
counter.textContent = `${change.plainTextLength}/${change.maxLength ?? '∞'}`;
|
|
79
|
+
saveButton.disabled = !change.dirty;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
73
82
|
|
|
74
|
-
|
|
83
|
+
const toolbar = createRichTextToolbar(toolbarContainer, {
|
|
84
|
+
editor,
|
|
85
|
+
placement: 'top'
|
|
86
|
+
});
|
|
75
87
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
toolbar.update();
|
|
89
|
+
|
|
90
|
+
window.addEventListener('beforeunload', () => {
|
|
91
|
+
toolbar.destroy();
|
|
92
|
+
editor.destroy();
|
|
93
|
+
});
|
|
82
94
|
```
|
|
83
95
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
- `bridgerte/style.css`:DOM 默认样式。
|
|
96
|
+
`placement: 'top' | 'bottom'` 只影响菜单语义、默认样式状态和无障碍命名;DOM 放在哪里、
|
|
97
|
+
是否吸顶、吸底或跟随键盘,由业务自己的布局决定。销毁时建议先销毁 toolbar,再销毁 editor。
|
|
98
|
+
|
|
99
|
+
WebView / RN / Flutter 也是同一心智:WebView 内的 editor 只提供内容和命令能力;原生菜单先读取
|
|
100
|
+
ready payload 中的 `menuSchema`,再订阅后续 `editor.commandStateChange` 自行渲染,并通过
|
|
101
|
+
bridge 发送命令。
|
|
91
102
|
|
|
92
103
|
## 基础配置
|
|
93
104
|
|
|
94
105
|
```ts
|
|
106
|
+
import { createRichTextEditor } from 'bridgerte/dom';
|
|
107
|
+
import type { EditorContent } from 'bridgerte/core';
|
|
108
|
+
|
|
109
|
+
const initialValue: Partial<EditorContent> = {
|
|
110
|
+
html: '<p>Hello BridgeRTE</p>',
|
|
111
|
+
plainText: 'Hello BridgeRTE'
|
|
112
|
+
};
|
|
113
|
+
|
|
95
114
|
const editor = createRichTextEditor(container, {
|
|
96
|
-
value:
|
|
97
|
-
html: '<p>Hello BridgeRTE</p>',
|
|
98
|
-
plainText: 'Hello BridgeRTE'
|
|
99
|
-
},
|
|
115
|
+
value: initialValue,
|
|
100
116
|
readonly: false,
|
|
101
|
-
platform: 'pc',
|
|
102
|
-
toolbarMode: 'top',
|
|
103
117
|
placeholder: '写点什么',
|
|
104
118
|
maxLength: 10000,
|
|
105
119
|
keyboardShortcuts: true,
|
|
106
120
|
onReady(api) {
|
|
107
121
|
api.focus();
|
|
122
|
+
},
|
|
123
|
+
onError(error) {
|
|
124
|
+
reportError(error);
|
|
125
|
+
},
|
|
126
|
+
onFocus() {
|
|
127
|
+
console.log('focus');
|
|
128
|
+
},
|
|
129
|
+
onBlur() {
|
|
130
|
+
console.log('blur');
|
|
108
131
|
}
|
|
109
132
|
});
|
|
110
133
|
```
|
|
111
134
|
|
|
112
|
-
|
|
135
|
+
常用选项:
|
|
113
136
|
|
|
114
137
|
- `value`:初始内容,传 `Partial<EditorContent>`。
|
|
115
138
|
- `readonly`:只读状态。
|
|
116
|
-
- `platform`:`'pc' | 'h5' | 'webview'`。
|
|
117
|
-
- `toolbarMode`:`'top' | 'bottom' | 'none' | 'native'`。
|
|
118
139
|
- `placeholder`:空态提示。
|
|
119
140
|
- `maxLength`:最大纯文本长度。
|
|
120
|
-
- `keyboardShortcuts
|
|
121
|
-
- `onReady(api)
|
|
141
|
+
- `keyboardShortcuts`:是否启用 DOM 基础快捷键,默认关闭。
|
|
142
|
+
- `onReady(api)`:编辑器初始化完成后回传 `EditorAPI`。
|
|
143
|
+
- `onContentChange(change)`:高频轻量内容摘要。
|
|
144
|
+
- `onChange(content)`:兼容旧项目的完整内容回调,大文档不建议逐字依赖。
|
|
122
145
|
- `onError(error)`:运行时错误。
|
|
123
146
|
- `onFocus()` / `onBlur()`:焦点变化。
|
|
124
147
|
|
|
125
|
-
|
|
148
|
+
Deprecated 兼容字段:
|
|
126
149
|
|
|
127
|
-
- `
|
|
128
|
-
|
|
129
|
-
- `
|
|
130
|
-
|
|
150
|
+
- `toolbarMode`:历史字段,当前在 `createRichTextEditor()` 中是 no-op,不再创建或控制 DOM
|
|
151
|
+
toolbar/tabbar。
|
|
152
|
+
- `toolbarConfig`:历史 editor 字段,当前不再影响 editor 创建。菜单显示结构请传给
|
|
153
|
+
`createRichTextToolbar()`。
|
|
131
154
|
|
|
132
155
|
## 内容读写
|
|
133
156
|
|
|
@@ -152,20 +175,25 @@ editor.setContent({
|
|
|
152
175
|
});
|
|
153
176
|
```
|
|
154
177
|
|
|
155
|
-
|
|
178
|
+
高频变化使用轻量摘要:
|
|
156
179
|
|
|
157
180
|
```ts
|
|
158
181
|
createRichTextEditor(container, {
|
|
182
|
+
maxLength: 5000,
|
|
159
183
|
onContentChange(change) {
|
|
160
184
|
saveButton.disabled = !change.dirty;
|
|
161
185
|
counter.textContent = `${change.plainTextLength}/${change.maxLength ?? '∞'}`;
|
|
186
|
+
warning.hidden = !change.isOverMaxLength;
|
|
162
187
|
}
|
|
163
188
|
});
|
|
164
189
|
```
|
|
165
190
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
191
|
+
重要边界:
|
|
192
|
+
|
|
193
|
+
- `onContentChange` 不携带完整 `html`、`json`、`plainText`,适合驱动 dirty、字数和保存按钮。
|
|
194
|
+
- 保存、提交、离开页面确认时,再主动调用 `getContent()`。
|
|
195
|
+
- `onChange(content)` 会回传完整内容,主要用于兼容旧项目;10w 内容场景不要逐字依赖它保存。
|
|
196
|
+
- WebView 高频 `editor.contentChange` 也只应依赖轻量摘要;完整内容通过 `requestContent` 获取。
|
|
169
197
|
|
|
170
198
|
## EditorAPI
|
|
171
199
|
|
|
@@ -178,7 +206,7 @@ editor.executeCommand({ type: 'format.bold' });
|
|
|
178
206
|
|
|
179
207
|
const states = editor.getCommandStates();
|
|
180
208
|
const unsubscribe = editor.subscribeCommandStateChange((nextStates) => {
|
|
181
|
-
|
|
209
|
+
renderToolbarState(nextStates);
|
|
182
210
|
});
|
|
183
211
|
|
|
184
212
|
unsubscribe();
|
|
@@ -190,8 +218,8 @@ editor.destroy();
|
|
|
190
218
|
- `getContent()`:读取完整内容。
|
|
191
219
|
- `setContent(content)`:写入内容。
|
|
192
220
|
- `executeCommand(command)`:执行命令。
|
|
193
|
-
- `requestPayloadPanel(request)
|
|
194
|
-
- `getCommandStates()
|
|
221
|
+
- `requestPayloadPanel(request)`:打开参数面板请求,供自绘菜单复用。
|
|
222
|
+
- `getCommandStates()`:读取当前命令状态。
|
|
195
223
|
- `subscribeCommandStateChange(listener)`:订阅命令状态变化。
|
|
196
224
|
- `setReadonly(readonly)`:切换只读。
|
|
197
225
|
- `focus()` / `blur()`:焦点控制。
|
|
@@ -199,6 +227,8 @@ editor.destroy();
|
|
|
199
227
|
|
|
200
228
|
## 命令 API
|
|
201
229
|
|
|
230
|
+
所有菜单最终都会落到 `EditorCommand`。业务也可以直接调用命令 API。
|
|
231
|
+
|
|
202
232
|
文本格式:
|
|
203
233
|
|
|
204
234
|
```ts
|
|
@@ -217,7 +247,7 @@ editor.executeCommand({ type: 'format.fontFamily', value: 'Arial' });
|
|
|
217
247
|
editor.executeCommand({ type: 'format.lineHeight', value: '1.75' });
|
|
218
248
|
```
|
|
219
249
|
|
|
220
|
-
|
|
250
|
+
段落、列表、对齐:
|
|
221
251
|
|
|
222
252
|
```ts
|
|
223
253
|
editor.executeCommand({ type: 'block.paragraph' });
|
|
@@ -239,7 +269,7 @@ editor.executeCommand({ type: 'indent.increase' });
|
|
|
239
269
|
editor.executeCommand({ type: 'indent.decrease' });
|
|
240
270
|
```
|
|
241
271
|
|
|
242
|
-
|
|
272
|
+
链接、表格、媒体、历史:
|
|
243
273
|
|
|
244
274
|
```ts
|
|
245
275
|
editor.executeCommand({ type: 'link.set', href: 'https://example.com', text: 'Example' });
|
|
@@ -277,21 +307,23 @@ editor.executeCommand({ type: 'content.clear' });
|
|
|
277
307
|
```
|
|
278
308
|
|
|
279
309
|
默认 toolbar 不提供图片/视频 URL 插入入口,但保留 `media.insertImage` 和
|
|
280
|
-
`media.insertVideo`
|
|
281
|
-
`link
|
|
310
|
+
`media.insertVideo` 命令。主动链接编辑也不是当前默认内置重点,业务可以通过自定义菜单调用
|
|
311
|
+
`link.*` 命令。
|
|
282
312
|
|
|
283
|
-
## Toolbar
|
|
313
|
+
## Toolbar / Tabbar 菜单配置
|
|
284
314
|
|
|
285
|
-
|
|
315
|
+
DOM toolbar/tabbar 只通过 `createRichTextToolbar()` 创建。它使用同一套菜单 schema/config,
|
|
316
|
+
并通过传入的 `EditorAPI` 订阅状态、执行命令。
|
|
286
317
|
|
|
287
318
|
```ts
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
319
|
+
const toolbar = createRichTextToolbar(toolbarContainer, {
|
|
320
|
+
editor,
|
|
321
|
+
placement: 'bottom',
|
|
291
322
|
toolbarConfig: {
|
|
292
323
|
toolbarKeys: [
|
|
293
324
|
'bold',
|
|
294
325
|
'italic',
|
|
326
|
+
'underline',
|
|
295
327
|
'|',
|
|
296
328
|
'heading-1',
|
|
297
329
|
'quote',
|
|
@@ -306,45 +338,39 @@ createRichTextEditor(container, {
|
|
|
306
338
|
excludeKeys: ['quote']
|
|
307
339
|
}
|
|
308
340
|
});
|
|
341
|
+
|
|
342
|
+
toolbar.update();
|
|
309
343
|
```
|
|
310
344
|
|
|
311
345
|
`toolbarConfig` 规则:
|
|
312
346
|
|
|
313
347
|
- `toolbarKeys`:完整控制显示顺序。
|
|
348
|
+
- 字符串项使用 `MenuItem.id`;`'|'` 只渲染分割线,不执行命令。
|
|
349
|
+
- 分组项使用 `{ key, title, icon, menuKeys }`;DOM toolbar 会渲染一个分组按钮,点击后打开
|
|
350
|
+
Y 轴纵向收纳菜单,例如上面的配置会显示 `[历史]` 按钮,菜单里是 `[撤销]`、`[重做]`。
|
|
351
|
+
- `key` 是分组入口的稳定 id,`title` 是按钮文案和 `aria-label`,`icon` 用于分组按钮图标。
|
|
314
352
|
- `insertKeys`:在默认菜单基础上插入。
|
|
315
353
|
- `excludeKeys`:隐藏默认菜单。
|
|
316
|
-
- `'|'`:分割线。
|
|
317
354
|
|
|
318
|
-
|
|
355
|
+
独立 toolbar:
|
|
319
356
|
|
|
320
357
|
```ts
|
|
321
|
-
import { createRichTextEditor, createRichTextToolbar, defaultMenuSchema } from 'bridgerte';
|
|
322
|
-
|
|
323
|
-
const editor = createRichTextEditor(editorContainer, {
|
|
324
|
-
toolbarMode: 'none'
|
|
325
|
-
});
|
|
326
|
-
|
|
327
358
|
const toolbar = createRichTextToolbar(toolbarContainer, {
|
|
328
359
|
editor,
|
|
329
|
-
|
|
330
|
-
placement: 'bottom',
|
|
360
|
+
placement: 'top',
|
|
331
361
|
toolbarConfig: {
|
|
332
362
|
toolbarKeys: ['bold', 'italic', '|', 'undo', 'redo']
|
|
333
363
|
}
|
|
334
364
|
});
|
|
335
365
|
|
|
336
366
|
toolbar.update();
|
|
337
|
-
toolbar.destroy();
|
|
338
|
-
editor.destroy();
|
|
339
367
|
```
|
|
340
368
|
|
|
341
|
-
`placement: 'top' | 'bottom'` 只表达菜单语义和默认样式状态。toolbar DOM 放在哪里、是否吸顶或吸底,
|
|
342
|
-
由业务自己的布局决定。
|
|
343
|
-
|
|
344
369
|
## 自定义菜单、Icon 和文案
|
|
345
370
|
|
|
346
371
|
```ts
|
|
347
|
-
import { createRichTextEditor,
|
|
372
|
+
import { createRichTextEditor, createRichTextToolbar } from 'bridgerte/dom';
|
|
373
|
+
import { defaultMenuSchema, type MenuItem } from 'bridgerte/native-spec';
|
|
348
374
|
|
|
349
375
|
const customMenu: MenuItem = {
|
|
350
376
|
id: 'custom-clear',
|
|
@@ -354,36 +380,50 @@ const customMenu: MenuItem = {
|
|
|
354
380
|
group: 'history'
|
|
355
381
|
};
|
|
356
382
|
|
|
357
|
-
|
|
358
|
-
|
|
383
|
+
const menuSchema = [...defaultMenuSchema, customMenu];
|
|
384
|
+
const icons = {
|
|
385
|
+
'custom-clear': '<svg aria-hidden="true" viewBox="0 0 24 24"><path d="M4 6h16"/></svg>'
|
|
386
|
+
};
|
|
387
|
+
const menuLabels = {
|
|
388
|
+
'custom-clear': '清空文档',
|
|
389
|
+
bold: '加粗文本'
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const editor = createRichTextEditor(editorContainer, {
|
|
393
|
+
menuSchema,
|
|
394
|
+
icons,
|
|
395
|
+
menuLabels
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const toolbar = createRichTextToolbar(toolbarContainer, {
|
|
399
|
+
editor,
|
|
400
|
+
menuSchema,
|
|
359
401
|
toolbarConfig: {
|
|
360
402
|
insertKeys: {
|
|
361
403
|
index: 0,
|
|
362
404
|
keys: ['custom-clear', '|']
|
|
363
405
|
}
|
|
364
406
|
},
|
|
365
|
-
icons
|
|
366
|
-
|
|
367
|
-
},
|
|
368
|
-
menuLabels: {
|
|
369
|
-
'custom-clear': '清空文档',
|
|
370
|
-
bold: '加粗文本'
|
|
371
|
-
}
|
|
407
|
+
icons,
|
|
408
|
+
menuLabels
|
|
372
409
|
});
|
|
410
|
+
|
|
411
|
+
toolbar.update();
|
|
373
412
|
```
|
|
374
413
|
|
|
375
|
-
|
|
414
|
+
稳定约束:
|
|
376
415
|
|
|
377
416
|
- `MenuItem.id` 是菜单配置使用的稳定 key。
|
|
378
417
|
- `MenuItem.icon` 是稳定 icon key,不是 SVG 字符串。
|
|
379
|
-
-
|
|
380
|
-
-
|
|
381
|
-
- `menuLabels`
|
|
382
|
-
- 缺失 icon 时,DOM
|
|
418
|
+
- 业务覆盖 icon 只能通过 `icons` map。
|
|
419
|
+
- 业务覆盖文案只能通过 `menuLabels`。
|
|
420
|
+
- `menuLabels` 影响按钮文本、tooltip 和 `aria-label`,不改变命令语义。
|
|
421
|
+
- 缺失 icon 时,DOM 菜单使用 label 文本兜底。
|
|
383
422
|
|
|
384
423
|
## Hoverbar
|
|
385
424
|
|
|
386
|
-
选中文本 hoverbar 默认开启,复用 `menuSchema`、`icons` 和 `menuLabels
|
|
425
|
+
选中文本 hoverbar 默认开启,复用 `menuSchema`、`icons` 和 `menuLabels`。它有独立的
|
|
426
|
+
`hoverbarConfig`,可以和 toolbar 展示不同菜单。
|
|
387
427
|
|
|
388
428
|
```ts
|
|
389
429
|
createRichTextEditor(container, {
|
|
@@ -412,11 +452,13 @@ createRichTextEditor(container, {
|
|
|
412
452
|
});
|
|
413
453
|
```
|
|
414
454
|
|
|
415
|
-
关闭后只是不显示 DOM hoverbar,不影响底层命令 API。
|
|
455
|
+
关闭后只是不显示 DOM hoverbar,不影响底层命令 API。H5/WebView 或品牌化项目可以关闭内置
|
|
456
|
+
hoverbar 后自绘选区菜单。
|
|
416
457
|
|
|
417
458
|
## 参数面板
|
|
418
459
|
|
|
419
|
-
|
|
460
|
+
颜色、背景色、字号、字体、行高、表格和代码语言都通过 `PayloadPanelSchema` 描述候选项。
|
|
461
|
+
DOM 默认 UI 和业务自绘 request 使用同一份 schema。
|
|
420
462
|
|
|
421
463
|
```ts
|
|
422
464
|
createRichTextEditor(container, {
|
|
@@ -432,7 +474,7 @@ createRichTextEditor(container, {
|
|
|
432
474
|
}
|
|
433
475
|
}
|
|
434
476
|
},
|
|
435
|
-
|
|
477
|
+
color: {
|
|
436
478
|
fields: {
|
|
437
479
|
value: {
|
|
438
480
|
options: [
|
|
@@ -442,15 +484,22 @@ createRichTextEditor(container, {
|
|
|
442
484
|
}
|
|
443
485
|
}
|
|
444
486
|
},
|
|
445
|
-
|
|
487
|
+
table: {
|
|
446
488
|
fields: {
|
|
447
489
|
rows: { defaultValue: '2', max: 6 },
|
|
448
490
|
cols: { defaultValue: '3', max: 6 }
|
|
449
491
|
}
|
|
492
|
+
},
|
|
493
|
+
'code-block-language': {
|
|
494
|
+
fields: {
|
|
495
|
+
language: {
|
|
496
|
+
includeValues: ['plain', 'javascript', 'typescript', 'json']
|
|
497
|
+
}
|
|
498
|
+
}
|
|
450
499
|
}
|
|
451
500
|
},
|
|
452
501
|
onPayloadPanelRequest(request) {
|
|
453
|
-
if (request.panel.id === '
|
|
502
|
+
if (request.panel.id === 'color') {
|
|
454
503
|
renderColorPanel(request);
|
|
455
504
|
return true;
|
|
456
505
|
}
|
|
@@ -458,21 +507,64 @@ createRichTextEditor(container, {
|
|
|
458
507
|
});
|
|
459
508
|
```
|
|
460
509
|
|
|
461
|
-
|
|
510
|
+
自绘接管规则:
|
|
511
|
+
|
|
512
|
+
```ts
|
|
513
|
+
createRichTextEditor(container, {
|
|
514
|
+
onPayloadPanelRequest(request) {
|
|
515
|
+
renderPanel({
|
|
516
|
+
title: request.panel.title,
|
|
517
|
+
fields: request.panel.fields,
|
|
518
|
+
currentValues: request.currentValues,
|
|
519
|
+
readonly: request.readonly,
|
|
520
|
+
submit: request.submit,
|
|
521
|
+
cancel: request.cancel
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
`onPayloadPanelRequest` 返回 `true` 表示业务接管渲染,DOM 默认面板不会显示。业务自绘完成后:
|
|
462
530
|
|
|
463
531
|
```ts
|
|
464
532
|
request.submit({ value: '#1677ff' });
|
|
465
533
|
request.cancel();
|
|
466
534
|
```
|
|
467
535
|
|
|
468
|
-
readonly 下 request 会带 `readonly: true
|
|
536
|
+
readonly 下 request 会带 `readonly: true`,自绘层应展示只读态;DOM 默认面板不会打开。
|
|
537
|
+
|
|
538
|
+
代码块语言也可以直接传完整 schema:
|
|
539
|
+
|
|
540
|
+
```ts
|
|
541
|
+
createRichTextEditor(container, {
|
|
542
|
+
codeBlockLanguagePanel: {
|
|
543
|
+
id: 'code-block-language',
|
|
544
|
+
title: '代码语言',
|
|
545
|
+
fields: [
|
|
546
|
+
{
|
|
547
|
+
type: 'select',
|
|
548
|
+
name: 'language',
|
|
549
|
+
label: '语言',
|
|
550
|
+
options: [
|
|
551
|
+
{ label: '纯文本', value: 'plain' },
|
|
552
|
+
{ label: 'TypeScript', value: 'typescript' },
|
|
553
|
+
{ label: 'JSON', value: 'json' }
|
|
554
|
+
]
|
|
555
|
+
}
|
|
556
|
+
]
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
```
|
|
469
560
|
|
|
470
561
|
## 图片、视频和上传
|
|
471
562
|
|
|
472
|
-
BridgeRTE
|
|
563
|
+
BridgeRTE 不内置上传后端。图片/视频上传必须由业务实现 `uploadAdapter`。
|
|
473
564
|
|
|
474
565
|
```ts
|
|
475
|
-
import
|
|
566
|
+
import { createRichTextEditor } from 'bridgerte/dom';
|
|
567
|
+
import type { UploadAdapter } from 'bridgerte/core';
|
|
476
568
|
|
|
477
569
|
const uploadBlob = async (url: string, file: unknown, signal?: AbortSignal) => {
|
|
478
570
|
const formData = new FormData();
|
|
@@ -512,6 +604,7 @@ createRichTextEditor(container, {
|
|
|
512
604
|
|
|
513
605
|
- 默认 toolbar 提供本地上传图片/视频入口。
|
|
514
606
|
- URL 图片/视频插入使用 `media.insertImage` / `media.insertVideo` 命令。
|
|
607
|
+
- 图片/视频加载前会显示占位和 loading 状态,资源加载完成后再显示真实媒体。
|
|
515
608
|
- 上传失败时可以重试或删除。
|
|
516
609
|
- 成功态 controls 支持左/中/右对齐、`20%`、`50%`、`100%` 显示比例和删除。
|
|
517
610
|
- `mediaDefaultWidthPercent` 可设为 `20 | 50 | 100`,默认 `50`。
|
|
@@ -521,7 +614,13 @@ createRichTextEditor(container, {
|
|
|
521
614
|
```ts
|
|
522
615
|
createRichTextEditor(container, {
|
|
523
616
|
mediaControlsConfig: {
|
|
524
|
-
toolbarKeys: [
|
|
617
|
+
toolbarKeys: [
|
|
618
|
+
'media-align-left',
|
|
619
|
+
'media-align-center',
|
|
620
|
+
'media-align-right',
|
|
621
|
+
'|',
|
|
622
|
+
'media-remove'
|
|
623
|
+
]
|
|
525
624
|
},
|
|
526
625
|
menuLabels: {
|
|
527
626
|
'media-remove': '删除媒体'
|
|
@@ -534,10 +633,12 @@ createRichTextEditor(container, {
|
|
|
534
633
|
|
|
535
634
|
## Mention
|
|
536
635
|
|
|
537
|
-
`@` mention
|
|
636
|
+
`@` mention 默认开启。`mentionProvider` 负责返回候选数据,展示字段由
|
|
637
|
+
`mentionMenuConfig` 控制。
|
|
538
638
|
|
|
539
639
|
```ts
|
|
540
|
-
import
|
|
640
|
+
import { createRichTextEditor } from 'bridgerte/dom';
|
|
641
|
+
import type { MentionItem } from 'bridgerte/core';
|
|
541
642
|
|
|
542
643
|
const mentionProvider = async (query: string): Promise<MentionItem[]> => {
|
|
543
644
|
const response = await fetch(`/api/members?q=${encodeURIComponent(query)}`);
|
|
@@ -603,7 +704,7 @@ createRichTextEditor(container, {
|
|
|
603
704
|
追加动态候选:
|
|
604
705
|
|
|
605
706
|
```ts
|
|
606
|
-
import type { SlashCommandItem } from 'bridgerte';
|
|
707
|
+
import type { SlashCommandItem } from 'bridgerte/core';
|
|
607
708
|
|
|
608
709
|
const slashCommandProvider = async (query: string): Promise<SlashCommandItem[]> => [
|
|
609
710
|
{
|
|
@@ -653,7 +754,7 @@ createRichTextEditor(container, {
|
|
|
653
754
|
|
|
654
755
|
## WebView
|
|
655
756
|
|
|
656
|
-
WebView
|
|
757
|
+
WebView 页面内使用 `createWebViewBridgeRuntime()` 接 RN/Flutter 外壳消息。
|
|
657
758
|
|
|
658
759
|
```ts
|
|
659
760
|
import { createWebViewBridgeRuntime } from 'bridgerte/webview';
|
|
@@ -707,20 +808,23 @@ runtime.receive(messageFromNative);
|
|
|
707
808
|
- `editor.ready`
|
|
708
809
|
- `editor.content`
|
|
709
810
|
- `editor.contentChange`
|
|
710
|
-
- `editor.selectionChange`
|
|
711
811
|
- `editor.commandStateChange`
|
|
712
812
|
- `editor.payloadPanelRequest`
|
|
713
813
|
- `editor.uploadRequest`
|
|
714
814
|
- `editor.heightChange`
|
|
715
815
|
- `editor.error`
|
|
716
816
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
`editor.contentChange`
|
|
817
|
+
内容消息边界:
|
|
818
|
+
|
|
819
|
+
- 高频自动 `editor.contentChange` 只传轻量摘要。
|
|
820
|
+
- 完整 `EditorContent` 通过原生侧发送 `editor.requestContent` 获取。
|
|
821
|
+
- `requestContent` 的响应消息是 `editor.content`。
|
|
822
|
+
- 兼容期也会同步发送旧 `editor.contentChange` 完整响应。
|
|
823
|
+
- bridge 不传 File、Blob、base64 或大体积二进制文件。
|
|
720
824
|
|
|
721
825
|
## RN / Flutter 原生菜单
|
|
722
826
|
|
|
723
|
-
|
|
827
|
+
RN/Flutter 原生侧可以读取 `bridgerte/native-spec` 来渲染自己的菜单:
|
|
724
828
|
|
|
725
829
|
```ts
|
|
726
830
|
import {
|
|
@@ -742,7 +846,7 @@ const toolbarItems = resolveToolbarMenu(defaultToolbarConfig, defaultMenuSchema)
|
|
|
742
846
|
- `payloadPanel` 描述需要原生侧补齐的参数。
|
|
743
847
|
- 命令状态可用 `isMenuItemCommandState(item, state)` 匹配。
|
|
744
848
|
|
|
745
|
-
原生菜单不复用 DOM CSS;WebView
|
|
849
|
+
原生菜单不复用 DOM CSS;WebView 内编辑器样式通过 `--bridgerte-*` CSS Variables 覆盖。
|
|
746
850
|
|
|
747
851
|
## 样式和主题
|
|
748
852
|
|
|
@@ -794,6 +898,13 @@ import 'bridgerte/style.css';
|
|
|
794
898
|
- `--bridgerte-hoverbar-button-size`
|
|
795
899
|
- `--bridgerte-editor-padding`
|
|
796
900
|
|
|
901
|
+
PC/H5 边界:
|
|
902
|
+
|
|
903
|
+
- DOM 默认样式只服务 Web/PC/H5。
|
|
904
|
+
- RN/Flutter 原生菜单不复用 DOM CSS。
|
|
905
|
+
- H5 触屏端不依赖 hover tooltip。
|
|
906
|
+
- 业务可以在外层容器覆盖 CSS Variables 实现品牌主题。
|
|
907
|
+
|
|
797
908
|
## 性能建议
|
|
798
909
|
|
|
799
910
|
BridgeRTE 按 10w 字符级内容设计输入路径:
|
|
@@ -824,7 +935,8 @@ const save = async () => {
|
|
|
824
935
|
|
|
825
936
|
## 当前边界
|
|
826
937
|
|
|
938
|
+
- 业务项目只安装 `bridgerte`,不需要安装 Lexical。
|
|
827
939
|
- 默认 toolbar 不提供图片/视频 URL 插入入口;业务用命令 API 自定义入口。
|
|
828
940
|
- 主动链接编辑不作为默认内置入口;业务用 `link.*` 命令自定义入口。
|
|
829
|
-
- BridgeRTE
|
|
941
|
+
- BridgeRTE 不内置上传后端,上传必须由业务实现 `uploadAdapter`。
|
|
830
942
|
- WebView bridge 不传大体积二进制和 base64 文件。
|