@ap666/office-word 0.1.7 → 0.2.10
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 +599 -118
- package/dist/components/CountdownBlockView.vue.d.ts +8 -1
- package/dist/components/RichTextEditor.vue.d.ts +2 -0
- package/dist/index.js +5381 -4138
- package/dist/index.umd.cjs +1453 -517
- package/dist/style.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,26 +1,41 @@
|
|
|
1
1
|
# @ap666/office-word
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
个人开发,未全方位测试,用于生产请多测试一下
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
中文 | [English](#english)
|
|
6
|
+
|
|
7
|
+
## 中文
|
|
8
|
+
|
|
9
|
+
`@ap666/office-word` 是一个基于 Vue 3 和 Tiptap 3 的富文本编辑器组件,内置文档式编辑界面、图片/视频/文件块、表格、公式、倒计时、高亮块、大纲、预览模式、导出能力,以及可选的 Yjs 协同编辑。
|
|
10
|
+
|
|
11
|
+
### 安装
|
|
6
12
|
|
|
7
13
|
```bash
|
|
8
14
|
npm install @ap666/office-word
|
|
9
15
|
```
|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
组件样式会作为独立 CSS 文件发布。建议在应用入口显式引入:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import '@ap666/office-word/style.css'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
如果你的包管理器没有自动安装 peer dependencies,请同时安装运行时 peer 包:
|
|
12
24
|
|
|
13
25
|
```bash
|
|
14
26
|
npm install vue @tiptap/core @tiptap/vue-3 @tiptap/pm @tiptap/starter-kit @tiptap/extension-placeholder @tiptap/extension-code-block @tiptap/extension-code-block-lowlight @tiptap/extension-collaboration @tiptap/extension-collaboration-caret @tiptap/extension-font-family @tiptap/extension-subscript @tiptap/extension-superscript @tiptap/extension-table @tiptap/extension-table-cell @tiptap/extension-table-header @tiptap/extension-table-row @tiptap/extension-task-item @tiptap/extension-task-list @tiptap/extension-text-style @tiptap/extension-underline @tiptap/y-tiptap lowlight y-prosemirror yjs
|
|
15
27
|
```
|
|
16
28
|
|
|
17
|
-
|
|
29
|
+
`html2canvas`、`jspdf`、`katex`、`marked`、`plyr` 已作为普通 dependencies 随包安装。
|
|
30
|
+
|
|
31
|
+
### 快速开始
|
|
18
32
|
|
|
19
33
|
```vue
|
|
20
34
|
<script setup lang="ts">
|
|
21
35
|
import { ref } from 'vue'
|
|
22
36
|
import type { JSONContent } from '@tiptap/core'
|
|
23
37
|
import { RichTextEditor } from '@ap666/office-word'
|
|
38
|
+
import '@ap666/office-word/style.css'
|
|
24
39
|
|
|
25
40
|
const content = ref<JSONContent | null>(null)
|
|
26
41
|
</script>
|
|
@@ -30,22 +45,28 @@ const content = ref<JSONContent | null>(null)
|
|
|
30
45
|
</template>
|
|
31
46
|
```
|
|
32
47
|
|
|
33
|
-
|
|
48
|
+
也可以使用默认导出:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import RichTextEditor from '@ap666/office-word'
|
|
52
|
+
```
|
|
34
53
|
|
|
35
|
-
|
|
54
|
+
### Props
|
|
36
55
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
| Prop | 类型 | 默认值 | 说明 |
|
|
57
|
+
| --- | --- | --- | --- |
|
|
58
|
+
| `modelValue` | `JSONContent \| null` | `null` | 编辑器内容,支持 `v-model`。 |
|
|
59
|
+
| `editable` | `boolean` | `true` | 是否允许编辑。 |
|
|
60
|
+
| `mode` | `'edit' \| 'preview'` | `'edit'` | 编辑模式或预览模式。 |
|
|
61
|
+
| `outlinePlacement` | `'left' \| 'right'` | `'right'` | 大纲面板位置。 |
|
|
62
|
+
| `messages` | `RichTextEditorMessages \| null` | `null` | 覆盖内置 UI 文案。 |
|
|
63
|
+
| `enabledExportItems` | `RichTextEditorExportItemKey[] \| null` | `null` | 导出菜单白名单。 |
|
|
64
|
+
| `enabledInsertMenuItems` | `RichTextEditorInsertMenuItemKey[] \| null` | `null` | 插入菜单白名单。 |
|
|
65
|
+
| `enabledToolbarActions` | `RichTextEditorToolbarActionKey[] \| null` | `null` | 工具栏能力白名单。 |
|
|
66
|
+
| `placeholder` | `string` | `''` | 空内容占位文案。 |
|
|
67
|
+
| `collaboration` | `RichTextEditorCollaborationOptions \| null` | `null` | Yjs 协同配置。 |
|
|
47
68
|
|
|
48
|
-
|
|
69
|
+
白名单 props 不传时表示启用全部内置项;传入数组后,仅数组里的项目会显示并可用。
|
|
49
70
|
|
|
50
71
|
```vue
|
|
51
72
|
<RichTextEditor
|
|
@@ -56,84 +77,130 @@ When these whitelist props are omitted, all built-in options stay enabled. Once
|
|
|
56
77
|
/>
|
|
57
78
|
```
|
|
58
79
|
|
|
59
|
-
|
|
80
|
+
### 可用 Key
|
|
81
|
+
|
|
82
|
+
导出菜单:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
type RichTextEditorExportItemKey = 'pdf' | 'html' | 'image' | 'print'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
插入菜单:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
type RichTextEditorInsertMenuItemKey =
|
|
92
|
+
| 'image'
|
|
93
|
+
| 'video'
|
|
94
|
+
| 'table'
|
|
95
|
+
| 'local-file'
|
|
96
|
+
| 'columns'
|
|
97
|
+
| 'highlight-block'
|
|
98
|
+
| 'date'
|
|
99
|
+
| 'code-block'
|
|
100
|
+
| 'formula'
|
|
101
|
+
| 'blockquote'
|
|
102
|
+
| 'emoji'
|
|
103
|
+
| 'link'
|
|
104
|
+
| 'divider'
|
|
105
|
+
| 'countdown'
|
|
106
|
+
| 'markdown-import'
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
工具栏:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
type RichTextEditorToolbarActionKey = 'blockquote'
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 事件
|
|
116
|
+
|
|
117
|
+
| 事件 | Payload | 说明 |
|
|
118
|
+
| --- | --- | --- |
|
|
119
|
+
| `update:modelValue` | `JSONContent` | 内容更新,用于 `v-model`。 |
|
|
120
|
+
| `change` | `JSONContent` | 内容更新事件。 |
|
|
121
|
+
| `local-file-upload` | `RichTextEditorLocalFilePayload` | 用户通过本地文件选择器插入文件后触发。 |
|
|
122
|
+
| `local-file-download` | `RichTextEditorLocalFilePayload` | 用户点击本地文件卡片下载按钮时触发。 |
|
|
123
|
+
|
|
124
|
+
### 自定义文案
|
|
125
|
+
|
|
126
|
+
编辑器默认 UI 文案为中文。你可以用 `messages` 覆盖任意 key:
|
|
60
127
|
|
|
61
128
|
```vue
|
|
62
129
|
<RichTextEditor
|
|
63
130
|
v-model="content"
|
|
64
131
|
:messages="{
|
|
65
|
-
'insert.localFile': '
|
|
66
|
-
'export.label': '
|
|
132
|
+
'insert.localFile': '附件',
|
|
133
|
+
'export.label': '下载',
|
|
67
134
|
}"
|
|
68
135
|
/>
|
|
69
136
|
```
|
|
70
137
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
138
|
+
常用文案 key:
|
|
139
|
+
|
|
140
|
+
| Key | 默认中文 |
|
|
141
|
+
| --- | --- |
|
|
142
|
+
| `insert.label` | 插入 |
|
|
143
|
+
| `insert.section.general` | 通用 |
|
|
144
|
+
| `insert.section.apps` | 小应用 |
|
|
145
|
+
| `insert.section.external` | 外部内容 |
|
|
146
|
+
| `insert.image` | 图片 |
|
|
147
|
+
| `insert.video` | 视频 |
|
|
148
|
+
| `insert.table` | 表格 |
|
|
149
|
+
| `insert.localFile` | 本地文件 |
|
|
150
|
+
| `insert.columns` | 分栏 |
|
|
151
|
+
| `insert.highlightBlock` | 高亮块 |
|
|
152
|
+
| `insert.date` | 日期 |
|
|
153
|
+
| `insert.codeBlock` | 代码块 |
|
|
154
|
+
| `insert.formula` | 公式 |
|
|
155
|
+
| `insert.blockquote` | 引用 |
|
|
156
|
+
| `insert.emoji` | 表情符号 |
|
|
157
|
+
| `insert.link` | 超链接 |
|
|
158
|
+
| `insert.divider` | 分隔线 |
|
|
159
|
+
| `insert.countdown` | 倒计时 |
|
|
160
|
+
| `insert.markdownImport` | Markdown 导入 |
|
|
161
|
+
| `export.label` | 导出 |
|
|
162
|
+
| `export.pdf` | 导出 PDF |
|
|
163
|
+
| `export.pdf.loading` | 导出 PDF 中... |
|
|
164
|
+
| `export.html` | 导出 HTML |
|
|
165
|
+
| `export.html.loading` | 导出 HTML 中... |
|
|
166
|
+
| `export.image` | 导出图片 |
|
|
167
|
+
| `export.image.loading` | 导出图片中... |
|
|
168
|
+
| `print.label` | 打印 |
|
|
169
|
+
| `print.loading` | 打印中... |
|
|
170
|
+
| `quote.apply` | 应用引用 |
|
|
171
|
+
| `quote.cancel` | 取消引用 |
|
|
172
|
+
| `quote.borderColor` | 边框颜色 |
|
|
173
|
+
| `quote.backgroundColor` | 背景颜色 |
|
|
174
|
+
| `outline.label` | 大纲 |
|
|
175
|
+
| `outline.collapse` | 收起大纲 |
|
|
176
|
+
| `outline.empty.description` | 对文档内容应用“标题”样式,即可自动生成大纲。 |
|
|
177
|
+
| `outline.empty.tip` | 点击左下角“大纲”按钮可以随时展开或收起。 |
|
|
178
|
+
| `status.wordCountUnit` | 个字 |
|
|
179
|
+
| `status.presentation.enter` | 演示 |
|
|
180
|
+
| `status.presentation.exit` | 退出演示 |
|
|
181
|
+
| `status.fullscreen.enter` | 全屏 |
|
|
182
|
+
| `status.fullscreen.exit` | 退出全屏 |
|
|
183
|
+
| `countdown.selectTime` | 请选择时间 |
|
|
184
|
+
| `countdown.settingsTitle` | 倒计时设置 |
|
|
185
|
+
| `formula.insertTitle` | 插入 LaTeX 公式 |
|
|
186
|
+
|
|
187
|
+
### 预览模式
|
|
188
|
+
|
|
189
|
+
`mode="preview"` 会切换到只读预览外壳。预览模式下工具栏隐藏,编辑禁用,大纲仍可放在左侧或右侧。
|
|
121
190
|
|
|
122
191
|
```vue
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
/>
|
|
129
|
-
</template>
|
|
192
|
+
<RichTextEditor
|
|
193
|
+
v-model="content"
|
|
194
|
+
mode="preview"
|
|
195
|
+
outline-placement="right"
|
|
196
|
+
/>
|
|
130
197
|
```
|
|
131
198
|
|
|
132
|
-
|
|
199
|
+
窄屏下预览模式会自动缩放页面画布,让文档在手机上保持可读。
|
|
133
200
|
|
|
134
|
-
|
|
201
|
+
### 协同编辑
|
|
135
202
|
|
|
136
|
-
|
|
203
|
+
组件支持普通单人编辑,也支持可选的 Yjs 协同编辑。
|
|
137
204
|
|
|
138
205
|
```vue
|
|
139
206
|
<script setup lang="ts">
|
|
@@ -142,6 +209,7 @@ import * as Y from 'yjs'
|
|
|
142
209
|
import { WebsocketProvider } from 'y-websocket'
|
|
143
210
|
import type { JSONContent } from '@tiptap/core'
|
|
144
211
|
import { RichTextEditor } from '@ap666/office-word'
|
|
212
|
+
import '@ap666/office-word/style.css'
|
|
145
213
|
|
|
146
214
|
const ydoc = new Y.Doc()
|
|
147
215
|
const provider = new WebsocketProvider('ws://localhost:1234', 'office-word-demo', ydoc)
|
|
@@ -164,13 +232,13 @@ const content = ref<JSONContent | null>(null)
|
|
|
164
232
|
</template>
|
|
165
233
|
```
|
|
166
234
|
|
|
167
|
-
|
|
235
|
+
协同模式下,Yjs fragment 是内容的事实来源。组件仍会 emit JSON 更新,但外部 `modelValue` 的变化不会再反向写入编辑器。如果需要远程光标,请在宿主项目安装 `y-websocket`,并传入 `provider` 和 `user`。
|
|
168
236
|
|
|
169
|
-
|
|
237
|
+
不要把 `modelValue` 当作协同房间的初始化种子。如果房间需要默认内容,请在挂载编辑器之前先初始化 Yjs 文档。
|
|
170
238
|
|
|
171
|
-
###
|
|
239
|
+
### 内置 Yjs 服务示例
|
|
172
240
|
|
|
173
|
-
|
|
241
|
+
发布包内包含一个最小 Yjs WebSocket 服务示例:
|
|
174
242
|
|
|
175
243
|
```bash
|
|
176
244
|
cd node_modules/@ap666/office-word/yjs
|
|
@@ -178,9 +246,7 @@ npm install
|
|
|
178
246
|
npm run start
|
|
179
247
|
```
|
|
180
248
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
Use the same websocket address and room name on every client:
|
|
249
|
+
默认监听地址为 `ws://0.0.0.0:1234`。客户端需要使用相同 WebSocket 地址和房间名:
|
|
184
250
|
|
|
185
251
|
```ts
|
|
186
252
|
import * as Y from 'yjs'
|
|
@@ -190,9 +256,9 @@ const ydoc = new Y.Doc()
|
|
|
190
256
|
const provider = new WebsocketProvider('ws://127.0.0.1:1234', 'office-word-demo', ydoc)
|
|
191
257
|
```
|
|
192
258
|
|
|
193
|
-
|
|
259
|
+
### 实例 API
|
|
194
260
|
|
|
195
|
-
|
|
261
|
+
通过 Vue `ref` 获取组件实例方法:
|
|
196
262
|
|
|
197
263
|
```vue
|
|
198
264
|
<script setup lang="ts">
|
|
@@ -208,22 +274,83 @@ const editorRef = ref<RichTextEditorInstance | null>(null)
|
|
|
208
274
|
</template>
|
|
209
275
|
```
|
|
210
276
|
|
|
211
|
-
|
|
277
|
+
可用方法:
|
|
278
|
+
|
|
279
|
+
| 方法 | 返回值 | 说明 |
|
|
280
|
+
| --- | --- | --- |
|
|
281
|
+
| `exportPdf()` | `Promise<Blob \| null>` | 导出 PDF。 |
|
|
282
|
+
| `exportImage(options?)` | `Promise<Blob \| null>` | 导出图片,支持 PNG/JPEG。 |
|
|
283
|
+
| `exportHtml()` | `string \| null` | 导出 HTML 字符串。 |
|
|
284
|
+
| `insertImage(payload)` | `boolean` | 插入 1 到 4 张图片。 |
|
|
285
|
+
| `insertVideo(payload)` | `boolean` | 插入视频块。 |
|
|
286
|
+
| `insertFile(payload)` | `boolean` | 插入链接/文件预览块。 |
|
|
287
|
+
| `insertLocalFile(payload)` | `boolean` | 插入本地文件卡片。 |
|
|
288
|
+
| `openLocalFilePicker()` | `void` | 打开本地文件选择器。 |
|
|
289
|
+
| `focus()` | `void` | 聚焦编辑器。 |
|
|
290
|
+
| `getJSON()` | `JSONContent \| null` | 获取当前 JSON 内容。 |
|
|
291
|
+
|
|
292
|
+
### 上传并插入
|
|
293
|
+
|
|
294
|
+
推荐集成流程:
|
|
295
|
+
|
|
296
|
+
1. 在业务层上传文件。
|
|
297
|
+
2. 等待接口返回可访问 URL。
|
|
298
|
+
3. 调用组件实例方法插入返回内容。
|
|
299
|
+
|
|
300
|
+
插入图片:
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
async function uploadImage(file: File) {
|
|
304
|
+
const imageUrl = await yourUploadApi(file)
|
|
305
|
+
|
|
306
|
+
editorRef.value?.insertImage({
|
|
307
|
+
src: imageUrl,
|
|
308
|
+
name: file.name,
|
|
309
|
+
alt: file.name,
|
|
310
|
+
description: '',
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
```
|
|
212
314
|
|
|
213
|
-
|
|
214
|
-
- `exportImage(options?): Promise<Blob | null>`
|
|
215
|
-
- `exportHtml(): string | null`
|
|
216
|
-
- `insertImage(payload | payload[]): boolean`
|
|
217
|
-
- `insertVideo(payload): boolean`
|
|
218
|
-
- `insertFile(payload): boolean`
|
|
219
|
-
- `insertLocalFile(payload): boolean`
|
|
220
|
-
- `openLocalFilePicker(): void`
|
|
221
|
-
- `focus(): void`
|
|
222
|
-
- `getJSON(): JSONContent | null`
|
|
315
|
+
一次最多插入 4 张图片:
|
|
223
316
|
|
|
224
|
-
|
|
317
|
+
```ts
|
|
318
|
+
editorRef.value?.insertImage([
|
|
319
|
+
{ src: 'https://cdn.example.com/a.png', name: 'a.png' },
|
|
320
|
+
{ src: 'https://cdn.example.com/b.png', name: 'b.png' },
|
|
321
|
+
])
|
|
322
|
+
```
|
|
225
323
|
|
|
226
|
-
|
|
324
|
+
插入视频:
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
async function uploadVideo(file: File) {
|
|
328
|
+
const videoUrl = await yourUploadApi(file)
|
|
329
|
+
|
|
330
|
+
editorRef.value?.insertVideo({
|
|
331
|
+
src: videoUrl,
|
|
332
|
+
name: file.name,
|
|
333
|
+
mimeType: file.type || 'video/mp4',
|
|
334
|
+
description: 'video description',
|
|
335
|
+
})
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
插入文件链接/预览块:
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
async function uploadFile(file: File) {
|
|
343
|
+
const fileUrl = await yourUploadApi(file)
|
|
344
|
+
|
|
345
|
+
editorRef.value?.insertFile({
|
|
346
|
+
url: fileUrl,
|
|
347
|
+
name: file.name,
|
|
348
|
+
displayMode: 'text',
|
|
349
|
+
})
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
插入本地文件卡片:
|
|
227
354
|
|
|
228
355
|
```ts
|
|
229
356
|
editorRef.value?.insertLocalFile({
|
|
@@ -234,12 +361,7 @@ editorRef.value?.insertLocalFile({
|
|
|
234
361
|
})
|
|
235
362
|
```
|
|
236
363
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
- `@local-file-upload`: emitted after a local file is selected from the editor picker and inserted
|
|
240
|
-
- `@local-file-download`: emitted when the file card download button is clicked
|
|
241
|
-
|
|
242
|
-
## Export Example
|
|
364
|
+
### 导出示例
|
|
243
365
|
|
|
244
366
|
```ts
|
|
245
367
|
async function handleExportPdf() {
|
|
@@ -272,7 +394,318 @@ function handleExportHtml() {
|
|
|
272
394
|
}
|
|
273
395
|
```
|
|
274
396
|
|
|
275
|
-
|
|
397
|
+
### 类型导入
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
import type {
|
|
401
|
+
RichTextEditorCollaborationProvider,
|
|
402
|
+
RichTextEditorCollaborationUser,
|
|
403
|
+
RichTextEditorCollaborationOptions,
|
|
404
|
+
RichTextEditorExportItemKey,
|
|
405
|
+
RichTextEditorFilePayload,
|
|
406
|
+
RichTextEditorImageExportOptions,
|
|
407
|
+
RichTextEditorImagePayload,
|
|
408
|
+
RichTextEditorInsertMenuItemKey,
|
|
409
|
+
RichTextEditorInstance,
|
|
410
|
+
RichTextEditorLocalFilePayload,
|
|
411
|
+
RichTextEditorMessages,
|
|
412
|
+
RichTextEditorProps,
|
|
413
|
+
RichTextEditorToolbarActionKey,
|
|
414
|
+
RichTextEditorVideoPayload,
|
|
415
|
+
} from '@ap666/office-word'
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### 包内文档
|
|
419
|
+
|
|
420
|
+
- 使用指南:`docs/usage.md`
|
|
421
|
+
- Yjs 服务示例:`yjs/README.md`
|
|
422
|
+
|
|
423
|
+
## English
|
|
424
|
+
|
|
425
|
+
`@ap666/office-word` is a reusable rich text editor component for Vue 3 and Tiptap 3. It ships with a document-style editing UI, image/video/file blocks, tables, formulas, countdown blocks, highlight blocks, outline navigation, preview mode, export APIs, and optional Yjs collaboration.
|
|
426
|
+
|
|
427
|
+
### Installation
|
|
428
|
+
|
|
429
|
+
```bash
|
|
430
|
+
npm install @ap666/office-word
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
The component styles are published as a separate CSS file. Import it in your application entry:
|
|
434
|
+
|
|
435
|
+
```ts
|
|
436
|
+
import '@ap666/office-word/style.css'
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
If your package manager does not auto-install peer dependencies, install the runtime peer packages as well:
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
npm install vue @tiptap/core @tiptap/vue-3 @tiptap/pm @tiptap/starter-kit @tiptap/extension-placeholder @tiptap/extension-code-block @tiptap/extension-code-block-lowlight @tiptap/extension-collaboration @tiptap/extension-collaboration-caret @tiptap/extension-font-family @tiptap/extension-subscript @tiptap/extension-superscript @tiptap/extension-table @tiptap/extension-table-cell @tiptap/extension-table-header @tiptap/extension-table-row @tiptap/extension-task-item @tiptap/extension-task-list @tiptap/extension-text-style @tiptap/extension-underline @tiptap/y-tiptap lowlight y-prosemirror yjs
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
`html2canvas`, `jspdf`, `katex`, `marked`, and `plyr` are regular dependencies and are installed with the package.
|
|
446
|
+
|
|
447
|
+
### Quick Start
|
|
448
|
+
|
|
449
|
+
```vue
|
|
450
|
+
<script setup lang="ts">
|
|
451
|
+
import { ref } from 'vue'
|
|
452
|
+
import type { JSONContent } from '@tiptap/core'
|
|
453
|
+
import { RichTextEditor } from '@ap666/office-word'
|
|
454
|
+
import '@ap666/office-word/style.css'
|
|
455
|
+
|
|
456
|
+
const content = ref<JSONContent | null>(null)
|
|
457
|
+
</script>
|
|
458
|
+
|
|
459
|
+
<template>
|
|
460
|
+
<RichTextEditor v-model="content" />
|
|
461
|
+
</template>
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Default import is also supported:
|
|
465
|
+
|
|
466
|
+
```ts
|
|
467
|
+
import RichTextEditor from '@ap666/office-word'
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Props
|
|
471
|
+
|
|
472
|
+
| Prop | Type | Default | Description |
|
|
473
|
+
| --- | --- | --- | --- |
|
|
474
|
+
| `modelValue` | `JSONContent \| null` | `null` | Editor content. Supports `v-model`. |
|
|
475
|
+
| `editable` | `boolean` | `true` | Enables or disables editing. |
|
|
476
|
+
| `mode` | `'edit' \| 'preview'` | `'edit'` | Edit mode or read-only preview mode. |
|
|
477
|
+
| `outlinePlacement` | `'left' \| 'right'` | `'right'` | Outline panel placement. |
|
|
478
|
+
| `messages` | `RichTextEditorMessages \| null` | `null` | Overrides built-in UI labels. |
|
|
479
|
+
| `enabledExportItems` | `RichTextEditorExportItemKey[] \| null` | `null` | Export menu whitelist. |
|
|
480
|
+
| `enabledInsertMenuItems` | `RichTextEditorInsertMenuItemKey[] \| null` | `null` | Insert menu whitelist. |
|
|
481
|
+
| `enabledToolbarActions` | `RichTextEditorToolbarActionKey[] \| null` | `null` | Toolbar action whitelist. |
|
|
482
|
+
| `placeholder` | `string` | `''` | Placeholder text for empty content. |
|
|
483
|
+
| `collaboration` | `RichTextEditorCollaborationOptions \| null` | `null` | Yjs collaboration options. |
|
|
484
|
+
|
|
485
|
+
When whitelist props are omitted, all built-in options remain enabled. Once a list is passed, only listed items are visible and usable.
|
|
486
|
+
|
|
487
|
+
```vue
|
|
488
|
+
<RichTextEditor
|
|
489
|
+
v-model="content"
|
|
490
|
+
:enabled-export-items="['html', 'image']"
|
|
491
|
+
:enabled-insert-menu-items="['image', 'local-file', 'blockquote']"
|
|
492
|
+
:enabled-toolbar-actions="['blockquote']"
|
|
493
|
+
/>
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Available Keys
|
|
497
|
+
|
|
498
|
+
Export menu:
|
|
499
|
+
|
|
500
|
+
```ts
|
|
501
|
+
type RichTextEditorExportItemKey = 'pdf' | 'html' | 'image' | 'print'
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
Insert menu:
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
type RichTextEditorInsertMenuItemKey =
|
|
508
|
+
| 'image'
|
|
509
|
+
| 'video'
|
|
510
|
+
| 'table'
|
|
511
|
+
| 'local-file'
|
|
512
|
+
| 'columns'
|
|
513
|
+
| 'highlight-block'
|
|
514
|
+
| 'date'
|
|
515
|
+
| 'code-block'
|
|
516
|
+
| 'formula'
|
|
517
|
+
| 'blockquote'
|
|
518
|
+
| 'emoji'
|
|
519
|
+
| 'link'
|
|
520
|
+
| 'divider'
|
|
521
|
+
| 'countdown'
|
|
522
|
+
| 'markdown-import'
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Toolbar:
|
|
526
|
+
|
|
527
|
+
```ts
|
|
528
|
+
type RichTextEditorToolbarActionKey = 'blockquote'
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
### Events
|
|
532
|
+
|
|
533
|
+
| Event | Payload | Description |
|
|
534
|
+
| --- | --- | --- |
|
|
535
|
+
| `update:modelValue` | `JSONContent` | Content update event used by `v-model`. |
|
|
536
|
+
| `change` | `JSONContent` | Content update event. |
|
|
537
|
+
| `local-file-upload` | `RichTextEditorLocalFilePayload` | Emitted after a local file is picked and inserted. |
|
|
538
|
+
| `local-file-download` | `RichTextEditorLocalFilePayload` | Emitted when the local file card download button is clicked. |
|
|
539
|
+
|
|
540
|
+
### Custom Labels
|
|
541
|
+
|
|
542
|
+
The default UI labels are Chinese. Use `messages` to override only the keys you need:
|
|
543
|
+
|
|
544
|
+
```vue
|
|
545
|
+
<RichTextEditor
|
|
546
|
+
v-model="content"
|
|
547
|
+
:messages="{
|
|
548
|
+
'insert.localFile': 'Attachment',
|
|
549
|
+
'export.label': 'Download',
|
|
550
|
+
}"
|
|
551
|
+
/>
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
Common message keys:
|
|
555
|
+
|
|
556
|
+
| Key | Default Chinese | English meaning |
|
|
557
|
+
| --- | --- | --- |
|
|
558
|
+
| `insert.label` | 插入 | Insert |
|
|
559
|
+
| `insert.section.general` | 通用 | General |
|
|
560
|
+
| `insert.section.apps` | 小应用 | Apps |
|
|
561
|
+
| `insert.section.external` | 外部内容 | External content |
|
|
562
|
+
| `insert.image` | 图片 | Image |
|
|
563
|
+
| `insert.video` | 视频 | Video |
|
|
564
|
+
| `insert.table` | 表格 | Table |
|
|
565
|
+
| `insert.localFile` | 本地文件 | Local file |
|
|
566
|
+
| `insert.columns` | 分栏 | Columns |
|
|
567
|
+
| `insert.highlightBlock` | 高亮块 | Highlight block |
|
|
568
|
+
| `insert.date` | 日期 | Date |
|
|
569
|
+
| `insert.codeBlock` | 代码块 | Code block |
|
|
570
|
+
| `insert.formula` | 公式 | Formula |
|
|
571
|
+
| `insert.blockquote` | 引用 | Quote |
|
|
572
|
+
| `insert.emoji` | 表情符号 | Emoji |
|
|
573
|
+
| `insert.link` | 超链接 | Link |
|
|
574
|
+
| `insert.divider` | 分隔线 | Divider |
|
|
575
|
+
| `insert.countdown` | 倒计时 | Countdown |
|
|
576
|
+
| `insert.markdownImport` | Markdown 导入 | Markdown import |
|
|
577
|
+
| `export.label` | 导出 | Export |
|
|
578
|
+
| `export.pdf` | 导出 PDF | Export PDF |
|
|
579
|
+
| `export.pdf.loading` | 导出 PDF 中... | Exporting PDF... |
|
|
580
|
+
| `export.html` | 导出 HTML | Export HTML |
|
|
581
|
+
| `export.html.loading` | 导出 HTML 中... | Exporting HTML... |
|
|
582
|
+
| `export.image` | 导出图片 | Export image |
|
|
583
|
+
| `export.image.loading` | 导出图片中... | Exporting image... |
|
|
584
|
+
| `print.label` | 打印 | Print |
|
|
585
|
+
| `print.loading` | 打印中... | Printing... |
|
|
586
|
+
| `quote.apply` | 应用引用 | Apply quote |
|
|
587
|
+
| `quote.cancel` | 取消引用 | Cancel quote |
|
|
588
|
+
| `quote.borderColor` | 边框颜色 | Border color |
|
|
589
|
+
| `quote.backgroundColor` | 背景颜色 | Background color |
|
|
590
|
+
| `outline.label` | 大纲 | Outline |
|
|
591
|
+
| `outline.collapse` | 收起大纲 | Collapse outline |
|
|
592
|
+
| `outline.empty.description` | 对文档内容应用“标题”样式,即可自动生成大纲。 | Apply heading styles to generate an outline. |
|
|
593
|
+
| `outline.empty.tip` | 点击左下角“大纲”按钮可以随时展开或收起。 | Use the Outline button to show or hide the outline. |
|
|
594
|
+
| `status.wordCountUnit` | 个字 | characters |
|
|
595
|
+
| `status.presentation.enter` | 演示 | Present |
|
|
596
|
+
| `status.presentation.exit` | 退出演示 | Exit presentation |
|
|
597
|
+
| `status.fullscreen.enter` | 全屏 | Fullscreen |
|
|
598
|
+
| `status.fullscreen.exit` | 退出全屏 | Exit fullscreen |
|
|
599
|
+
| `countdown.selectTime` | 请选择时间 | Select time |
|
|
600
|
+
| `countdown.settingsTitle` | 倒计时设置 | Countdown settings |
|
|
601
|
+
| `formula.insertTitle` | 插入 LaTeX 公式 | Insert LaTeX formula |
|
|
602
|
+
|
|
603
|
+
### Preview Mode
|
|
604
|
+
|
|
605
|
+
Use `mode="preview"` to switch the component into a read-only preview shell. In preview mode, the toolbar is hidden, editing is disabled, and the outline can be placed on either side.
|
|
606
|
+
|
|
607
|
+
```vue
|
|
608
|
+
<RichTextEditor
|
|
609
|
+
v-model="content"
|
|
610
|
+
mode="preview"
|
|
611
|
+
outline-placement="right"
|
|
612
|
+
/>
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
On narrow screens, preview mode automatically scales the page canvas so the document stays readable on phones.
|
|
616
|
+
|
|
617
|
+
### Collaboration
|
|
618
|
+
|
|
619
|
+
The component supports both normal single-user editing and optional Yjs collaboration.
|
|
620
|
+
|
|
621
|
+
```vue
|
|
622
|
+
<script setup lang="ts">
|
|
623
|
+
import { ref } from 'vue'
|
|
624
|
+
import * as Y from 'yjs'
|
|
625
|
+
import { WebsocketProvider } from 'y-websocket'
|
|
626
|
+
import type { JSONContent } from '@tiptap/core'
|
|
627
|
+
import { RichTextEditor } from '@ap666/office-word'
|
|
628
|
+
import '@ap666/office-word/style.css'
|
|
629
|
+
|
|
630
|
+
const ydoc = new Y.Doc()
|
|
631
|
+
const provider = new WebsocketProvider('ws://localhost:1234', 'office-word-demo', ydoc)
|
|
632
|
+
const content = ref<JSONContent | null>(null)
|
|
633
|
+
</script>
|
|
634
|
+
|
|
635
|
+
<template>
|
|
636
|
+
<RichTextEditor
|
|
637
|
+
v-model="content"
|
|
638
|
+
:collaboration="{
|
|
639
|
+
document: ydoc,
|
|
640
|
+
field: 'content',
|
|
641
|
+
provider,
|
|
642
|
+
user: {
|
|
643
|
+
name: 'Zhang San',
|
|
644
|
+
color: '#3b82f6',
|
|
645
|
+
},
|
|
646
|
+
}"
|
|
647
|
+
/>
|
|
648
|
+
</template>
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
In collaboration mode, the shared Yjs fragment becomes the source of truth. The component still emits JSON updates, but external `modelValue` changes are not pushed back into the editor. If you want remote cursors, install `y-websocket` in the host project and pass `provider` plus `user`.
|
|
652
|
+
|
|
653
|
+
Do not use `modelValue` as the initial seed source for a collaborative room. Seed the Yjs document before mounting the editor if the room needs default content.
|
|
654
|
+
|
|
655
|
+
### Bundled Yjs Server Example
|
|
656
|
+
|
|
657
|
+
The published package includes a minimal Yjs WebSocket server example under `yjs/`.
|
|
658
|
+
|
|
659
|
+
```bash
|
|
660
|
+
cd node_modules/@ap666/office-word/yjs
|
|
661
|
+
npm install
|
|
662
|
+
npm run start
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
The default address is `ws://0.0.0.0:1234`. Use the same WebSocket address and room name on every client:
|
|
666
|
+
|
|
667
|
+
```ts
|
|
668
|
+
import * as Y from 'yjs'
|
|
669
|
+
import { WebsocketProvider } from 'y-websocket'
|
|
670
|
+
|
|
671
|
+
const ydoc = new Y.Doc()
|
|
672
|
+
const provider = new WebsocketProvider('ws://127.0.0.1:1234', 'office-word-demo', ydoc)
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
### Instance API
|
|
676
|
+
|
|
677
|
+
Use a Vue `ref` to access the editor instance methods:
|
|
678
|
+
|
|
679
|
+
```vue
|
|
680
|
+
<script setup lang="ts">
|
|
681
|
+
import { ref } from 'vue'
|
|
682
|
+
import { RichTextEditor } from '@ap666/office-word'
|
|
683
|
+
import type { RichTextEditorInstance } from '@ap666/office-word'
|
|
684
|
+
|
|
685
|
+
const editorRef = ref<RichTextEditorInstance | null>(null)
|
|
686
|
+
</script>
|
|
687
|
+
|
|
688
|
+
<template>
|
|
689
|
+
<RichTextEditor ref="editorRef" />
|
|
690
|
+
</template>
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
Available methods:
|
|
694
|
+
|
|
695
|
+
| Method | Return | Description |
|
|
696
|
+
| --- | --- | --- |
|
|
697
|
+
| `exportPdf()` | `Promise<Blob \| null>` | Exports a PDF. |
|
|
698
|
+
| `exportImage(options?)` | `Promise<Blob \| null>` | Exports an image as PNG or JPEG. |
|
|
699
|
+
| `exportHtml()` | `string \| null` | Exports an HTML string. |
|
|
700
|
+
| `insertImage(payload)` | `boolean` | Inserts 1 to 4 images. |
|
|
701
|
+
| `insertVideo(payload)` | `boolean` | Inserts a video block. |
|
|
702
|
+
| `insertFile(payload)` | `boolean` | Inserts a link/file preview block. |
|
|
703
|
+
| `insertLocalFile(payload)` | `boolean` | Inserts a local file card. |
|
|
704
|
+
| `openLocalFilePicker()` | `void` | Opens the local file picker. |
|
|
705
|
+
| `focus()` | `void` | Focuses the editor. |
|
|
706
|
+
| `getJSON()` | `JSONContent \| null` | Returns the current JSON document. |
|
|
707
|
+
|
|
708
|
+
### Upload And Insert
|
|
276
709
|
|
|
277
710
|
Recommended integration flow:
|
|
278
711
|
|
|
@@ -280,7 +713,7 @@ Recommended integration flow:
|
|
|
280
713
|
2. Wait for the API to return the final accessible URL.
|
|
281
714
|
3. Call the editor instance method to insert the returned content.
|
|
282
715
|
|
|
283
|
-
|
|
716
|
+
Insert image:
|
|
284
717
|
|
|
285
718
|
```ts
|
|
286
719
|
async function uploadImage(file: File) {
|
|
@@ -295,7 +728,7 @@ async function uploadImage(file: File) {
|
|
|
295
728
|
}
|
|
296
729
|
```
|
|
297
730
|
|
|
298
|
-
You can
|
|
731
|
+
You can insert up to 4 images in one call:
|
|
299
732
|
|
|
300
733
|
```ts
|
|
301
734
|
editorRef.value?.insertImage([
|
|
@@ -304,7 +737,7 @@ editorRef.value?.insertImage([
|
|
|
304
737
|
])
|
|
305
738
|
```
|
|
306
739
|
|
|
307
|
-
|
|
740
|
+
Insert video:
|
|
308
741
|
|
|
309
742
|
```ts
|
|
310
743
|
async function uploadVideo(file: File) {
|
|
@@ -319,7 +752,7 @@ async function uploadVideo(file: File) {
|
|
|
319
752
|
}
|
|
320
753
|
```
|
|
321
754
|
|
|
322
|
-
|
|
755
|
+
Insert file link or preview:
|
|
323
756
|
|
|
324
757
|
```ts
|
|
325
758
|
async function uploadFile(file: File) {
|
|
@@ -333,24 +766,72 @@ async function uploadFile(file: File) {
|
|
|
333
766
|
}
|
|
334
767
|
```
|
|
335
768
|
|
|
336
|
-
|
|
769
|
+
Insert a local file card:
|
|
770
|
+
|
|
771
|
+
```ts
|
|
772
|
+
editorRef.value?.insertLocalFile({
|
|
773
|
+
url: 'https://cdn.example.com/files/demo.txt',
|
|
774
|
+
name: 'demo.txt',
|
|
775
|
+
size: 2048,
|
|
776
|
+
mimeType: 'text/plain',
|
|
777
|
+
})
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
### Export Example
|
|
781
|
+
|
|
782
|
+
```ts
|
|
783
|
+
async function handleExportPdf() {
|
|
784
|
+
const blob = await editorRef.value?.exportPdf()
|
|
785
|
+
if (!blob) return
|
|
786
|
+
|
|
787
|
+
const url = URL.createObjectURL(blob)
|
|
788
|
+
const link = document.createElement('a')
|
|
789
|
+
link.href = url
|
|
790
|
+
link.download = 'demo.pdf'
|
|
791
|
+
link.click()
|
|
792
|
+
URL.revokeObjectURL(url)
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
async function handleExportImage() {
|
|
796
|
+
const blob = await editorRef.value?.exportImage({
|
|
797
|
+
type: 'image/png',
|
|
798
|
+
})
|
|
799
|
+
if (!blob) return
|
|
800
|
+
|
|
801
|
+
const url = URL.createObjectURL(blob)
|
|
802
|
+
window.open(url, '_blank')
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function handleExportHtml() {
|
|
806
|
+
const html = editorRef.value?.exportHtml()
|
|
807
|
+
if (!html) return
|
|
808
|
+
|
|
809
|
+
console.log(html)
|
|
810
|
+
}
|
|
811
|
+
```
|
|
337
812
|
|
|
338
|
-
|
|
813
|
+
### Type Imports
|
|
339
814
|
|
|
340
815
|
```ts
|
|
341
816
|
import type {
|
|
342
817
|
RichTextEditorCollaborationProvider,
|
|
343
818
|
RichTextEditorCollaborationUser,
|
|
344
819
|
RichTextEditorCollaborationOptions,
|
|
820
|
+
RichTextEditorExportItemKey,
|
|
345
821
|
RichTextEditorFilePayload,
|
|
346
822
|
RichTextEditorImageExportOptions,
|
|
347
823
|
RichTextEditorImagePayload,
|
|
824
|
+
RichTextEditorInsertMenuItemKey,
|
|
348
825
|
RichTextEditorInstance,
|
|
826
|
+
RichTextEditorLocalFilePayload,
|
|
827
|
+
RichTextEditorMessages,
|
|
828
|
+
RichTextEditorProps,
|
|
829
|
+
RichTextEditorToolbarActionKey,
|
|
349
830
|
RichTextEditorVideoPayload,
|
|
350
831
|
} from '@ap666/office-word'
|
|
351
832
|
```
|
|
352
833
|
|
|
353
|
-
|
|
834
|
+
### Package Docs
|
|
354
835
|
|
|
355
|
-
-
|
|
356
|
-
-
|
|
836
|
+
- Usage guide: `docs/usage.md`
|
|
837
|
+
- Bundled Yjs server example: `yjs/README.md`
|