vue2-bbl-editor 1.3.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/CHANGELOG.md +659 -0
- package/COMPONENT_USAGE_GUIDE.md +2590 -0
- package/CONTENT_WRAPPER_GUIDE.md +385 -0
- package/EXTERNAL_HTML_RENDERING.md +266 -0
- package/EXTERNAL_INTEGRATION_GUIDE.md +833 -0
- package/INSTALLATION.md +282 -0
- package/LICENSE +21 -0
- package/PACKAGE_DOCUMENTATION.md +1386 -0
- package/QUICK_SETUP.md +99 -0
- package/README.md +1694 -0
- package/dist/demo.html +10 -0
- package/dist/vue2-bbl-editor.common.js +24486 -0
- package/dist/vue2-bbl-editor.common.js.map +1 -0
- package/dist/vue2-bbl-editor.css +1 -0
- package/dist/vue2-bbl-editor.umd.js +24497 -0
- package/dist/vue2-bbl-editor.umd.js.map +1 -0
- package/dist/vue2-bbl-editor.umd.min.js +6 -0
- package/dist/vue2-bbl-editor.umd.min.js.map +1 -0
- package/package.json +172 -0
- package/types/index.d.ts +266 -0
|
@@ -0,0 +1,833 @@
|
|
|
1
|
+
# External Project Integration Guide
|
|
2
|
+
|
|
3
|
+
This guide shows how external projects can integrate the Vue2 Premium BBL Editor upload system into their applications.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### 1. Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install your-vue2-premium-bbl-editor
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### 2. Basic Setup
|
|
14
|
+
|
|
15
|
+
```vue
|
|
16
|
+
<template>
|
|
17
|
+
<PremiumBblEditor
|
|
18
|
+
v-model="content"
|
|
19
|
+
@ready="onEditorReady"
|
|
20
|
+
:config="editorConfig"
|
|
21
|
+
/>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script>
|
|
25
|
+
import {
|
|
26
|
+
PremiumBblEditor,
|
|
27
|
+
UploadManager,
|
|
28
|
+
LocalStorageAdapter,
|
|
29
|
+
createUploadConfig,
|
|
30
|
+
integrateTipTapUpload
|
|
31
|
+
} from 'your-vue2-premium-bbl-editor'
|
|
32
|
+
|
|
33
|
+
export default {
|
|
34
|
+
components: { PremiumBblEditor },
|
|
35
|
+
data() {
|
|
36
|
+
return {
|
|
37
|
+
content: '<p>Start typing...</p>',
|
|
38
|
+
uploadManager: null,
|
|
39
|
+
editorConfig: { toolbar: { image: true } }
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
async created() {
|
|
43
|
+
const config = createUploadConfig('localStorage')
|
|
44
|
+
this.uploadManager = new UploadManager(config)
|
|
45
|
+
|
|
46
|
+
const adapter = new LocalStorageAdapter()
|
|
47
|
+
this.uploadManager.registerAdapter(adapter)
|
|
48
|
+
this.uploadManager.setDefaultAdapter('local-storage')
|
|
49
|
+
},
|
|
50
|
+
methods: {
|
|
51
|
+
onEditorReady(editor) {
|
|
52
|
+
integrateTipTapUpload(editor, this.uploadManager)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Integration Patterns
|
|
60
|
+
|
|
61
|
+
### Pattern 1: Simple Integration (Recommended)
|
|
62
|
+
|
|
63
|
+
Use the `integrateTipTapUpload` helper function for automatic integration:
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
import { integrateTipTapUpload } from 'your-vue2-premium-bbl-editor'
|
|
67
|
+
|
|
68
|
+
onEditorReady(editor) {
|
|
69
|
+
integrateTipTapUpload(editor, this.uploadManager, {
|
|
70
|
+
onStart: () => console.log('Upload started'),
|
|
71
|
+
onEnd: () => console.log('Upload finished')
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Pattern 2: Manual Integration
|
|
77
|
+
|
|
78
|
+
For more control, use the factory pattern:
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
import { createTipTapUploadAdapterFactory } from 'your-vue2-premium-bbl-editor'
|
|
82
|
+
|
|
83
|
+
onEditorReady(editor) {
|
|
84
|
+
const createUploadAdapter = createTipTapUploadAdapterFactory(this.uploadManager)
|
|
85
|
+
editor.plugins.get("FileRepository").createUploadAdapter = createUploadAdapter
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Custom Upload Adapters
|
|
90
|
+
|
|
91
|
+
Create adapters for your specific backend:
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
class MyCustomAdapter {
|
|
95
|
+
constructor(options) {
|
|
96
|
+
this.name = 'my-custom-adapter'
|
|
97
|
+
this.endpoint = options.endpoint
|
|
98
|
+
this.apiKey = options.apiKey
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async upload(file, options = {}) {
|
|
102
|
+
const formData = new FormData()
|
|
103
|
+
formData.append('file', file)
|
|
104
|
+
|
|
105
|
+
const response = await fetch(this.endpoint, {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
headers: { 'Authorization': `Bearer ${this.apiKey}` },
|
|
108
|
+
body: formData
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
throw new Error(`Upload failed: ${response.statusText}`)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const result = await response.json()
|
|
116
|
+
return {
|
|
117
|
+
url: result.url,
|
|
118
|
+
size: file.size,
|
|
119
|
+
metadata: {
|
|
120
|
+
fileName: file.name,
|
|
121
|
+
fileType: file.type
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Register and use
|
|
128
|
+
const adapter = new MyCustomAdapter({
|
|
129
|
+
endpoint: '/api/upload',
|
|
130
|
+
apiKey: 'your-api-key'
|
|
131
|
+
})
|
|
132
|
+
uploadManager.registerAdapter(adapter)
|
|
133
|
+
uploadManager.setDefaultAdapter('my-custom-adapter')
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Configuration Options
|
|
137
|
+
|
|
138
|
+
### Upload Configuration
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
const config = createUploadConfig('adapter-name', {
|
|
142
|
+
validation: {
|
|
143
|
+
maxSize: 10 * 1024 * 1024, // 10MB
|
|
144
|
+
allowedTypes: ['image/jpeg', 'image/png', 'video/mp4'],
|
|
145
|
+
customValidator: (file) => {
|
|
146
|
+
// Custom validation logic
|
|
147
|
+
return file.name.length < 100
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
retry: {
|
|
151
|
+
maxAttempts: 3,
|
|
152
|
+
baseDelay: 1000,
|
|
153
|
+
maxDelay: 5000
|
|
154
|
+
},
|
|
155
|
+
progress: {
|
|
156
|
+
throttleMs: 100 // Progress update frequency
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Editor Configuration
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
const editorConfig = {
|
|
165
|
+
toolbar: {
|
|
166
|
+
image: true,
|
|
167
|
+
video: true,
|
|
168
|
+
// Other toolbar options
|
|
169
|
+
},
|
|
170
|
+
// Other TipTap configuration options
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Event Handling
|
|
175
|
+
|
|
176
|
+
Listen to upload events for better user experience:
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
// Progress tracking
|
|
180
|
+
uploadManager.on('progress', (progress) => {
|
|
181
|
+
console.log(`Upload progress: ${progress.percentage}%`)
|
|
182
|
+
this.uploadProgress = progress.percentage
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// Upload completion
|
|
186
|
+
uploadManager.on('uploadCompleted', (event) => {
|
|
187
|
+
console.log('Upload completed:', event.result.url)
|
|
188
|
+
this.showSuccessMessage('File uploaded successfully!')
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
// Upload failure
|
|
192
|
+
uploadManager.on('uploadFailed', (event) => {
|
|
193
|
+
console.error('Upload failed:', event.error.message)
|
|
194
|
+
this.showErrorMessage(`Upload failed: ${event.error.message}`)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
// Retry attempts
|
|
198
|
+
uploadManager.on('retryAttemptStarted', (event) => {
|
|
199
|
+
console.log(`Retry attempt ${event.attempt}/${event.maxAttempts}`)
|
|
200
|
+
})
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Environment-Specific Setup
|
|
204
|
+
|
|
205
|
+
### Development Environment
|
|
206
|
+
|
|
207
|
+
```javascript
|
|
208
|
+
// Use local storage for development
|
|
209
|
+
const localAdapter = new LocalStorageAdapter({
|
|
210
|
+
generateObjectUrl: true,
|
|
211
|
+
persistToIndexedDB: false
|
|
212
|
+
})
|
|
213
|
+
uploadManager.registerAdapter(localAdapter)
|
|
214
|
+
uploadManager.setDefaultAdapter('local-storage')
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Production Environment
|
|
218
|
+
|
|
219
|
+
```javascript
|
|
220
|
+
// Use your production API
|
|
221
|
+
const productionAdapter = new CustomApiAdapter({
|
|
222
|
+
endpoint: process.env.VUE_APP_UPLOAD_ENDPOINT,
|
|
223
|
+
apiKey: process.env.VUE_APP_API_KEY
|
|
224
|
+
})
|
|
225
|
+
uploadManager.registerAdapter(productionAdapter)
|
|
226
|
+
uploadManager.setDefaultAdapter('custom-api')
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Advanced Upload System Integration
|
|
230
|
+
|
|
231
|
+
The Vue2 Premium BBL Editor includes a comprehensive upload management system with multiple adapters, retry logic, progress tracking, and error handling.
|
|
232
|
+
|
|
233
|
+
### Complete Upload System Setup
|
|
234
|
+
|
|
235
|
+
```vue
|
|
236
|
+
<template>
|
|
237
|
+
<div class="upload-integration-demo">
|
|
238
|
+
<PremiumBblEditor
|
|
239
|
+
v-model="content"
|
|
240
|
+
@ready="onEditorReady"
|
|
241
|
+
/>
|
|
242
|
+
|
|
243
|
+
<!-- Upload Progress -->
|
|
244
|
+
<div v-if="uploadStatus.active" class="upload-progress">
|
|
245
|
+
<div class="progress-bar">
|
|
246
|
+
<div
|
|
247
|
+
class="progress-fill"
|
|
248
|
+
:style="{ width: uploadStatus.progress + '%' }"
|
|
249
|
+
></div>
|
|
250
|
+
</div>
|
|
251
|
+
<p>{{ uploadStatus.message }} - {{ uploadStatus.progress }}%</p>
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
<!-- Upload Statistics -->
|
|
255
|
+
<div class="upload-stats" v-if="uploadStats">
|
|
256
|
+
<h4>Upload Statistics</h4>
|
|
257
|
+
<p>Active Uploads: {{ uploadStats.activeUploads }}</p>
|
|
258
|
+
<p>Completed: {{ uploadStats.progressStats.completed }}</p>
|
|
259
|
+
<p>Failed: {{ uploadStats.progressStats.failed }}</p>
|
|
260
|
+
<p>Current Adapter: {{ uploadStats.defaultAdapter }}</p>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
</template>
|
|
264
|
+
|
|
265
|
+
<script>
|
|
266
|
+
import {
|
|
267
|
+
PremiumBblEditor,
|
|
268
|
+
UploadManager,
|
|
269
|
+
LocalStorageAdapter,
|
|
270
|
+
integrateTipTapUpload,
|
|
271
|
+
createUploadConfig
|
|
272
|
+
} from 'your-vue2-premium-bbl-editor'
|
|
273
|
+
|
|
274
|
+
// Custom API Adapter for your backend
|
|
275
|
+
class CustomApiAdapter {
|
|
276
|
+
constructor(config = {}) {
|
|
277
|
+
this.name = 'custom-api'
|
|
278
|
+
this.apiUrl = config.apiUrl || '/api/upload'
|
|
279
|
+
this.headers = config.headers || {}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
getName() {
|
|
283
|
+
return this.name
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async upload(file, options) {
|
|
287
|
+
const formData = new FormData()
|
|
288
|
+
formData.append('file', file)
|
|
289
|
+
formData.append('uploadId', options.uploadId)
|
|
290
|
+
|
|
291
|
+
const response = await fetch(this.apiUrl, {
|
|
292
|
+
method: 'POST',
|
|
293
|
+
headers: this.headers,
|
|
294
|
+
body: formData
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
if (!response.ok) {
|
|
298
|
+
throw new Error(`Upload failed: ${response.statusText}`)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const data = await response.json()
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
success: true,
|
|
305
|
+
url: data.url,
|
|
306
|
+
publicUrl: data.publicUrl || data.url,
|
|
307
|
+
uploadId: options.uploadId,
|
|
308
|
+
size: file.size,
|
|
309
|
+
adapter: this.name,
|
|
310
|
+
metadata: {
|
|
311
|
+
fileName: file.name,
|
|
312
|
+
fileType: file.type,
|
|
313
|
+
uploadedAt: new Date().toISOString()
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
supportsStrategy(strategy) {
|
|
319
|
+
return strategy === 'direct'
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
validateConfig() {
|
|
323
|
+
return true
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export default {
|
|
328
|
+
components: {
|
|
329
|
+
PremiumBblEditor
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
data() {
|
|
333
|
+
return {
|
|
334
|
+
content: '<p>Advanced upload system integration example</p>',
|
|
335
|
+
uploadManager: null,
|
|
336
|
+
uploadStatus: {
|
|
337
|
+
active: false,
|
|
338
|
+
progress: 0,
|
|
339
|
+
message: ''
|
|
340
|
+
},
|
|
341
|
+
uploadStats: null
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
async created() {
|
|
346
|
+
await this.initializeUploadSystem()
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
methods: {
|
|
350
|
+
async initializeUploadSystem() {
|
|
351
|
+
try {
|
|
352
|
+
// Create comprehensive upload configuration
|
|
353
|
+
const config = createUploadConfig('localStorage', {
|
|
354
|
+
validation: {
|
|
355
|
+
maxSize: 10 * 1024 * 1024, // 10MB
|
|
356
|
+
allowedTypes: [
|
|
357
|
+
'image/jpeg', 'image/png', 'image/gif', 'image/webp',
|
|
358
|
+
'video/mp4', 'video/webm', 'video/ogg'
|
|
359
|
+
],
|
|
360
|
+
customValidator: (file) => {
|
|
361
|
+
// Custom validation logic
|
|
362
|
+
if (file.name.length > 100) {
|
|
363
|
+
throw new Error('Filename too long')
|
|
364
|
+
}
|
|
365
|
+
return true
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
retry: {
|
|
369
|
+
maxAttempts: 3,
|
|
370
|
+
baseDelay: 1000,
|
|
371
|
+
maxDelay: 5000,
|
|
372
|
+
backoffFactor: 2
|
|
373
|
+
},
|
|
374
|
+
progress: {
|
|
375
|
+
throttleMs: 100 // Progress update frequency
|
|
376
|
+
},
|
|
377
|
+
debug: process.env.NODE_ENV === 'development'
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
// Initialize upload manager
|
|
381
|
+
this.uploadManager = new UploadManager(config)
|
|
382
|
+
|
|
383
|
+
// Register multiple adapters
|
|
384
|
+
await this.setupAdapters()
|
|
385
|
+
|
|
386
|
+
// Set up comprehensive event listeners
|
|
387
|
+
this.setupEventListeners()
|
|
388
|
+
|
|
389
|
+
// Update initial stats
|
|
390
|
+
this.updateStats()
|
|
391
|
+
|
|
392
|
+
console.log('✅ Upload system initialized successfully')
|
|
393
|
+
|
|
394
|
+
} catch (error) {
|
|
395
|
+
console.error('❌ Upload system initialization failed:', error)
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
|
|
399
|
+
async setupAdapters() {
|
|
400
|
+
// Local storage adapter for development
|
|
401
|
+
const localAdapter = new LocalStorageAdapter({
|
|
402
|
+
generateObjectUrl: true,
|
|
403
|
+
persistToIndexedDB: false
|
|
404
|
+
})
|
|
405
|
+
this.uploadManager.registerAdapter(localAdapter)
|
|
406
|
+
|
|
407
|
+
// Custom API adapter for production
|
|
408
|
+
const apiAdapter = new CustomApiAdapter({
|
|
409
|
+
apiUrl: process.env.VUE_APP_UPLOAD_URL || '/api/upload',
|
|
410
|
+
headers: {
|
|
411
|
+
'Authorization': `Bearer ${this.getAuthToken()}`,
|
|
412
|
+
'X-Client-Version': '1.0.0'
|
|
413
|
+
}
|
|
414
|
+
})
|
|
415
|
+
this.uploadManager.registerAdapter(apiAdapter)
|
|
416
|
+
|
|
417
|
+
// Set default adapter based on environment
|
|
418
|
+
const defaultAdapter = process.env.NODE_ENV === 'production'
|
|
419
|
+
? 'custom-api'
|
|
420
|
+
: 'local-storage'
|
|
421
|
+
|
|
422
|
+
this.uploadManager.setDefaultAdapter(defaultAdapter)
|
|
423
|
+
|
|
424
|
+
console.log(`📡 Adapters registered, using: ${defaultAdapter}`)
|
|
425
|
+
},
|
|
426
|
+
|
|
427
|
+
setupEventListeners() {
|
|
428
|
+
// Upload started
|
|
429
|
+
this.uploadManager.on('uploadStarted', (event) => {
|
|
430
|
+
this.uploadStatus = {
|
|
431
|
+
active: true,
|
|
432
|
+
progress: 0,
|
|
433
|
+
message: `Starting upload: ${event.fileName}`
|
|
434
|
+
}
|
|
435
|
+
console.log('🚀 Upload started:', event)
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
// Progress tracking
|
|
439
|
+
this.uploadManager.on('progress', (progress) => {
|
|
440
|
+
this.uploadStatus.progress = Math.round(progress.percentage)
|
|
441
|
+
this.uploadStatus.message = `Uploading: ${progress.fileName}`
|
|
442
|
+
|
|
443
|
+
if (progress.speed) {
|
|
444
|
+
this.uploadStatus.message += ` (${this.formatSpeed(progress.speed)})`
|
|
445
|
+
}
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
// Upload completed
|
|
449
|
+
this.uploadManager.on('uploadCompleted', (event) => {
|
|
450
|
+
this.uploadStatus = {
|
|
451
|
+
active: false,
|
|
452
|
+
progress: 100,
|
|
453
|
+
message: 'Upload completed successfully!'
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
console.log('✅ Upload completed:', event.result.url)
|
|
457
|
+
|
|
458
|
+
// Clear status after delay
|
|
459
|
+
setTimeout(() => {
|
|
460
|
+
this.uploadStatus.active = false
|
|
461
|
+
}, 2000)
|
|
462
|
+
|
|
463
|
+
this.updateStats()
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
// Upload failed
|
|
467
|
+
this.uploadManager.on('uploadFailed', (event) => {
|
|
468
|
+
this.uploadStatus = {
|
|
469
|
+
active: false,
|
|
470
|
+
progress: 0,
|
|
471
|
+
message: `Upload failed: ${event.error.message}`
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
console.error('❌ Upload failed:', event.error)
|
|
475
|
+
|
|
476
|
+
// Show user-friendly error message
|
|
477
|
+
this.showErrorNotification(this.getUserFriendlyError(event.error))
|
|
478
|
+
|
|
479
|
+
// Clear status after delay
|
|
480
|
+
setTimeout(() => {
|
|
481
|
+
this.uploadStatus.active = false
|
|
482
|
+
}, 5000)
|
|
483
|
+
|
|
484
|
+
this.updateStats()
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
// Retry attempts
|
|
488
|
+
this.uploadManager.on('retryAttemptStarted', (event) => {
|
|
489
|
+
this.uploadStatus.message = `Retry attempt ${event.attempt}/${event.maxAttempts}`
|
|
490
|
+
console.log(`🔄 Retry attempt ${event.attempt}/${event.maxAttempts}`)
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
// Adapter changes
|
|
494
|
+
this.uploadManager.on('defaultAdapterChanged', (event) => {
|
|
495
|
+
console.log(`🔄 Switched to adapter: ${event.newDefault}`)
|
|
496
|
+
this.updateStats()
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
// Validation errors
|
|
500
|
+
this.uploadManager.on('validationFailed', (event) => {
|
|
501
|
+
console.warn('⚠️ Validation failed:', event.error.message)
|
|
502
|
+
this.showErrorNotification(event.error.message)
|
|
503
|
+
})
|
|
504
|
+
},
|
|
505
|
+
|
|
506
|
+
onEditorReady(editor) {
|
|
507
|
+
console.log('📝 Editor ready, integrating upload system...')
|
|
508
|
+
|
|
509
|
+
// Integrate upload system with TipTap
|
|
510
|
+
integrateTipTapUpload(editor, this.uploadManager, {
|
|
511
|
+
onStart: () => {
|
|
512
|
+
console.log('🎬 TipTap upload integration started')
|
|
513
|
+
},
|
|
514
|
+
onEnd: () => {
|
|
515
|
+
console.log('🎬 TipTap upload integration ended')
|
|
516
|
+
}
|
|
517
|
+
})
|
|
518
|
+
|
|
519
|
+
console.log('✅ TipTap upload integration completed')
|
|
520
|
+
},
|
|
521
|
+
|
|
522
|
+
updateStats() {
|
|
523
|
+
if (this.uploadManager) {
|
|
524
|
+
this.uploadStats = this.uploadManager.getStats()
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
|
|
528
|
+
getUserFriendlyError(error) {
|
|
529
|
+
const errorMap = {
|
|
530
|
+
'FILE_TOO_LARGE': 'File is too large. Please choose a smaller file.',
|
|
531
|
+
'INVALID_FILE_TYPE': 'File type not supported. Please choose a different file.',
|
|
532
|
+
'NETWORK_ERROR': 'Network error. Please check your connection and try again.',
|
|
533
|
+
'UPLOAD_TIMEOUT': 'Upload timed out. Please try again.',
|
|
534
|
+
'SERVER_ERROR': 'Server error. Please try again later.'
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return errorMap[error.code] || error.message || 'Upload failed. Please try again.'
|
|
538
|
+
},
|
|
539
|
+
|
|
540
|
+
showErrorNotification(message) {
|
|
541
|
+
// Implement your notification system
|
|
542
|
+
alert(message) // Replace with your notification component
|
|
543
|
+
},
|
|
544
|
+
|
|
545
|
+
formatSpeed(bytesPerSecond) {
|
|
546
|
+
const units = ['B/s', 'KB/s', 'MB/s', 'GB/s']
|
|
547
|
+
let size = bytesPerSecond
|
|
548
|
+
let unitIndex = 0
|
|
549
|
+
|
|
550
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
551
|
+
size /= 1024
|
|
552
|
+
unitIndex++
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return `${size.toFixed(1)} ${units[unitIndex]}`
|
|
556
|
+
},
|
|
557
|
+
|
|
558
|
+
getAuthToken() {
|
|
559
|
+
// Return your authentication token
|
|
560
|
+
return localStorage.getItem('authToken') || ''
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
|
|
564
|
+
beforeDestroy() {
|
|
565
|
+
if (this.uploadManager) {
|
|
566
|
+
this.uploadManager.destroy()
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
</script>
|
|
571
|
+
|
|
572
|
+
<style scoped>
|
|
573
|
+
.upload-integration-demo {
|
|
574
|
+
max-width: 1000px;
|
|
575
|
+
margin: 0 auto;
|
|
576
|
+
padding: 20px;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.upload-progress {
|
|
580
|
+
margin: 20px 0;
|
|
581
|
+
padding: 15px;
|
|
582
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
583
|
+
color: white;
|
|
584
|
+
border-radius: 8px;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
.progress-bar {
|
|
588
|
+
width: 100%;
|
|
589
|
+
height: 8px;
|
|
590
|
+
background: rgba(255, 255, 255, 0.3);
|
|
591
|
+
border-radius: 4px;
|
|
592
|
+
margin-bottom: 10px;
|
|
593
|
+
overflow: hidden;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.progress-fill {
|
|
597
|
+
height: 100%;
|
|
598
|
+
background: white;
|
|
599
|
+
border-radius: 4px;
|
|
600
|
+
transition: width 0.3s ease;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.upload-stats {
|
|
604
|
+
margin-top: 20px;
|
|
605
|
+
padding: 15px;
|
|
606
|
+
background: #f8f9fa;
|
|
607
|
+
border: 1px solid #dee2e6;
|
|
608
|
+
border-radius: 6px;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
.upload-stats h4 {
|
|
612
|
+
margin-top: 0;
|
|
613
|
+
color: #495057;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.upload-stats p {
|
|
617
|
+
margin: 5px 0;
|
|
618
|
+
font-size: 14px;
|
|
619
|
+
color: #6c757d;
|
|
620
|
+
}
|
|
621
|
+
</style>
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
### Upload Configuration Presets
|
|
625
|
+
|
|
626
|
+
The upload system includes pre-configured setups for common scenarios:
|
|
627
|
+
|
|
628
|
+
```javascript
|
|
629
|
+
// Local Storage (Development)
|
|
630
|
+
const localConfig = createUploadConfig('localStorage', {
|
|
631
|
+
validation: {
|
|
632
|
+
maxSize: 10 * 1024 * 1024, // 10MB
|
|
633
|
+
allowedTypes: ['image/jpeg', 'image/png', 'image/gif', 'video/mp4']
|
|
634
|
+
},
|
|
635
|
+
retry: {
|
|
636
|
+
maxAttempts: 2,
|
|
637
|
+
baseDelay: 500
|
|
638
|
+
}
|
|
639
|
+
})
|
|
640
|
+
|
|
641
|
+
// AWS S3 (Production)
|
|
642
|
+
const s3Config = createUploadConfig('awsS3', {
|
|
643
|
+
adapters: {
|
|
644
|
+
'aws-s3': {
|
|
645
|
+
type: 'aws-s3',
|
|
646
|
+
credentials: {
|
|
647
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
648
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
649
|
+
region: 'us-east-1',
|
|
650
|
+
bucket: 'my-uploads-bucket'
|
|
651
|
+
},
|
|
652
|
+
options: {
|
|
653
|
+
forcePathStyle: false,
|
|
654
|
+
usePresignedUrls: true
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
validation: {
|
|
659
|
+
maxSize: 50 * 1024 * 1024, // 50MB for S3
|
|
660
|
+
allowedTypes: [
|
|
661
|
+
'image/jpeg', 'image/png', 'image/gif', 'image/webp',
|
|
662
|
+
'video/mp4', 'video/webm', 'video/ogg'
|
|
663
|
+
]
|
|
664
|
+
},
|
|
665
|
+
chunking: {
|
|
666
|
+
defaultChunkSize: 5 * 1024 * 1024, // 5MB chunks
|
|
667
|
+
autoChunkThreshold: 10 * 1024 * 1024, // 10MB threshold
|
|
668
|
+
parallelUploads: 3
|
|
669
|
+
}
|
|
670
|
+
})
|
|
671
|
+
|
|
672
|
+
// Cloudinary (Media Management)
|
|
673
|
+
const cloudinaryConfig = createUploadConfig('cloudinary', {
|
|
674
|
+
adapters: {
|
|
675
|
+
'cloudinary': {
|
|
676
|
+
type: 'cloudinary',
|
|
677
|
+
credentials: {
|
|
678
|
+
cloudName: 'my-cloud',
|
|
679
|
+
apiKey: process.env.CLOUDINARY_API_KEY,
|
|
680
|
+
apiSecret: process.env.CLOUDINARY_API_SECRET
|
|
681
|
+
},
|
|
682
|
+
options: {
|
|
683
|
+
uploadPreset: 'my_preset',
|
|
684
|
+
folder: 'editor_uploads',
|
|
685
|
+
transformation: {
|
|
686
|
+
quality: 'auto',
|
|
687
|
+
fetch_format: 'auto'
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
},
|
|
692
|
+
validation: {
|
|
693
|
+
maxSize: 100 * 1024 * 1024 // 100MB for Cloudinary
|
|
694
|
+
}
|
|
695
|
+
})
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
### Multiple Upload Strategies
|
|
699
|
+
|
|
700
|
+
```javascript
|
|
701
|
+
// Direct upload strategy
|
|
702
|
+
const directStrategy = new DirectUploadStrategy({
|
|
703
|
+
endpoint: '/api/upload',
|
|
704
|
+
method: 'POST',
|
|
705
|
+
headers: {
|
|
706
|
+
'Authorization': 'Bearer token'
|
|
707
|
+
}
|
|
708
|
+
})
|
|
709
|
+
|
|
710
|
+
// Presigned URL strategy (for S3)
|
|
711
|
+
const presignedStrategy = new PresignedUrlStrategy({
|
|
712
|
+
getPresignedUrl: async (file) => {
|
|
713
|
+
const response = await fetch('/api/presigned-url', {
|
|
714
|
+
method: 'POST',
|
|
715
|
+
headers: { 'Content-Type': 'application/json' },
|
|
716
|
+
body: JSON.stringify({
|
|
717
|
+
fileName: file.name,
|
|
718
|
+
fileType: file.type,
|
|
719
|
+
fileSize: file.size
|
|
720
|
+
})
|
|
721
|
+
})
|
|
722
|
+
const data = await response.json()
|
|
723
|
+
return data.presignedUrl
|
|
724
|
+
}
|
|
725
|
+
})
|
|
726
|
+
|
|
727
|
+
// Register strategies with upload manager
|
|
728
|
+
uploadManager.registerStrategy('direct', directStrategy)
|
|
729
|
+
uploadManager.registerStrategy('presigned', presignedStrategy)
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
## Error Handling
|
|
733
|
+
|
|
734
|
+
Implement robust error handling:
|
|
735
|
+
|
|
736
|
+
```javascript
|
|
737
|
+
uploadManager.on('uploadFailed', (event) => {
|
|
738
|
+
const { error, uploadId, adapter } = event
|
|
739
|
+
|
|
740
|
+
// Log for debugging
|
|
741
|
+
console.error('Upload failed:', {
|
|
742
|
+
uploadId,
|
|
743
|
+
adapter,
|
|
744
|
+
error: error.message,
|
|
745
|
+
stack: error.stack
|
|
746
|
+
})
|
|
747
|
+
|
|
748
|
+
// Show user-friendly message
|
|
749
|
+
let userMessage = 'Upload failed. Please try again.'
|
|
750
|
+
|
|
751
|
+
if (error.code === 'FILE_TOO_LARGE') {
|
|
752
|
+
userMessage = 'File is too large. Please choose a smaller file.'
|
|
753
|
+
} else if (error.code === 'INVALID_FILE_TYPE') {
|
|
754
|
+
userMessage = 'File type not supported. Please choose a different file.'
|
|
755
|
+
} else if (error.code === 'NETWORK_ERROR') {
|
|
756
|
+
userMessage = 'Network error. Please check your connection and try again.'
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
this.showErrorNotification(userMessage)
|
|
760
|
+
})
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
## TypeScript Support
|
|
764
|
+
|
|
765
|
+
If using TypeScript, import types:
|
|
766
|
+
|
|
767
|
+
```typescript
|
|
768
|
+
import {
|
|
769
|
+
UploadManager,
|
|
770
|
+
UploadConfig,
|
|
771
|
+
UploadAdapter,
|
|
772
|
+
UploadResult
|
|
773
|
+
} from 'your-vue2-premium-bbl-editor'
|
|
774
|
+
|
|
775
|
+
interface CustomAdapterOptions {
|
|
776
|
+
endpoint: string
|
|
777
|
+
apiKey: string
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
class CustomAdapter implements UploadAdapter {
|
|
781
|
+
name = 'custom-adapter'
|
|
782
|
+
|
|
783
|
+
constructor(private options: CustomAdapterOptions) {}
|
|
784
|
+
|
|
785
|
+
async upload(file: File): Promise<UploadResult> {
|
|
786
|
+
// Implementation
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
## Best Practices
|
|
792
|
+
|
|
793
|
+
1. **Always handle errors gracefully** - Show user-friendly error messages
|
|
794
|
+
2. **Provide upload progress feedback** - Users expect to see progress
|
|
795
|
+
3. **Validate files before upload** - Use the built-in validation system
|
|
796
|
+
4. **Use appropriate adapters** - Local storage for dev, cloud services for production
|
|
797
|
+
5. **Clean up resources** - Call `uploadManager.destroy()` when component unmounts
|
|
798
|
+
6. **Test with different file types and sizes** - Ensure your validation works correctly
|
|
799
|
+
|
|
800
|
+
## Troubleshooting
|
|
801
|
+
|
|
802
|
+
### Common Issues
|
|
803
|
+
|
|
804
|
+
1. **Upload not working**: Check if adapter is registered and set as default
|
|
805
|
+
2. **Files not appearing in editor**: Ensure the upload result returns a valid URL
|
|
806
|
+
3. **Progress not updating**: Check if progress events are being emitted
|
|
807
|
+
4. **Memory issues**: Use appropriate file size limits and clean up resources
|
|
808
|
+
|
|
809
|
+
### Debug Mode
|
|
810
|
+
|
|
811
|
+
Enable debug logging:
|
|
812
|
+
|
|
813
|
+
```javascript
|
|
814
|
+
const config = createUploadConfig('adapter-name', {
|
|
815
|
+
debug: true // Enables detailed logging
|
|
816
|
+
})
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
## Examples
|
|
820
|
+
|
|
821
|
+
See the `examples/` directory for complete working examples:
|
|
822
|
+
|
|
823
|
+
- `examples/external-project-integration.vue` - Basic integration
|
|
824
|
+
- `examples/custom-adapter-integration.vue` - Custom adapter example
|
|
825
|
+
- `examples/tiptap-integration-example.vue` - Full-featured example
|
|
826
|
+
|
|
827
|
+
## Support
|
|
828
|
+
|
|
829
|
+
For issues and questions:
|
|
830
|
+
1. Check the troubleshooting section
|
|
831
|
+
2. Review the examples
|
|
832
|
+
3. Open an issue on GitHub
|
|
833
|
+
4. Check the API documentation
|