neweditor 1.1.0 → 1.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
@@ -1,693 +1,693 @@
1
- # TestFox RichText Editor
2
-
3
- 基于 TipTap 的富文本编辑器,专为 TestFox 项目设计,支持完整的主题系统和亮色/暗色模式切换。
4
-
5
- ## 特性
6
-
7
- - ✅ 完整的富文本编辑功能
8
- - ✅ 支持 40+ 种编程语言语法高亮
9
- - ✅ Slash Command(斜杠命令)快捷操作
10
- - ✅ 图片粘贴上传(Ctrl+V)
11
- - ✅ 文件拖拽上传(图片、视频、音频)
12
- - ✅ 自动上传到服务器(支持自定义上传处理器)
13
- - ✅ 拖拽排序功能
14
- - ✅ 表格编辑(合并单元格、调整列宽等)
15
- - ✅ 多语言支持(中文/英文)
16
- - ✅ 主题系统(亮色/暗色模式)
17
- - ✅ 响应式设计
18
-
19
- ## 安装
20
-
21
- ```bash
22
- npm install neweditor
23
- # 或
24
- pnpm install neweditor
25
- # 或
26
- yarn add neweditor
27
- ```
28
-
29
- ## 使用
30
-
31
- ### 基础使用
32
-
33
- ```vue
34
- <template>
35
- <RichTextEditor :editor="editor" locale="zh-CN" />
36
- </template>
37
-
38
- <script setup>
39
- import { useEditor, RichTextEditor, allExtensions } from 'neweditor'
40
- import 'neweditor/dist/style.css'
41
-
42
- const editor = useEditor({
43
- extensions: allExtensions,
44
- content: '<p>Hello World!</p>',
45
- })
46
- </script>
47
- ```
48
-
49
- ### 完整示例
50
-
51
- ```vue
52
- <template>
53
- <div class="editor-wrapper">
54
- <RichTextEditor
55
- :editor="editor"
56
- locale="zh-CN"
57
- class="my-editor"
58
- />
59
- </div>
60
- </template>
61
-
62
- <script setup>
63
- import { ref } from 'vue'
64
- import { useEditor, RichTextEditor, allExtensions } from 'neweditor'
65
- import 'neweditor/dist/style.css'
66
-
67
- const content = ref('<p>初始内容</p>')
68
-
69
- const editor = useEditor({
70
- extensions: allExtensions,
71
- content: content.value,
72
- editable: true,
73
- autofocus: false,
74
- editorProps: {
75
- attributes: {
76
- 'data-placeholder': '请输入内容...',
77
- },
78
- },
79
- onUpdate: ({ editor }) => {
80
- content.value = editor.getHTML()
81
- },
82
- onFocus: ({ editor }) => {
83
- console.log('编辑器获得焦点')
84
- },
85
- onBlur: ({ editor }) => {
86
- console.log('编辑器失去焦点')
87
- },
88
- })
89
- </script>
90
-
91
- <style>
92
- .editor-wrapper {
93
- border: 1px solid #d9d9d9;
94
- border-radius: 4px;
95
- }
96
- </style>
97
- ```
98
-
99
- ### 带文件上传功能的示例
100
-
101
- ```vue
102
- <template>
103
- <div class="editor-wrapper">
104
- <RichTextEditor
105
- :editor="editor"
106
- locale="zh-CN"
107
- class="my-editor"
108
- />
109
- </div>
110
- </template>
111
-
112
- <script setup>
113
- import { ref } from 'vue'
114
- import { useEditor, RichTextEditor, allExtensions } from 'testfox-richtext-editor'
115
- import 'testfox-richtext-editor/dist/style.css'
116
-
117
- const content = ref('<p>初始内容</p>')
118
-
119
- // 自定义文件上传处理器
120
- const handleFileUpload = async (file) => {
121
- try {
122
- // 1. 验证文件大小(例如限制 100MB)
123
- const maxSize = 100 * 1024 * 1024
124
- if (file.size > maxSize) {
125
- console.error('文件大小不能超过100MB')
126
- return null
127
- }
128
-
129
- // 2. 创建 FormData
130
- const formData = new FormData()
131
- formData.append('file', file)
132
-
133
- // 3. 调用你的上传 API
134
- const response = await fetch('/api/upload', {
135
- method: 'POST',
136
- body: formData,
137
- })
138
-
139
- const result = await response.json()
140
-
141
- // 4. 返回文件 URL(成功)或 null(失败)
142
- if (result.success) {
143
- return result.fileUrl // 返回文件的访问 URL
144
- } else {
145
- console.error('上传失败:', result.message)
146
- return null
147
- }
148
- } catch (error) {
149
- console.error('上传异常:', error)
150
- return null
151
- }
152
- }
153
-
154
- // 配置扩展并传递上传处理器
155
- const configuredExtensions = allExtensions.map(ext => {
156
- // 为图片、视频、音频扩展配置上传处理器
157
- if (ext.name === 'image' || ext.name === 'video' || ext.name === 'audio') {
158
- return ext.configure({
159
- uploadHandler: handleFileUpload
160
- })
161
- }
162
- return ext
163
- })
164
-
165
- const editor = useEditor({
166
- extensions: configuredExtensions, // 使用配置好的扩展
167
- content: content.value,
168
- editable: true,
169
- onUpdate: ({ editor }) => {
170
- content.value = editor.getHTML()
171
- },
172
- })
173
- </script>
174
-
175
- <style>
176
- .editor-wrapper {
177
- border: 1px solid #d9d9d9;
178
- border-radius: 4px;
179
- }
180
- </style>
181
- ```
182
-
183
- ## API
184
-
185
- ### useEditor(options)
186
-
187
- 创建编辑器实例的 Hook。
188
-
189
- **参数:**
190
-
191
- ```typescript
192
- interface EditorOptions {
193
- extensions: Extension[] // 扩展列表
194
- content?: string // 初始内容(HTML)
195
- editable?: boolean // 是否可编辑
196
- autofocus?: boolean // 是否自动聚焦
197
- editorProps?: EditorProps // 编辑器属性
198
- onUpdate?: (props) => void // 内容更新回调
199
- onFocus?: (props) => void // 获得焦点回调
200
- onBlur?: (props) => void // 失去焦点回调
201
- onCreate?: (props) => void // 创建完成回调
202
- }
203
- ```
204
-
205
- **返回值:**
206
-
207
- 返回一个响应式的编辑器实例。
208
-
209
- ### 文件上传配置
210
-
211
- #### uploadHandler
212
-
213
- 文件上传处理器函数,用于处理图片、视频、音频的上传。
214
-
215
- **类型签名:**
216
-
217
- ```typescript
218
- type UploadHandler = (file: File) => Promise<string | null>
219
- ```
220
-
221
- **参数:**
222
- - `file`: File 对象,包含要上传的文件
223
-
224
- **返回值:**
225
- - 成功:返回文件的访问 URL(字符串)
226
- - 失败:返回 `null`
227
-
228
- **配置方式:**
229
-
230
- ```javascript
231
- // 为需要上传功能的扩展配置 uploadHandler
232
- const configuredExtensions = allExtensions.map(ext => {
233
- if (ext.name === 'image' || ext.name === 'video' || ext.name === 'audio') {
234
- return ext.configure({
235
- uploadHandler: async (file) => {
236
- // 你的上传逻辑
237
- const url = await uploadToServer(file)
238
- return url // 返回 URL 或 null
239
- }
240
- })
241
- }
242
- return ext
243
- })
244
- ```
245
-
246
- **工作流程:**
247
-
248
- 1. 用户粘贴图片或拖拽文件到编辑器
249
- 2. 编辑器创建临时预览(Blob URL)
250
- 3. 立即显示预览,用户无需等待
251
- 4. 后台调用 `uploadHandler` 上传文件
252
- 5. 上传成功后,自动替换为真实 URL
253
- 6. 上传失败则删除临时预览
254
-
255
- **示例实现:**
256
-
257
- ```javascript
258
- const handleFileUpload = async (file) => {
259
- try {
260
- // 验证文件大小
261
- const maxSize = 100 * 1024 * 1024 // 100MB
262
- if (file.size > maxSize) {
263
- console.error('文件过大')
264
- return null
265
- }
266
-
267
- // 上传到服务器
268
- const formData = new FormData()
269
- formData.append('file', file)
270
-
271
- const response = await fetch('/api/upload', {
272
- method: 'POST',
273
- body: formData,
274
- })
275
-
276
- const result = await response.json()
277
-
278
- // 返回文件 URL
279
- return result.success ? result.fileUrl : null
280
- } catch (error) {
281
- console.error('上传失败:', error)
282
- return null
283
- }
284
- }
285
- ```
286
-
287
- ### RichTextEditor 组件
288
-
289
- 编辑器 UI 组件。
290
-
291
- **Props:**
292
-
293
- | 属性 | 类型 | 默认值 | 说明 |
294
- |------|------|--------|------|
295
- | `editor` | Editor | - | 编辑器实例(必需) |
296
- | `locale` | String | `'zh-CN'` | 语言设置(zh-CN/en) |
297
-
298
- ### allExtensions
299
-
300
- 包含所有内置扩展的数组,包括:
301
-
302
- - 文本格式化(粗体、斜体、下划线、删除线等)
303
- - 标题(H1-H6)
304
- - 列表(有序、无序、任务列表)
305
- - 代码(行内代码、代码块)
306
- - 表格
307
- - 图片、视频、音频
308
- - 链接
309
- - 引用块
310
- - 分栏布局
311
- - 折叠面板
312
- - Slash Command
313
- - 拖拽排序
314
- - 搜索和替换
315
- - 等等...
316
-
317
- ## 编辑器功能
318
-
319
- ### 文本格式化
320
- - 粗体、斜体、下划线、删除线、上标、下标
321
- - 标题(H1-H6)
322
- - 文本颜色和背景色
323
- - 字体大小
324
- - 文本对齐
325
- - 清除格式
326
- - 格式刷
327
-
328
- ### 列表和引用
329
- - 无序列表
330
- - 有序列表
331
- - 任务列表
332
- - 引用块
333
- - 缩进
334
-
335
- ### 代码
336
- - 行内代码
337
- - 代码块(支持 40+ 种语言语法高亮)
338
-
339
- ### 媒体内容
340
- - 图片(支持粘贴、拖拽上传、调整大小)
341
- - 视频(支持拖拽上传)
342
- - 音频(支持拖拽上传)
343
- - iframe
344
-
345
- ### 表格
346
- - 创建表格
347
- - 添加/删除行和列
348
- - 合并/拆分单元格
349
- - 调整列宽
350
- - 表格拖拽排序
351
-
352
- ### 快捷功能
353
- - Slash Command(输入 `/` 或 `、` 触发)
354
- - 拖拽排序
355
- - 搜索和替换
356
- - 撤销/重做
357
- - 字符统计
358
-
359
- ## 快捷键
360
-
361
- | 快捷键 | 功能 |
362
- |--------|------|
363
- | `Ctrl/Cmd + B` | 粗体 |
364
- | `Ctrl/Cmd + I` | 斜体 |
365
- | `Ctrl/Cmd + U` | 下划线 |
366
- | `Ctrl/Cmd + Shift + X` | 删除线 |
367
- | `Ctrl/Cmd + E` | 行内代码 |
368
- | `Ctrl/Cmd + Z` | 撤销 |
369
- | `Ctrl/Cmd + Shift + Z` | 重做 |
370
- | `Ctrl/Cmd + K` | 插入链接 |
371
- | `Ctrl/Cmd + V` | 粘贴图片 |
372
- | `/` 或 `、` | Slash Command |
373
- | `Tab` | 增加缩进 |
374
- | `Shift + Tab` | 减少缩进 |
375
-
376
- ## 主题系统
377
-
378
- 编辑器支持亮色和暗色两种主题模式,通过 CSS 变量控制。
379
-
380
- ### 主题变量
381
-
382
- ```css
383
- /* 亮色主题 */
384
- .theme-light .testfox-rich-text-editor {
385
- --editor-primary-color: #9373ee;
386
- --editor-text-color: rgba(0, 0, 0, 0.85);
387
- --editor-background-color: #ffffff;
388
- --editor-border-color: #f0f0f0;
389
- /* ... 更多变量 */
390
- }
391
-
392
- /* 暗色主题 */
393
- .theme-dark .testfox-rich-text-editor {
394
- --editor-primary-color: #9373ee;
395
- --editor-text-color: rgba(255, 255, 255, 0.85);
396
- --editor-background-color: #1f1f1f;
397
- --editor-border-color: #303030;
398
- /* ... 更多变量 */
399
- }
400
- ```
401
-
402
- ### 自定义主题
403
-
404
- 可以通过覆盖 CSS 变量来自定义主题:
405
-
406
- ```css
407
- .my-editor {
408
- --editor-primary-color: #your-color;
409
- --editor-background-color: #your-bg;
410
- }
411
- ```
412
-
413
- ## 开发
414
-
415
- ### 安装依赖
416
-
417
- ```bash
418
- npm install
419
- ```
420
-
421
- ### 开发模式
422
-
423
- ```bash
424
- npm run dev
425
- ```
426
-
427
- ### 构建
428
-
429
- ```bash
430
- npm run build
431
- ```
432
-
433
- ### 类型检查
434
-
435
- ```bash
436
- npm run typecheck
437
- ```
438
-
439
- ## 扩展开发
440
-
441
- 如需为编辑器添加新功能,可以创建自定义扩展。详细的扩展开发指南请参考:
442
-
443
- - [扩展开发文档](./docs/extension.md)
444
- - [Slash Command 指南](./docs/slash-command-guide.md)
445
- - [Slash Command 示例](./docs/slash-command-examples.md)
446
-
447
- ## 文件上传功能
448
-
449
- ### 功能特性
450
-
451
- 编辑器支持以下文件上传方式:
452
-
453
- - ✅ **图片粘贴上传**:按 `Ctrl+V` 粘贴图片,自动上传
454
- - ✅ **图片拖拽上传**:拖拽图片文件到编辑器
455
- - ✅ **视频拖拽上传**:拖拽视频文件到编辑器
456
- - ✅ **音频拖拽上传**:拖拽音频文件到编辑器
457
- - ✅ **即时预览**:上传前立即显示预览
458
- - ✅ **自动替换**:上传成功后自动替换为真实 URL
459
- - ✅ **失败处理**:上传失败自动删除预览
460
-
461
- ### 支持的文件类型
462
-
463
- | 类型 | 扩展名 | 操作方式 |
464
- |------|--------|---------|
465
- | 图片 | jpg, jpeg, png, gif, webp, svg, bmp | 粘贴、拖拽 |
466
- | 视频 | mp4, webm, ogg | 拖拽 |
467
- | 音频 | mp3, wav, ogg | 拖拽 |
468
-
469
- ### 实现步骤
470
-
471
- #### 1. 定义上传处理器
472
-
473
- ```javascript
474
- const handleFileUpload = async (file) => {
475
- try {
476
- // 验证文件大小
477
- const maxSize = 100 * 1024 * 1024 // 100MB
478
- if (file.size > maxSize) {
479
- console.error('文件大小不能超过100MB')
480
- return null
481
- }
482
-
483
- // 创建 FormData
484
- const formData = new FormData()
485
- formData.append('file', file)
486
-
487
- // 调用上传 API
488
- const response = await fetch('/api/upload', {
489
- method: 'POST',
490
- body: formData,
491
- })
492
-
493
- const result = await response.json()
494
-
495
- // 返回文件 URL(成功)或 null(失败)
496
- if (result.success) {
497
- return result.fileUrl
498
- } else {
499
- console.error('上传失败:', result.message)
500
- return null
501
- }
502
- } catch (error) {
503
- console.error('上传异常:', error)
504
- return null
505
- }
506
- }
507
- ```
508
-
509
- #### 2. 配置扩展
510
-
511
- ```javascript
512
- import { allExtensions } from 'testfox-richtext-editor'
513
-
514
- // 为需要上传功能的扩展配置 uploadHandler
515
- const configuredExtensions = allExtensions.map(ext => {
516
- if (ext.name === 'image' || ext.name === 'video' || ext.name === 'audio') {
517
- return ext.configure({
518
- uploadHandler: handleFileUpload
519
- })
520
- }
521
- return ext
522
- })
523
- ```
524
-
525
- #### 3. 创建编辑器
526
-
527
- ```javascript
528
- const editor = useEditor({
529
- extensions: configuredExtensions, // 使用配置好的扩展
530
- content: '<p>Hello World!</p>',
531
- })
532
- ```
533
-
534
- ### 完整示例
535
-
536
- ```vue
537
- <template>
538
- <RichTextEditor :editor="editor" locale="zh-CN" />
539
- </template>
540
-
541
- <script setup>
542
- import { useEditor, RichTextEditor, allExtensions } from 'testfox-richtext-editor'
543
- import 'testfox-richtext-editor/dist/style.css'
544
-
545
- // 上传处理器
546
- const handleFileUpload = async (file) => {
547
- try {
548
- const maxSize = 100 * 1024 * 1024
549
- if (file.size > maxSize) {
550
- alert('文件大小不能超过100MB')
551
- return null
552
- }
553
-
554
- const formData = new FormData()
555
- formData.append('file', file)
556
-
557
- const response = await fetch('/api/upload', {
558
- method: 'POST',
559
- body: formData,
560
- })
561
-
562
- const result = await response.json()
563
- return result.success ? result.fileUrl : null
564
- } catch (error) {
565
- console.error('上传失败:', error)
566
- return null
567
- }
568
- }
569
-
570
- // 配置扩展
571
- const configuredExtensions = allExtensions.map(ext => {
572
- if (ext.name === 'image' || ext.name === 'video' || ext.name === 'audio') {
573
- return ext.configure({ uploadHandler: handleFileUpload })
574
- }
575
- return ext
576
- })
577
-
578
- // 创建编辑器
579
- const editor = useEditor({
580
- extensions: configuredExtensions,
581
- content: '<p>试试粘贴图片或拖拽文件到这里...</p>',
582
- })
583
- </script>
584
- ```
585
-
586
- ### 上传流程
587
-
588
- ```
589
- 1. 用户粘贴图片或拖拽文件
590
-
591
- 2. 创建临时预览(Blob URL)
592
-
593
- 3. 立即显示预览(用户无需等待)
594
-
595
- 4. 后台调用 uploadHandler 上传
596
-
597
- 5. 上传成功
598
- ├─ 替换为真实 URL
599
- └─ 释放临时 Blob URL
600
-
601
- 6. 上传失败
602
- ├─ 删除临时预览
603
- └─ 释放临时 Blob URL
604
- ```
605
-
606
- ### 注意事项
607
-
608
- 1. **返回值约定**
609
- - 成功:必须返回文件的完整访问 URL(字符串)
610
- - 失败:必须返回 `null`
611
-
612
- 2. **文件大小限制**
613
- - 建议在前端验证文件大小
614
- - 避免上传超大文件导致失败
615
-
616
- 3. **错误处理**
617
- - 捕获所有异常并返回 `null`
618
- - 提供友好的错误提示
619
-
620
- 4. **URL 格式**
621
- - 返回的 URL 必须是可访问的完整 URL
622
- - 例如:`https://example.com/uploads/image.jpg`
623
-
624
- 5. **异步处理**
625
- - uploadHandler 必须是异步函数
626
- - 使用 `async/await` 或返回 Promise
627
-
628
- ### 常见问题
629
-
630
- **Q: 粘贴后图片消失了?**
631
-
632
- A: 检查 uploadHandler 是否返回了正确的 URL。如果返回 `null`,图片会被删除。
633
-
634
- **Q: 如何禁用上传功能?**
635
-
636
- A: 不配置 uploadHandler 即可。图片会保持为 Blob URL(仅本地预览)。
637
-
638
- **Q: 支持哪些文件类型?**
639
-
640
- A: 图片(粘贴+拖拽)、视频(拖拽)、音频(拖拽)。可以通过扩展支持更多类型。
641
-
642
- **Q: 如何自定义文件大小限制?**
643
-
644
- A: 在 uploadHandler 中添加验证逻辑:
645
-
646
- ```javascript
647
- const maxSize = 50 * 1024 * 1024 // 50MB
648
- if (file.size > maxSize) {
649
- alert('文件过大')
650
- return null
651
- }
652
- ```
653
-
654
- **Q: 如何添加上传进度提示?**
655
-
656
- A: 在 uploadHandler 中使用 UI 库显示进度:
657
-
658
- ```javascript
659
- const handleFileUpload = async (file) => {
660
- // 显示加载提示
661
- showLoading('正在上传...')
662
-
663
- try {
664
- const url = await uploadToServer(file)
665
- hideLoading()
666
- return url
667
- } catch (error) {
668
- hideLoading()
669
- showError('上传失败')
670
- return null
671
- }
672
- }
673
- ```
674
-
675
- ## 浏览器兼容性
676
-
677
- - Chrome/Edge: ✅ 完全支持
678
- - Firefox: ✅ 完全支持
679
- - Safari: ✅ 完全支持
680
- - IE: ❌ 不支持
681
-
682
- ## 相关链接
683
-
684
- - [Gitee 仓库](https://gitee.com/xzq_95/testfox-richtext-editor)
685
- - [TipTap 官方文档](https://tiptap.dev/)
686
-
687
- ## 许可证
688
-
689
- GPL-3.0
690
-
691
- ## 贡献
692
-
693
- 欢迎提交 Issue 和 Pull Request!
1
+ # TestFox RichText Editor
2
+
3
+ 基于 TipTap 的富文本编辑器,专为 TestFox 项目设计,支持完整的主题系统和亮色/暗色模式切换。
4
+
5
+ ## 特性
6
+
7
+ - ✅ 完整的富文本编辑功能
8
+ - ✅ 支持 40+ 种编程语言语法高亮
9
+ - ✅ Slash Command(斜杠命令)快捷操作
10
+ - ✅ 图片粘贴上传(Ctrl+V)
11
+ - ✅ 文件拖拽上传(图片、视频、音频)
12
+ - ✅ 自动上传到服务器(支持自定义上传处理器)
13
+ - ✅ 拖拽排序功能
14
+ - ✅ 表格编辑(合并单元格、调整列宽等)
15
+ - ✅ 多语言支持(中文/英文)
16
+ - ✅ 主题系统(亮色/暗色模式)
17
+ - ✅ 响应式设计
18
+
19
+ ## 安装
20
+
21
+ ```bash
22
+ npm install neweditor
23
+ # 或
24
+ pnpm install neweditor
25
+ # 或
26
+ yarn add neweditor
27
+ ```
28
+
29
+ ## 使用
30
+
31
+ ### 基础使用
32
+
33
+ ```vue
34
+ <template>
35
+ <RichTextEditor :editor="editor" locale="zh-CN" />
36
+ </template>
37
+
38
+ <script setup>
39
+ import { useEditor, RichTextEditor, allExtensions } from 'neweditor'
40
+ import 'neweditor/dist/style.css'
41
+
42
+ const editor = useEditor({
43
+ extensions: allExtensions,
44
+ content: '<p>Hello World!</p>',
45
+ })
46
+ </script>
47
+ ```
48
+
49
+ ### 完整示例
50
+
51
+ ```vue
52
+ <template>
53
+ <div class="editor-wrapper">
54
+ <RichTextEditor
55
+ :editor="editor"
56
+ locale="zh-CN"
57
+ class="my-editor"
58
+ />
59
+ </div>
60
+ </template>
61
+
62
+ <script setup>
63
+ import { ref } from 'vue'
64
+ import { useEditor, RichTextEditor, allExtensions } from 'neweditor'
65
+ import 'neweditor/dist/style.css'
66
+
67
+ const content = ref('<p>初始内容</p>')
68
+
69
+ const editor = useEditor({
70
+ extensions: allExtensions,
71
+ content: content.value,
72
+ editable: true,
73
+ autofocus: false,
74
+ editorProps: {
75
+ attributes: {
76
+ 'data-placeholder': '请输入内容...',
77
+ },
78
+ },
79
+ onUpdate: ({ editor }) => {
80
+ content.value = editor.getHTML()
81
+ },
82
+ onFocus: ({ editor }) => {
83
+ console.log('编辑器获得焦点')
84
+ },
85
+ onBlur: ({ editor }) => {
86
+ console.log('编辑器失去焦点')
87
+ },
88
+ })
89
+ </script>
90
+
91
+ <style>
92
+ .editor-wrapper {
93
+ border: 1px solid #d9d9d9;
94
+ border-radius: 4px;
95
+ }
96
+ </style>
97
+ ```
98
+
99
+ ### 带文件上传功能的示例
100
+
101
+ ```vue
102
+ <template>
103
+ <div class="editor-wrapper">
104
+ <RichTextEditor
105
+ :editor="editor"
106
+ locale="zh-CN"
107
+ class="my-editor"
108
+ />
109
+ </div>
110
+ </template>
111
+
112
+ <script setup>
113
+ import { ref } from 'vue'
114
+ import { useEditor, RichTextEditor, allExtensions } from 'testfox-richtext-editor'
115
+ import 'testfox-richtext-editor/dist/style.css'
116
+
117
+ const content = ref('<p>初始内容</p>')
118
+
119
+ // 自定义文件上传处理器
120
+ const handleFileUpload = async (file) => {
121
+ try {
122
+ // 1. 验证文件大小(例如限制 100MB)
123
+ const maxSize = 100 * 1024 * 1024
124
+ if (file.size > maxSize) {
125
+ console.error('文件大小不能超过100MB')
126
+ return null
127
+ }
128
+
129
+ // 2. 创建 FormData
130
+ const formData = new FormData()
131
+ formData.append('file', file)
132
+
133
+ // 3. 调用你的上传 API
134
+ const response = await fetch('/api/upload', {
135
+ method: 'POST',
136
+ body: formData,
137
+ })
138
+
139
+ const result = await response.json()
140
+
141
+ // 4. 返回文件 URL(成功)或 null(失败)
142
+ if (result.success) {
143
+ return result.fileUrl // 返回文件的访问 URL
144
+ } else {
145
+ console.error('上传失败:', result.message)
146
+ return null
147
+ }
148
+ } catch (error) {
149
+ console.error('上传异常:', error)
150
+ return null
151
+ }
152
+ }
153
+
154
+ // 配置扩展并传递上传处理器
155
+ const configuredExtensions = allExtensions.map(ext => {
156
+ // 为图片、视频、音频扩展配置上传处理器
157
+ if (ext.name === 'image' || ext.name === 'video' || ext.name === 'audio') {
158
+ return ext.configure({
159
+ uploadHandler: handleFileUpload
160
+ })
161
+ }
162
+ return ext
163
+ })
164
+
165
+ const editor = useEditor({
166
+ extensions: configuredExtensions, // 使用配置好的扩展
167
+ content: content.value,
168
+ editable: true,
169
+ onUpdate: ({ editor }) => {
170
+ content.value = editor.getHTML()
171
+ },
172
+ })
173
+ </script>
174
+
175
+ <style>
176
+ .editor-wrapper {
177
+ border: 1px solid #d9d9d9;
178
+ border-radius: 4px;
179
+ }
180
+ </style>
181
+ ```
182
+
183
+ ## API
184
+
185
+ ### useEditor(options)
186
+
187
+ 创建编辑器实例的 Hook。
188
+
189
+ **参数:**
190
+
191
+ ```typescript
192
+ interface EditorOptions {
193
+ extensions: Extension[] // 扩展列表
194
+ content?: string // 初始内容(HTML)
195
+ editable?: boolean // 是否可编辑
196
+ autofocus?: boolean // 是否自动聚焦
197
+ editorProps?: EditorProps // 编辑器属性
198
+ onUpdate?: (props) => void // 内容更新回调
199
+ onFocus?: (props) => void // 获得焦点回调
200
+ onBlur?: (props) => void // 失去焦点回调
201
+ onCreate?: (props) => void // 创建完成回调
202
+ }
203
+ ```
204
+
205
+ **返回值:**
206
+
207
+ 返回一个响应式的编辑器实例。
208
+
209
+ ### 文件上传配置
210
+
211
+ #### uploadHandler
212
+
213
+ 文件上传处理器函数,用于处理图片、视频、音频的上传。
214
+
215
+ **类型签名:**
216
+
217
+ ```typescript
218
+ type UploadHandler = (file: File) => Promise<string | null>
219
+ ```
220
+
221
+ **参数:**
222
+ - `file`: File 对象,包含要上传的文件
223
+
224
+ **返回值:**
225
+ - 成功:返回文件的访问 URL(字符串)
226
+ - 失败:返回 `null`
227
+
228
+ **配置方式:**
229
+
230
+ ```javascript
231
+ // 为需要上传功能的扩展配置 uploadHandler
232
+ const configuredExtensions = allExtensions.map(ext => {
233
+ if (ext.name === 'image' || ext.name === 'video' || ext.name === 'audio') {
234
+ return ext.configure({
235
+ uploadHandler: async (file) => {
236
+ // 你的上传逻辑
237
+ const url = await uploadToServer(file)
238
+ return url // 返回 URL 或 null
239
+ }
240
+ })
241
+ }
242
+ return ext
243
+ })
244
+ ```
245
+
246
+ **工作流程:**
247
+
248
+ 1. 用户粘贴图片或拖拽文件到编辑器
249
+ 2. 编辑器创建临时预览(Blob URL)
250
+ 3. 立即显示预览,用户无需等待
251
+ 4. 后台调用 `uploadHandler` 上传文件
252
+ 5. 上传成功后,自动替换为真实 URL
253
+ 6. 上传失败则删除临时预览
254
+
255
+ **示例实现:**
256
+
257
+ ```javascript
258
+ const handleFileUpload = async (file) => {
259
+ try {
260
+ // 验证文件大小
261
+ const maxSize = 100 * 1024 * 1024 // 100MB
262
+ if (file.size > maxSize) {
263
+ console.error('文件过大')
264
+ return null
265
+ }
266
+
267
+ // 上传到服务器
268
+ const formData = new FormData()
269
+ formData.append('file', file)
270
+
271
+ const response = await fetch('/api/upload', {
272
+ method: 'POST',
273
+ body: formData,
274
+ })
275
+
276
+ const result = await response.json()
277
+
278
+ // 返回文件 URL
279
+ return result.success ? result.fileUrl : null
280
+ } catch (error) {
281
+ console.error('上传失败:', error)
282
+ return null
283
+ }
284
+ }
285
+ ```
286
+
287
+ ### RichTextEditor 组件
288
+
289
+ 编辑器 UI 组件。
290
+
291
+ **Props:**
292
+
293
+ | 属性 | 类型 | 默认值 | 说明 |
294
+ |------|------|--------|------|
295
+ | `editor` | Editor | - | 编辑器实例(必需) |
296
+ | `locale` | String | `'zh-CN'` | 语言设置(zh-CN/en) |
297
+
298
+ ### allExtensions
299
+
300
+ 包含所有内置扩展的数组,包括:
301
+
302
+ - 文本格式化(粗体、斜体、下划线、删除线等)
303
+ - 标题(H1-H6)
304
+ - 列表(有序、无序、任务列表)
305
+ - 代码(行内代码、代码块)
306
+ - 表格
307
+ - 图片、视频、音频
308
+ - 链接
309
+ - 引用块
310
+ - 分栏布局
311
+ - 折叠面板
312
+ - Slash Command
313
+ - 拖拽排序
314
+ - 搜索和替换
315
+ - 等等...
316
+
317
+ ## 编辑器功能
318
+
319
+ ### 文本格式化
320
+ - 粗体、斜体、下划线、删除线、上标、下标
321
+ - 标题(H1-H6)
322
+ - 文本颜色和背景色
323
+ - 字体大小
324
+ - 文本对齐
325
+ - 清除格式
326
+ - 格式刷
327
+
328
+ ### 列表和引用
329
+ - 无序列表
330
+ - 有序列表
331
+ - 任务列表
332
+ - 引用块
333
+ - 缩进
334
+
335
+ ### 代码
336
+ - 行内代码
337
+ - 代码块(支持 40+ 种语言语法高亮)
338
+
339
+ ### 媒体内容
340
+ - 图片(支持粘贴、拖拽上传、调整大小)
341
+ - 视频(支持拖拽上传)
342
+ - 音频(支持拖拽上传)
343
+ - iframe
344
+
345
+ ### 表格
346
+ - 创建表格
347
+ - 添加/删除行和列
348
+ - 合并/拆分单元格
349
+ - 调整列宽
350
+ - 表格拖拽排序
351
+
352
+ ### 快捷功能
353
+ - Slash Command(输入 `/` 或 `、` 触发)
354
+ - 拖拽排序
355
+ - 搜索和替换
356
+ - 撤销/重做
357
+ - 字符统计
358
+
359
+ ## 快捷键
360
+
361
+ | 快捷键 | 功能 |
362
+ |--------|------|
363
+ | `Ctrl/Cmd + B` | 粗体 |
364
+ | `Ctrl/Cmd + I` | 斜体 |
365
+ | `Ctrl/Cmd + U` | 下划线 |
366
+ | `Ctrl/Cmd + Shift + X` | 删除线 |
367
+ | `Ctrl/Cmd + E` | 行内代码 |
368
+ | `Ctrl/Cmd + Z` | 撤销 |
369
+ | `Ctrl/Cmd + Shift + Z` | 重做 |
370
+ | `Ctrl/Cmd + K` | 插入链接 |
371
+ | `Ctrl/Cmd + V` | 粘贴图片 |
372
+ | `/` 或 `、` | Slash Command |
373
+ | `Tab` | 增加缩进 |
374
+ | `Shift + Tab` | 减少缩进 |
375
+
376
+ ## 主题系统
377
+
378
+ 编辑器支持亮色和暗色两种主题模式,通过 CSS 变量控制。
379
+
380
+ ### 主题变量
381
+
382
+ ```css
383
+ /* 亮色主题 */
384
+ .theme-light .testfox-rich-text-editor {
385
+ --editor-primary-color: #9373ee;
386
+ --editor-text-color: rgba(0, 0, 0, 0.85);
387
+ --editor-background-color: #ffffff;
388
+ --editor-border-color: #f0f0f0;
389
+ /* ... 更多变量 */
390
+ }
391
+
392
+ /* 暗色主题 */
393
+ .theme-dark .testfox-rich-text-editor {
394
+ --editor-primary-color: #9373ee;
395
+ --editor-text-color: rgba(255, 255, 255, 0.85);
396
+ --editor-background-color: #1f1f1f;
397
+ --editor-border-color: #303030;
398
+ /* ... 更多变量 */
399
+ }
400
+ ```
401
+
402
+ ### 自定义主题
403
+
404
+ 可以通过覆盖 CSS 变量来自定义主题:
405
+
406
+ ```css
407
+ .my-editor {
408
+ --editor-primary-color: #your-color;
409
+ --editor-background-color: #your-bg;
410
+ }
411
+ ```
412
+
413
+ ## 开发
414
+
415
+ ### 安装依赖
416
+
417
+ ```bash
418
+ npm install
419
+ ```
420
+
421
+ ### 开发模式
422
+
423
+ ```bash
424
+ npm run dev
425
+ ```
426
+
427
+ ### 构建
428
+
429
+ ```bash
430
+ npm run build
431
+ ```
432
+
433
+ ### 类型检查
434
+
435
+ ```bash
436
+ npm run typecheck
437
+ ```
438
+
439
+ ## 扩展开发
440
+
441
+ 如需为编辑器添加新功能,可以创建自定义扩展。详细的扩展开发指南请参考:
442
+
443
+ - [扩展开发文档](./docs/extension.md)
444
+ - [Slash Command 指南](./docs/slash-command-guide.md)
445
+ - [Slash Command 示例](./docs/slash-command-examples.md)
446
+
447
+ ## 文件上传功能
448
+
449
+ ### 功能特性
450
+
451
+ 编辑器支持以下文件上传方式:
452
+
453
+ - ✅ **图片粘贴上传**:按 `Ctrl+V` 粘贴图片,自动上传
454
+ - ✅ **图片拖拽上传**:拖拽图片文件到编辑器
455
+ - ✅ **视频拖拽上传**:拖拽视频文件到编辑器
456
+ - ✅ **音频拖拽上传**:拖拽音频文件到编辑器
457
+ - ✅ **即时预览**:上传前立即显示预览
458
+ - ✅ **自动替换**:上传成功后自动替换为真实 URL
459
+ - ✅ **失败处理**:上传失败自动删除预览
460
+
461
+ ### 支持的文件类型
462
+
463
+ | 类型 | 扩展名 | 操作方式 |
464
+ |------|--------|---------|
465
+ | 图片 | jpg, jpeg, png, gif, webp, svg, bmp | 粘贴、拖拽 |
466
+ | 视频 | mp4, webm, ogg | 拖拽 |
467
+ | 音频 | mp3, wav, ogg | 拖拽 |
468
+
469
+ ### 实现步骤
470
+
471
+ #### 1. 定义上传处理器
472
+
473
+ ```javascript
474
+ const handleFileUpload = async (file) => {
475
+ try {
476
+ // 验证文件大小
477
+ const maxSize = 100 * 1024 * 1024 // 100MB
478
+ if (file.size > maxSize) {
479
+ console.error('文件大小不能超过100MB')
480
+ return null
481
+ }
482
+
483
+ // 创建 FormData
484
+ const formData = new FormData()
485
+ formData.append('file', file)
486
+
487
+ // 调用上传 API
488
+ const response = await fetch('/api/upload', {
489
+ method: 'POST',
490
+ body: formData,
491
+ })
492
+
493
+ const result = await response.json()
494
+
495
+ // 返回文件 URL(成功)或 null(失败)
496
+ if (result.success) {
497
+ return result.fileUrl
498
+ } else {
499
+ console.error('上传失败:', result.message)
500
+ return null
501
+ }
502
+ } catch (error) {
503
+ console.error('上传异常:', error)
504
+ return null
505
+ }
506
+ }
507
+ ```
508
+
509
+ #### 2. 配置扩展
510
+
511
+ ```javascript
512
+ import { allExtensions } from 'testfox-richtext-editor'
513
+
514
+ // 为需要上传功能的扩展配置 uploadHandler
515
+ const configuredExtensions = allExtensions.map(ext => {
516
+ if (ext.name === 'image' || ext.name === 'video' || ext.name === 'audio') {
517
+ return ext.configure({
518
+ uploadHandler: handleFileUpload
519
+ })
520
+ }
521
+ return ext
522
+ })
523
+ ```
524
+
525
+ #### 3. 创建编辑器
526
+
527
+ ```javascript
528
+ const editor = useEditor({
529
+ extensions: configuredExtensions, // 使用配置好的扩展
530
+ content: '<p>Hello World!</p>',
531
+ })
532
+ ```
533
+
534
+ ### 完整示例
535
+
536
+ ```vue
537
+ <template>
538
+ <RichTextEditor :editor="editor" locale="zh-CN" />
539
+ </template>
540
+
541
+ <script setup>
542
+ import { useEditor, RichTextEditor, allExtensions } from 'testfox-richtext-editor'
543
+ import 'testfox-richtext-editor/dist/style.css'
544
+
545
+ // 上传处理器
546
+ const handleFileUpload = async (file) => {
547
+ try {
548
+ const maxSize = 100 * 1024 * 1024
549
+ if (file.size > maxSize) {
550
+ alert('文件大小不能超过100MB')
551
+ return null
552
+ }
553
+
554
+ const formData = new FormData()
555
+ formData.append('file', file)
556
+
557
+ const response = await fetch('/api/upload', {
558
+ method: 'POST',
559
+ body: formData,
560
+ })
561
+
562
+ const result = await response.json()
563
+ return result.success ? result.fileUrl : null
564
+ } catch (error) {
565
+ console.error('上传失败:', error)
566
+ return null
567
+ }
568
+ }
569
+
570
+ // 配置扩展
571
+ const configuredExtensions = allExtensions.map(ext => {
572
+ if (ext.name === 'image' || ext.name === 'video' || ext.name === 'audio') {
573
+ return ext.configure({ uploadHandler: handleFileUpload })
574
+ }
575
+ return ext
576
+ })
577
+
578
+ // 创建编辑器
579
+ const editor = useEditor({
580
+ extensions: configuredExtensions,
581
+ content: '<p>试试粘贴图片或拖拽文件到这里...</p>',
582
+ })
583
+ </script>
584
+ ```
585
+
586
+ ### 上传流程
587
+
588
+ ```
589
+ 1. 用户粘贴图片或拖拽文件
590
+
591
+ 2. 创建临时预览(Blob URL)
592
+
593
+ 3. 立即显示预览(用户无需等待)
594
+
595
+ 4. 后台调用 uploadHandler 上传
596
+
597
+ 5. 上传成功
598
+ ├─ 替换为真实 URL
599
+ └─ 释放临时 Blob URL
600
+
601
+ 6. 上传失败
602
+ ├─ 删除临时预览
603
+ └─ 释放临时 Blob URL
604
+ ```
605
+
606
+ ### 注意事项
607
+
608
+ 1. **返回值约定**
609
+ - 成功:必须返回文件的完整访问 URL(字符串)
610
+ - 失败:必须返回 `null`
611
+
612
+ 2. **文件大小限制**
613
+ - 建议在前端验证文件大小
614
+ - 避免上传超大文件导致失败
615
+
616
+ 3. **错误处理**
617
+ - 捕获所有异常并返回 `null`
618
+ - 提供友好的错误提示
619
+
620
+ 4. **URL 格式**
621
+ - 返回的 URL 必须是可访问的完整 URL
622
+ - 例如:`https://example.com/uploads/image.jpg`
623
+
624
+ 5. **异步处理**
625
+ - uploadHandler 必须是异步函数
626
+ - 使用 `async/await` 或返回 Promise
627
+
628
+ ### 常见问题
629
+
630
+ **Q: 粘贴后图片消失了?**
631
+
632
+ A: 检查 uploadHandler 是否返回了正确的 URL。如果返回 `null`,图片会被删除。
633
+
634
+ **Q: 如何禁用上传功能?**
635
+
636
+ A: 不配置 uploadHandler 即可。图片会保持为 Blob URL(仅本地预览)。
637
+
638
+ **Q: 支持哪些文件类型?**
639
+
640
+ A: 图片(粘贴+拖拽)、视频(拖拽)、音频(拖拽)。可以通过扩展支持更多类型。
641
+
642
+ **Q: 如何自定义文件大小限制?**
643
+
644
+ A: 在 uploadHandler 中添加验证逻辑:
645
+
646
+ ```javascript
647
+ const maxSize = 50 * 1024 * 1024 // 50MB
648
+ if (file.size > maxSize) {
649
+ alert('文件过大')
650
+ return null
651
+ }
652
+ ```
653
+
654
+ **Q: 如何添加上传进度提示?**
655
+
656
+ A: 在 uploadHandler 中使用 UI 库显示进度:
657
+
658
+ ```javascript
659
+ const handleFileUpload = async (file) => {
660
+ // 显示加载提示
661
+ showLoading('正在上传...')
662
+
663
+ try {
664
+ const url = await uploadToServer(file)
665
+ hideLoading()
666
+ return url
667
+ } catch (error) {
668
+ hideLoading()
669
+ showError('上传失败')
670
+ return null
671
+ }
672
+ }
673
+ ```
674
+
675
+ ## 浏览器兼容性
676
+
677
+ - Chrome/Edge: ✅ 完全支持
678
+ - Firefox: ✅ 完全支持
679
+ - Safari: ✅ 完全支持
680
+ - IE: ❌ 不支持
681
+
682
+ ## 相关链接
683
+
684
+ - [Gitee 仓库](https://gitee.com/xzq_95/testfox-richtext-editor)
685
+ - [TipTap 官方文档](https://tiptap.dev/)
686
+
687
+ ## 许可证
688
+
689
+ GPL-3.0
690
+
691
+ ## 贡献
692
+
693
+ 欢迎提交 Issue 和 Pull Request!