neweditor 1.0.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
@@ -7,7 +7,9 @@
7
7
  - ✅ 完整的富文本编辑功能
8
8
  - ✅ 支持 40+ 种编程语言语法高亮
9
9
  - ✅ Slash Command(斜杠命令)快捷操作
10
- - ✅ 图片粘贴功能(Ctrl+V)
10
+ - ✅ 图片粘贴上传(Ctrl+V)
11
+ - ✅ 文件拖拽上传(图片、视频、音频)
12
+ - ✅ 自动上传到服务器(支持自定义上传处理器)
11
13
  - ✅ 拖拽排序功能
12
14
  - ✅ 表格编辑(合并单元格、调整列宽等)
13
15
  - ✅ 多语言支持(中文/英文)
@@ -17,11 +19,11 @@
17
19
  ## 安装
18
20
 
19
21
  ```bash
20
- npm install testfox-richtext-editor
22
+ npm install neweditor
21
23
  # 或
22
- pnpm install testfox-richtext-editor
24
+ pnpm install neweditor
23
25
  # 或
24
- yarn add testfox-richtext-editor
26
+ yarn add neweditor
25
27
  ```
26
28
 
27
29
  ## 使用
@@ -34,8 +36,8 @@ yarn add testfox-richtext-editor
34
36
  </template>
35
37
 
36
38
  <script setup>
37
- import { useEditor, RichTextEditor, allExtensions } from 'testfox-richtext-editor'
38
- import 'testfox-richtext-editor/dist/style.css'
39
+ import { useEditor, RichTextEditor, allExtensions } from 'neweditor'
40
+ import 'neweditor/dist/style.css'
39
41
 
40
42
  const editor = useEditor({
41
43
  extensions: allExtensions,
@@ -59,8 +61,8 @@ const editor = useEditor({
59
61
 
60
62
  <script setup>
61
63
  import { ref } from 'vue'
62
- import { useEditor, RichTextEditor, allExtensions } from 'testfox-richtext-editor'
63
- import 'testfox-richtext-editor/dist/style.css'
64
+ import { useEditor, RichTextEditor, allExtensions } from 'neweditor'
65
+ import 'neweditor/dist/style.css'
64
66
 
65
67
  const content = ref('<p>初始内容</p>')
66
68
 
@@ -94,6 +96,90 @@ const editor = useEditor({
94
96
  </style>
95
97
  ```
96
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
+
97
183
  ## API
98
184
 
99
185
  ### useEditor(options)
@@ -120,6 +206,84 @@ interface EditorOptions {
120
206
 
121
207
  返回一个响应式的编辑器实例。
122
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
+
123
287
  ### RichTextEditor 组件
124
288
 
125
289
  编辑器 UI 组件。
@@ -173,9 +337,9 @@ interface EditorOptions {
173
337
  - 代码块(支持 40+ 种语言语法高亮)
174
338
 
175
339
  ### 媒体内容
176
- - 图片(支持粘贴、拖拽调整大小)
177
- - 视频
178
- - 音频
340
+ - 图片(支持粘贴、拖拽上传、调整大小)
341
+ - 视频(支持拖拽上传)
342
+ - 音频(支持拖拽上传)
179
343
  - iframe
180
344
 
181
345
  ### 表格
@@ -280,6 +444,234 @@ npm run typecheck
280
444
  - [Slash Command 指南](./docs/slash-command-guide.md)
281
445
  - [Slash Command 示例](./docs/slash-command-examples.md)
282
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
+
283
675
  ## 浏览器兼容性
284
676
 
285
677
  - Chrome/Edge: ✅ 完全支持
@@ -1,5 +1,6 @@
1
1
  import { NodeViewProps } from '../../tiptap/vue-3';
2
2
  declare const _default: import('vue').DefineComponent<NodeViewProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<NodeViewProps> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
3
- inputRef: HTMLInputElement;
3
+ fileInputRef: HTMLInputElement;
4
+ linkInputRef: HTMLInputElement;
4
5
  }, any>;
5
6
  export default _default;
@@ -0,0 +1,17 @@
1
+ interface ImageInfo {
2
+ src: string;
3
+ alt?: string;
4
+ }
5
+ type __VLS_Props = {
6
+ visible: boolean;
7
+ images: ImageInfo[];
8
+ current: number;
9
+ };
10
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
11
+ "update:visible": (value: boolean) => any;
12
+ "update:current": (value: number) => any;
13
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
14
+ "onUpdate:visible"?: ((value: boolean) => any) | undefined;
15
+ "onUpdate:current"?: ((value: number) => any) | undefined;
16
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
17
+ export default _default;
@@ -1,5 +1,6 @@
1
1
  import { NodeViewProps } from '../../tiptap/vue-3';
2
2
  declare const _default: import('vue').DefineComponent<NodeViewProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<NodeViewProps> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
3
- inputRef: HTMLInputElement;
3
+ fileInputRef: HTMLInputElement;
4
+ linkInputRef: HTMLInputElement;
4
5
  }, any>;
5
6
  export default _default;