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.
@@ -0,0 +1,2590 @@
1
+ # Complete Component Usage Guide
2
+
3
+ This guide provides comprehensive examples of how to use all components included in the Vue2 Premium BBL Editor package.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Main Editor Components](#main-editor-components)
8
+ 2. [Toolbar Components](#toolbar-components)
9
+ 3. [Menu Components](#menu-components)
10
+ 4. [Modal Components](#modal-components)
11
+ 5. [Upload System Components](#upload-system-components)
12
+ 6. [Development Tools](#development-tools)
13
+ 7. [Composables](#composables)
14
+ 8. [Advanced Usage Patterns](#advanced-usage-patterns)
15
+
16
+ ## Main Editor Components
17
+
18
+ ### PremiumBblEditor (Composition API)
19
+
20
+ The main editor component using Vue 3 Composition API (compatible with Vue 2.6+ via @vue/composition-api).
21
+
22
+ **Import:**
23
+ ```javascript
24
+ import { PremiumBblEditor } from 'vue2-premium-bbl-editor'
25
+ ```
26
+
27
+ **Basic Usage:**
28
+ ```vue
29
+ <template>
30
+ <PremiumBblEditor
31
+ v-model="content"
32
+ placeholder="Start writing..."
33
+ @ready="handleReady"
34
+ />
35
+ </template>
36
+ ```
37
+
38
+ ### PremiumBblEditorOptionsAPI (Options API)
39
+
40
+ The same editor component using pure Vue 2 Options API (no Composition API dependency required).
41
+
42
+ **Import:**
43
+ ```javascript
44
+ import { PremiumBblEditorOptionsAPI } from 'vue2-premium-bbl-editor'
45
+ ```
46
+
47
+ **Basic Usage:**
48
+ ```vue
49
+ <template>
50
+ <PremiumBblEditorOptionsAPI
51
+ v-model="content"
52
+ placeholder="Start writing with Options API..."
53
+ @ready="handleReady"
54
+ />
55
+ </template>
56
+ ```
57
+
58
+ ### Complete Options API Example
59
+
60
+ ```vue
61
+ <template>
62
+ <PremiumBblEditorOptionsAPI
63
+ v-model="content"
64
+ :placeholder="placeholder"
65
+ :editable="editable"
66
+ :theme="theme"
67
+ :toolbar-config="toolbarConfig"
68
+ :extension-config="extensionConfig"
69
+ :upload-handler="uploadHandler"
70
+ :auto-save="autoSave"
71
+ :auto-save-interval="autoSaveInterval"
72
+ :max-height="maxHeight"
73
+ :min-height="minHeight"
74
+ :font-families="fontFamilies"
75
+ :font-sizes="fontSizes"
76
+ :line-heights="lineHeights"
77
+ :allowed-headings="allowedHeadings"
78
+ :max-file-size="maxFileSize"
79
+ :allowed-image-types="allowedImageTypes"
80
+ :allowed-video-types="allowedVideoTypes"
81
+ :show-toolbar="showToolbar"
82
+ :show-bubble-menu="showBubbleMenu"
83
+ :show-table-bubble-menu="showTableBubbleMenu"
84
+ :show-image-bubble-menu="showImageBubbleMenu"
85
+ :show-video-bubble-menu="showVideoBubbleMenu"
86
+ :editor-class="editorClass"
87
+ :toolbar-class="toolbarClass"
88
+ :content-class="contentClass"
89
+ @input="handleInput"
90
+ @update="handleUpdate"
91
+ @ready="handleReady"
92
+ @created="handleCreated"
93
+ @focus="handleFocus"
94
+ @blur="handleBlur"
95
+ @destroyed="handleDestroyed"
96
+ @auto-save="handleAutoSave"
97
+ @content-limit-exceeded="handleLimitExceeded"
98
+ @error="handleError"
99
+ />
100
+ </template>
101
+
102
+ <script>
103
+ import { PremiumBblEditorOptionsAPI } from 'vue2-premium-bbl-editor'
104
+
105
+ export default {
106
+ components: {
107
+ PremiumBblEditorOptionsAPI
108
+ },
109
+
110
+ data() {
111
+ return {
112
+ content: '<p>Start writing with Options API...</p>',
113
+ placeholder: 'Enter your content here...',
114
+ editable: true,
115
+ theme: 'default', // 'default', 'minimal', 'dark'
116
+ autoSave: true,
117
+ autoSaveInterval: 30000, // 30 seconds
118
+ maxHeight: 500,
119
+ minHeight: 200,
120
+ maxFileSize: 10 * 1024 * 1024, // 10MB
121
+ showToolbar: true,
122
+ showBubbleMenu: true,
123
+ showTableBubbleMenu: true,
124
+ showImageBubbleMenu: true,
125
+ showVideoBubbleMenu: true,
126
+
127
+ // Toolbar configuration
128
+ toolbarConfig: {
129
+ bold: true,
130
+ italic: true,
131
+ underline: true,
132
+ strike: true,
133
+ code: true,
134
+ textColor: true,
135
+ highlight: true,
136
+ fontFamily: true,
137
+ fontSize: true,
138
+ textAlign: true,
139
+ lineHeight: true,
140
+ headings: true,
141
+ lists: true,
142
+ blockquote: true,
143
+ codeBlock: true,
144
+ horizontalRule: true,
145
+ link: true,
146
+ image: true,
147
+ video: true,
148
+ table: true,
149
+ clearFormatting: true,
150
+ sourceCode: true,
151
+ undo: true,
152
+ redo: true
153
+ },
154
+
155
+ // Extension configuration
156
+ extensionConfig: {
157
+ image: {
158
+ allowResize: true,
159
+ allowAlignment: true,
160
+ allowDelete: true,
161
+ maxWidth: '100%',
162
+ quality: 0.8
163
+ },
164
+ video: {
165
+ allowResize: true,
166
+ allowAlignment: true,
167
+ allowDelete: true,
168
+ maxWidth: '100%',
169
+ autoplay: false
170
+ },
171
+ table: {
172
+ resizable: true,
173
+ allowRowControls: true,
174
+ allowColumnControls: true,
175
+ cellSelection: true
176
+ },
177
+ link: {
178
+ openOnClick: false,
179
+ autolink: true,
180
+ linkOnPaste: true
181
+ }
182
+ },
183
+
184
+ // Styling options
185
+ fontFamilies: [
186
+ 'Inter',
187
+ 'Arial',
188
+ 'Helvetica',
189
+ 'Times New Roman',
190
+ 'Georgia',
191
+ 'Courier New'
192
+ ],
193
+ fontSizes: [
194
+ '12px', '14px', '16px', '18px', '20px', '24px', '28px', '32px'
195
+ ],
196
+ lineHeights: [
197
+ '1', '1.2', '1.4', '1.6', '1.8', '2', '2.5', '3'
198
+ ],
199
+ allowedHeadings: [1, 2, 3, 4, 5, 6],
200
+ allowedImageTypes: [
201
+ 'image/jpeg',
202
+ 'image/png',
203
+ 'image/gif',
204
+ 'image/webp'
205
+ ],
206
+ allowedVideoTypes: [
207
+ 'video/mp4',
208
+ 'video/webm',
209
+ 'video/ogg'
210
+ ],
211
+
212
+ // Local file base64 management
213
+ useBase64Upload: true,
214
+ base64Quality: 0.8,
215
+ base64MaxWidth: 1920,
216
+ base64MaxHeight: 1080,
217
+ enableImageCompression: true,
218
+ preserveOriginalFileName: true,
219
+ base64Prefix: 'data:',
220
+
221
+ // CSS classes
222
+ editorClass: 'my-custom-editor-options',
223
+ toolbarClass: 'my-custom-toolbar-options',
224
+ contentClass: 'my-custom-content-options'
225
+ }
226
+ },
227
+
228
+ methods: {
229
+ // Event handlers
230
+ handleInput(content) {
231
+ console.log('Options API - Content changed:', content)
232
+ },
233
+
234
+ handleUpdate(content) {
235
+ console.log('Options API - Content updated:', content)
236
+ },
237
+
238
+ handleReady(editor) {
239
+ console.log('Options API - Editor ready:', editor)
240
+ // Store editor reference for later use
241
+ this.editorInstance = editor
242
+ },
243
+
244
+ handleCreated(editor) {
245
+ console.log('Options API - Editor created:', editor)
246
+ },
247
+
248
+ handleFocus() {
249
+ console.log('Options API - Editor focused')
250
+ },
251
+
252
+ handleBlur() {
253
+ console.log('Options API - Editor blurred')
254
+ },
255
+
256
+ handleDestroyed() {
257
+ console.log('Options API - Editor destroyed')
258
+ },
259
+
260
+ handleAutoSave(content) {
261
+ console.log('Options API - Auto-saving content...')
262
+ // Save to your backend
263
+ this.saveToServer(content)
264
+ },
265
+
266
+ handleLimitExceeded({ current, max }) {
267
+ console.log(`Options API - Content limit exceeded: ${current}/${max}`)
268
+ alert(`Content too long: ${current}/${max} characters`)
269
+ },
270
+
271
+ handleError(error) {
272
+ console.error('Options API - Editor error:', error)
273
+ // Handle different error types
274
+ switch (error.type) {
275
+ case 'missing-dependencies':
276
+ this.showDependencyError(error.dependencies)
277
+ break
278
+ case 'initialization-failed':
279
+ this.showInitializationError(error.message)
280
+ break
281
+ default:
282
+ this.showGenericError(error.message)
283
+ }
284
+ },
285
+
286
+ handleImageCompressed(compressionData) {
287
+ console.log('Options API - Image compressed:', compressionData)
288
+
289
+ // Calculate savings percentage
290
+ const savings = Math.round(((compressionData.originalSize - compressionData.compressedSize) / compressionData.originalSize) * 100)
291
+
292
+ // Show compression statistics
293
+ console.log(`Compression stats:
294
+ Original: ${this.formatFileSize(compressionData.originalSize)}
295
+ Compressed: ${this.formatFileSize(compressionData.compressedSize)}
296
+ Savings: ${savings}%
297
+ Ratio: ${compressionData.compressionRatio.toFixed(2)}x
298
+ Original dimensions: ${compressionData.originalDimensions.width}×${compressionData.originalDimensions.height}
299
+ Compressed dimensions: ${compressionData.compressedDimensions.width}×${compressionData.compressedDimensions.height}
300
+ `)
301
+
302
+ // You could emit this data to parent component or store it
303
+ this.$emit('image-compression-stats', compressionData)
304
+ },
305
+
306
+ // Custom upload handler
307
+ async uploadHandler(file) {
308
+ try {
309
+ const formData = new FormData()
310
+ formData.append('file', file)
311
+
312
+ const response = await fetch('/api/upload', {
313
+ method: 'POST',
314
+ headers: {
315
+ 'Authorization': `Bearer ${this.getAuthToken()}`
316
+ },
317
+ body: formData
318
+ })
319
+
320
+ if (!response.ok) {
321
+ throw new Error(`Upload failed: ${response.statusText}`)
322
+ }
323
+
324
+ const data = await response.json()
325
+ return {
326
+ src: data.url,
327
+ alt: file.name
328
+ }
329
+ } catch (error) {
330
+ console.error('Options API - Upload error:', error)
331
+ throw error
332
+ }
333
+ },
334
+
335
+ // Helper methods
336
+ async saveToServer(content) {
337
+ try {
338
+ await fetch('/api/save', {
339
+ method: 'POST',
340
+ headers: {
341
+ 'Content-Type': 'application/json',
342
+ 'Authorization': `Bearer ${this.getAuthToken()}`
343
+ },
344
+ body: JSON.stringify({ content })
345
+ })
346
+ console.log('Options API - Content saved successfully')
347
+ } catch (error) {
348
+ console.error('Options API - Save failed:', error)
349
+ }
350
+ },
351
+
352
+ getAuthToken() {
353
+ return localStorage.getItem('authToken') || ''
354
+ },
355
+
356
+ formatFileSize(bytes) {
357
+ if (bytes === 0) return '0 Bytes'
358
+ const k = 1024
359
+ const sizes = ['Bytes', 'KB', 'MB', 'GB']
360
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
361
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
362
+ },
363
+
364
+ showDependencyError(dependencies) {
365
+ const message = `Missing dependencies: ${dependencies.join(', ')}`
366
+ alert(message)
367
+ },
368
+
369
+ showInitializationError(message) {
370
+ alert(`Editor initialization failed: ${message}`)
371
+ },
372
+
373
+ showGenericError(message) {
374
+ alert(`Editor error: ${message}`)
375
+ }
376
+ }
377
+ }
378
+ </script>
379
+
380
+ <style scoped>
381
+ .my-custom-editor-options {
382
+ border: 2px solid #e2e8f0;
383
+ border-radius: 8px;
384
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
385
+ }
386
+
387
+ .my-custom-toolbar-options {
388
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
389
+ border-bottom: 1px solid #e2e8f0;
390
+ }
391
+
392
+ .my-custom-content-options {
393
+ font-family: 'Georgia', serif;
394
+ line-height: 1.6;
395
+ }
396
+ </style>
397
+ ```
398
+
399
+ ### Component Comparison
400
+
401
+ | Feature | PremiumBblEditor (Composition API) | PremiumBblEditorOptionsAPI (Options API) |
402
+ |---------|-----------------------------------|------------------------------------------|
403
+ | **Vue Version** | Vue 2.6+ with @vue/composition-api | Pure Vue 2.x |
404
+ | **Dependencies** | Requires @vue/composition-api | No additional dependencies |
405
+ | **Performance** | Slightly better reactivity | Standard Vue 2 reactivity |
406
+ | **Bundle Size** | Slightly larger (composition-api) | Smaller bundle |
407
+ | **API Style** | Modern Composition API patterns | Traditional Options API |
408
+ | **Props** | Identical | Identical |
409
+ | **Events** | Identical | Identical |
410
+ | **Methods** | Identical | Identical |
411
+ | **Features** | All features available | All features available |
412
+
413
+ ### When to Use Which Version
414
+
415
+ **Use PremiumBblEditor (Composition API) when:**
416
+ - You're already using @vue/composition-api in your project
417
+ - You prefer modern Vue 3 style code patterns
418
+ - You want the latest reactivity improvements
419
+ - You're planning to migrate to Vue 3 in the future
420
+
421
+ **Use PremiumBblEditorOptionsAPI (Options API) when:**
422
+ - You want to minimize dependencies
423
+ - Your project uses traditional Vue 2 patterns
424
+ - You want the smallest possible bundle size
425
+ - You're working with legacy Vue 2 projects
426
+ - You prefer the familiar Options API syntax
427
+
428
+ ### Shared Props, Events, and Methods
429
+
430
+ Both components share the exact same interface:
431
+
432
+ ```vue
433
+ <template>
434
+ <PremiumBblEditor
435
+ v-model="content"
436
+ :placeholder="placeholder"
437
+ :editable="editable"
438
+ :theme="theme"
439
+ :toolbar-config="toolbarConfig"
440
+ :extension-config="extensionConfig"
441
+ :upload-handler="uploadHandler"
442
+ :auto-save="autoSave"
443
+ :auto-save-interval="autoSaveInterval"
444
+ :max-height="maxHeight"
445
+ :min-height="minHeight"
446
+ :font-families="fontFamilies"
447
+ :font-sizes="fontSizes"
448
+ :line-heights="lineHeights"
449
+ :allowed-headings="allowedHeadings"
450
+ :max-file-size="maxFileSize"
451
+ :allowed-image-types="allowedImageTypes"
452
+ :allowed-video-types="allowedVideoTypes"
453
+ :show-toolbar="showToolbar"
454
+ :show-bubble-menu="showBubbleMenu"
455
+ :show-table-bubble-menu="showTableBubbleMenu"
456
+ :show-image-bubble-menu="showImageBubbleMenu"
457
+ :show-video-bubble-menu="showVideoBubbleMenu"
458
+ :editor-class="editorClass"
459
+ :toolbar-class="toolbarClass"
460
+ :content-class="contentClass"
461
+ @input="handleInput"
462
+ @update="handleUpdate"
463
+ @ready="handleReady"
464
+ @created="handleCreated"
465
+ @focus="handleFocus"
466
+ @blur="handleBlur"
467
+ @destroyed="handleDestroyed"
468
+ @auto-save="handleAutoSave"
469
+ @content-limit-exceeded="handleLimitExceeded"
470
+ @error="handleError"
471
+ />
472
+ </template>
473
+
474
+ <script>
475
+ import { PremiumBblEditor } from 'vue2-premium-bbl-editor'
476
+
477
+ export default {
478
+ components: {
479
+ PremiumBblEditor
480
+ },
481
+
482
+ data() {
483
+ return {
484
+ content: '<p>Start writing...</p>',
485
+ placeholder: 'Enter your content here...',
486
+ editable: true,
487
+ theme: 'default', // 'default', 'minimal', 'dark'
488
+ autoSave: true,
489
+ autoSaveInterval: 30000, // 30 seconds
490
+ maxHeight: 500,
491
+ minHeight: 200,
492
+ maxFileSize: 10 * 1024 * 1024, // 10MB
493
+ showToolbar: true,
494
+ showBubbleMenu: true,
495
+ showTableBubbleMenu: true,
496
+ showImageBubbleMenu: true,
497
+ showVideoBubbleMenu: true,
498
+
499
+ // Toolbar configuration
500
+ toolbarConfig: {
501
+ bold: true,
502
+ italic: true,
503
+ underline: true,
504
+ strike: true,
505
+ code: true,
506
+ textColor: true,
507
+ highlight: true,
508
+ fontFamily: true,
509
+ fontSize: true,
510
+ textAlign: true,
511
+ lineHeight: true,
512
+ headings: true,
513
+ lists: true,
514
+ blockquote: true,
515
+ codeBlock: true,
516
+ horizontalRule: true,
517
+ link: true,
518
+ image: true,
519
+ video: true,
520
+ table: true,
521
+ clearFormatting: true,
522
+ sourceCode: true,
523
+ undo: true,
524
+ redo: true
525
+ },
526
+
527
+ // Extension configuration
528
+ extensionConfig: {
529
+ image: {
530
+ allowResize: true,
531
+ allowAlignment: true,
532
+ allowDelete: true,
533
+ maxWidth: '100%',
534
+ quality: 0.8
535
+ },
536
+ video: {
537
+ allowResize: true,
538
+ allowAlignment: true,
539
+ allowDelete: true,
540
+ maxWidth: '100%',
541
+ autoplay: false
542
+ },
543
+ table: {
544
+ resizable: true,
545
+ allowRowControls: true,
546
+ allowColumnControls: true,
547
+ cellSelection: true
548
+ },
549
+ link: {
550
+ openOnClick: false,
551
+ autolink: true,
552
+ linkOnPaste: true
553
+ }
554
+ },
555
+
556
+ // Styling options
557
+ fontFamilies: [
558
+ 'Inter',
559
+ 'Arial',
560
+ 'Helvetica',
561
+ 'Times New Roman',
562
+ 'Georgia',
563
+ 'Courier New'
564
+ ],
565
+ fontSizes: [
566
+ '12px', '14px', '16px', '18px', '20px', '24px', '28px', '32px'
567
+ ],
568
+ lineHeights: [
569
+ '1', '1.2', '1.4', '1.6', '1.8', '2', '2.5', '3'
570
+ ],
571
+ allowedHeadings: [1, 2, 3, 4, 5, 6],
572
+ allowedImageTypes: [
573
+ 'image/jpeg',
574
+ 'image/png',
575
+ 'image/gif',
576
+ 'image/webp'
577
+ ],
578
+ allowedVideoTypes: [
579
+ 'video/mp4',
580
+ 'video/webm',
581
+ 'video/ogg'
582
+ ],
583
+
584
+ // CSS classes
585
+ editorClass: 'my-custom-editor',
586
+ toolbarClass: 'my-custom-toolbar',
587
+ contentClass: 'my-custom-content'
588
+ }
589
+ },
590
+
591
+ methods: {
592
+ // Event handlers
593
+ handleInput(content) {
594
+ console.log('Content changed:', content)
595
+ },
596
+
597
+ handleUpdate(content) {
598
+ console.log('Content updated:', content)
599
+ },
600
+
601
+ handleReady(editor) {
602
+ console.log('Editor ready:', editor)
603
+ // Editor is ready, you can now use editor methods
604
+ },
605
+
606
+ handleCreated(editor) {
607
+ console.log('Editor created:', editor)
608
+ },
609
+
610
+ handleFocus() {
611
+ console.log('Editor focused')
612
+ },
613
+
614
+ handleBlur() {
615
+ console.log('Editor blurred')
616
+ },
617
+
618
+ handleDestroyed() {
619
+ console.log('Editor destroyed')
620
+ },
621
+
622
+ handleAutoSave(content) {
623
+ console.log('Auto-saving content...')
624
+ // Save to your backend
625
+ this.saveToServer(content)
626
+ },
627
+
628
+ handleLimitExceeded({ current, max }) {
629
+ console.log(`Content limit exceeded: ${current}/${max}`)
630
+ alert(`Content too long: ${current}/${max} characters`)
631
+ },
632
+
633
+ handleError(error) {
634
+ console.error('Editor error:', error)
635
+ // Handle different error types
636
+ switch (error.type) {
637
+ case 'missing-dependencies':
638
+ this.showDependencyError(error.dependencies)
639
+ break
640
+ case 'initialization-failed':
641
+ this.showInitializationError(error.message)
642
+ break
643
+ default:
644
+ this.showGenericError(error.message)
645
+ }
646
+ },
647
+
648
+ // Custom upload handler
649
+ async uploadHandler(file) {
650
+ try {
651
+ const formData = new FormData()
652
+ formData.append('file', file)
653
+
654
+ const response = await fetch('/api/upload', {
655
+ method: 'POST',
656
+ headers: {
657
+ 'Authorization': `Bearer ${this.authToken}`
658
+ },
659
+ body: formData
660
+ })
661
+
662
+ if (!response.ok) {
663
+ throw new Error(`Upload failed: ${response.statusText}`)
664
+ }
665
+
666
+ const data = await response.json()
667
+ return {
668
+ src: data.url,
669
+ alt: file.name
670
+ }
671
+ } catch (error) {
672
+ console.error('Upload error:', error)
673
+ throw error
674
+ }
675
+ },
676
+
677
+ // Helper methods
678
+ async saveToServer(content) {
679
+ try {
680
+ await fetch('/api/save', {
681
+ method: 'POST',
682
+ headers: {
683
+ 'Content-Type': 'application/json',
684
+ 'Authorization': `Bearer ${this.authToken}`
685
+ },
686
+ body: JSON.stringify({ content })
687
+ })
688
+ console.log('Content saved successfully')
689
+ } catch (error) {
690
+ console.error('Save failed:', error)
691
+ }
692
+ },
693
+
694
+ showDependencyError(dependencies) {
695
+ const message = `Missing dependencies: ${dependencies.join(', ')}`
696
+ alert(message)
697
+ },
698
+
699
+ showInitializationError(message) {
700
+ alert(`Editor initialization failed: ${message}`)
701
+ },
702
+
703
+ showGenericError(message) {
704
+ alert(`Editor error: ${message}`)
705
+ }
706
+ }
707
+ }
708
+ </script>
709
+
710
+ <style scoped>
711
+ .my-custom-editor {
712
+ border: 2px solid #e2e8f0;
713
+ border-radius: 8px;
714
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
715
+ }
716
+
717
+ .my-custom-toolbar {
718
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
719
+ border-bottom: 1px solid #e2e8f0;
720
+ }
721
+
722
+ .my-custom-content {
723
+ font-family: 'Georgia', serif;
724
+ line-height: 1.6;
725
+ }
726
+ </style>
727
+ ```
728
+
729
+ ### PremiumBblEditorOptionsAPI
730
+
731
+ The same editor component using Vue 2 Options API (for projects not using Composition API).
732
+
733
+ ```vue
734
+ <template>
735
+ <PremiumBblEditorOptionsAPI
736
+ v-model="content"
737
+ :placeholder="placeholder"
738
+ :toolbar-config="toolbarConfig"
739
+ @ready="handleReady"
740
+ />
741
+ </template>
742
+
743
+ <script>
744
+ import { PremiumBblEditorOptionsAPI } from 'vue2-premium-bbl-editor'
745
+
746
+ export default {
747
+ components: {
748
+ PremiumBblEditorOptionsAPI
749
+ },
750
+
751
+ data() {
752
+ return {
753
+ content: '<p>Options API Editor</p>',
754
+ placeholder: 'Start writing with Options API...',
755
+ toolbarConfig: {
756
+ bold: true,
757
+ italic: true,
758
+ underline: true,
759
+ link: true,
760
+ image: true
761
+ }
762
+ }
763
+ },
764
+
765
+ methods: {
766
+ handleReady(editor) {
767
+ console.log('Options API Editor ready:', editor)
768
+ }
769
+ }
770
+ }
771
+ </script>
772
+ ```
773
+
774
+ ## Toolbar Components
775
+
776
+ ### ToolbarMain
777
+
778
+ Complete toolbar with all formatting options.
779
+
780
+ ```vue
781
+ <template>
782
+ <div class="custom-editor">
783
+ <ToolbarMain
784
+ v-if="editor"
785
+ :editor="editor"
786
+ :config="toolbarConfig"
787
+ :font-families="fontFamilies"
788
+ :font-sizes="fontSizes"
789
+ :line-heights="lineHeights"
790
+ :allowed-headings="allowedHeadings"
791
+ :source-code-mode="isSourceMode"
792
+ @execute-command="executeCommand"
793
+ @insert-link="openLinkModal"
794
+ @insert-image="openImageModal"
795
+ @insert-video="openVideoModal"
796
+ @clear-formatting="clearFormatting"
797
+ @toggle-source-code="toggleSourceCode"
798
+ />
799
+
800
+ <div class="editor-content">
801
+ <editor-content :editor="editor" />
802
+ </div>
803
+ </div>
804
+ </template>
805
+
806
+ <script>
807
+ import { EditorContent } from '@tiptap/vue-2'
808
+ import { ToolbarMain, useEditor } from 'vue2-premium-bbl-editor'
809
+
810
+ export default {
811
+ components: {
812
+ EditorContent,
813
+ ToolbarMain
814
+ },
815
+
816
+ setup() {
817
+ const { editor, executeCommand } = useEditor({
818
+ content: '<p>Custom toolbar implementation</p>'
819
+ })
820
+
821
+ return {
822
+ editor,
823
+ executeCommand
824
+ }
825
+ },
826
+
827
+ data() {
828
+ return {
829
+ isSourceMode: false,
830
+ toolbarConfig: {
831
+ bold: true,
832
+ italic: true,
833
+ underline: true,
834
+ textColor: true,
835
+ highlight: true,
836
+ fontFamily: true,
837
+ fontSize: true,
838
+ headings: true,
839
+ lists: true,
840
+ link: true,
841
+ image: true,
842
+ video: true,
843
+ table: true
844
+ },
845
+ fontFamilies: ['Inter', 'Arial', 'Georgia'],
846
+ fontSizes: ['14px', '16px', '18px', '20px'],
847
+ lineHeights: ['1.2', '1.4', '1.6', '1.8'],
848
+ allowedHeadings: [1, 2, 3, 4]
849
+ }
850
+ },
851
+
852
+ methods: {
853
+ openLinkModal() {
854
+ console.log('Open link modal')
855
+ // Your link modal logic
856
+ },
857
+
858
+ openImageModal() {
859
+ console.log('Open image modal')
860
+ // Your image modal logic
861
+ },
862
+
863
+ openVideoModal() {
864
+ console.log('Open video modal')
865
+ // Your video modal logic
866
+ },
867
+
868
+ clearFormatting() {
869
+ this.executeCommand('unsetAllMarks')
870
+ this.executeCommand('clearNodes')
871
+ },
872
+
873
+ toggleSourceCode() {
874
+ this.isSourceMode = !this.isSourceMode
875
+ // Toggle between visual and source code mode
876
+ }
877
+ }
878
+ }
879
+ </script>
880
+ ```
881
+
882
+ ### ToolbarGroup
883
+
884
+ Group related toolbar buttons together.
885
+
886
+ ```vue
887
+ <template>
888
+ <div class="custom-toolbar">
889
+ <ToolbarGroup label="Text Formatting">
890
+ <ToolbarButton
891
+ :editor="editor"
892
+ command="toggleBold"
893
+ icon="B"
894
+ tooltip="Bold"
895
+ :is-active="editor && editor.isActive('bold')"
896
+ />
897
+ <ToolbarButton
898
+ :editor="editor"
899
+ command="toggleItalic"
900
+ icon="I"
901
+ tooltip="Italic"
902
+ :is-active="editor && editor.isActive('italic')"
903
+ />
904
+ <ToolbarButton
905
+ :editor="editor"
906
+ command="toggleUnderline"
907
+ icon="U"
908
+ tooltip="Underline"
909
+ :is-active="editor && editor.isActive('underline')"
910
+ />
911
+ </ToolbarGroup>
912
+
913
+ <ToolbarGroup label="Structure">
914
+ <ToolbarButton
915
+ :editor="editor"
916
+ command="toggleBulletList"
917
+ icon="•"
918
+ tooltip="Bullet List"
919
+ :is-active="editor && editor.isActive('bulletList')"
920
+ />
921
+ <ToolbarButton
922
+ :editor="editor"
923
+ command="toggleOrderedList"
924
+ icon="1."
925
+ tooltip="Numbered List"
926
+ :is-active="editor && editor.isActive('orderedList')"
927
+ />
928
+ </ToolbarGroup>
929
+ </div>
930
+ </template>
931
+
932
+ <script>
933
+ import { ToolbarGroup, ToolbarButton } from 'vue2-premium-bbl-editor'
934
+
935
+ export default {
936
+ components: {
937
+ ToolbarGroup,
938
+ ToolbarButton
939
+ },
940
+
941
+ props: {
942
+ editor: {
943
+ type: Object,
944
+ required: true
945
+ }
946
+ }
947
+ }
948
+ </script>
949
+ ```
950
+
951
+ ### ToolbarButton
952
+
953
+ Individual toolbar button component.
954
+
955
+ ```vue
956
+ <template>
957
+ <div class="toolbar-buttons">
958
+ <!-- Basic button -->
959
+ <ToolbarButton
960
+ :editor="editor"
961
+ command="toggleBold"
962
+ icon="B"
963
+ tooltip="Bold (Ctrl+B)"
964
+ :is-active="editor && editor.isActive('bold')"
965
+ @click="handleBoldClick"
966
+ />
967
+
968
+ <!-- Button with custom icon -->
969
+ <ToolbarButton
970
+ :editor="editor"
971
+ command="toggleItalic"
972
+ tooltip="Italic (Ctrl+I)"
973
+ :is-active="editor && editor.isActive('italic')"
974
+ >
975
+ <template #icon>
976
+ <svg width="16" height="16" viewBox="0 0 24 24">
977
+ <path d="M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4h-8z"/>
978
+ </svg>
979
+ </template>
980
+ </ToolbarButton>
981
+
982
+ <!-- Dropdown button -->
983
+ <ToolbarButton
984
+ :editor="editor"
985
+ :is-dropdown="true"
986
+ tooltip="Heading"
987
+ @click="toggleHeadingDropdown"
988
+ >
989
+ <template #icon>H</template>
990
+ <template #dropdown v-if="showHeadingDropdown">
991
+ <div class="heading-dropdown">
992
+ <div @click="setHeading(1)">Heading 1</div>
993
+ <div @click="setHeading(2)">Heading 2</div>
994
+ <div @click="setHeading(3)">Heading 3</div>
995
+ <div @click="setParagraph">Paragraph</div>
996
+ </div>
997
+ </template>
998
+ </ToolbarButton>
999
+ </div>
1000
+ </template>
1001
+
1002
+ <script>
1003
+ import { ToolbarButton } from 'vue2-premium-bbl-editor'
1004
+
1005
+ export default {
1006
+ components: {
1007
+ ToolbarButton
1008
+ },
1009
+
1010
+ props: {
1011
+ editor: {
1012
+ type: Object,
1013
+ required: true
1014
+ }
1015
+ },
1016
+
1017
+ data() {
1018
+ return {
1019
+ showHeadingDropdown: false
1020
+ }
1021
+ },
1022
+
1023
+ methods: {
1024
+ handleBoldClick() {
1025
+ console.log('Bold button clicked')
1026
+ // Custom logic before/after bold toggle
1027
+ },
1028
+
1029
+ toggleHeadingDropdown() {
1030
+ this.showHeadingDropdown = !this.showHeadingDropdown
1031
+ },
1032
+
1033
+ setHeading(level) {
1034
+ this.editor.chain().focus().toggleHeading({ level }).run()
1035
+ this.showHeadingDropdown = false
1036
+ },
1037
+
1038
+ setParagraph() {
1039
+ this.editor.chain().focus().setParagraph().run()
1040
+ this.showHeadingDropdown = false
1041
+ }
1042
+ }
1043
+ }
1044
+ </script>
1045
+
1046
+ <style scoped>
1047
+ .toolbar-buttons {
1048
+ display: flex;
1049
+ gap: 4px;
1050
+ align-items: center;
1051
+ }
1052
+
1053
+ .heading-dropdown {
1054
+ position: absolute;
1055
+ top: 100%;
1056
+ left: 0;
1057
+ background: white;
1058
+ border: 1px solid #e2e8f0;
1059
+ border-radius: 4px;
1060
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
1061
+ z-index: 10;
1062
+ min-width: 120px;
1063
+ }
1064
+
1065
+ .heading-dropdown div {
1066
+ padding: 8px 12px;
1067
+ cursor: pointer;
1068
+ border-bottom: 1px solid #f1f5f9;
1069
+ }
1070
+
1071
+ .heading-dropdown div:hover {
1072
+ background: #f8fafc;
1073
+ }
1074
+
1075
+ .heading-dropdown div:last-child {
1076
+ border-bottom: none;
1077
+ }
1078
+ </style>
1079
+ ```
1080
+
1081
+ ## Menu Components
1082
+
1083
+ ### EditorBubbleMenu
1084
+
1085
+ Text selection bubble menu that appears when text is selected.
1086
+
1087
+ ```vue
1088
+ <template>
1089
+ <div class="editor-with-bubble">
1090
+ <editor-content :editor="editor" />
1091
+
1092
+ <EditorBubbleMenu
1093
+ v-if="editor"
1094
+ :editor="editor"
1095
+ :config="bubbleConfig"
1096
+ :show-link-options="true"
1097
+ :show-text-formatting="true"
1098
+ :show-text-styling="true"
1099
+ @execute-command="executeCommand"
1100
+ @open-link-modal="openLinkModal"
1101
+ />
1102
+ </div>
1103
+ </template>
1104
+
1105
+ <script>
1106
+ import { EditorContent } from '@tiptap/vue-2'
1107
+ import { EditorBubbleMenu, useEditor } from 'vue2-premium-bbl-editor'
1108
+
1109
+ export default {
1110
+ components: {
1111
+ EditorContent,
1112
+ EditorBubbleMenu
1113
+ },
1114
+
1115
+ setup() {
1116
+ const { editor, executeCommand } = useEditor({
1117
+ content: '<p>Select text to see the bubble menu</p>'
1118
+ })
1119
+
1120
+ return {
1121
+ editor,
1122
+ executeCommand
1123
+ }
1124
+ },
1125
+
1126
+ data() {
1127
+ return {
1128
+ bubbleConfig: {
1129
+ bold: true,
1130
+ italic: true,
1131
+ underline: true,
1132
+ strike: true,
1133
+ code: true,
1134
+ textColor: true,
1135
+ highlight: true,
1136
+ link: true
1137
+ }
1138
+ }
1139
+ },
1140
+
1141
+ methods: {
1142
+ openLinkModal() {
1143
+ console.log('Open link modal from bubble menu')
1144
+ // Your link modal logic
1145
+ }
1146
+ }
1147
+ }
1148
+ </script>
1149
+ ```
1150
+
1151
+ ### TableBubbleMenu
1152
+
1153
+ Table-specific bubble menu for table operations.
1154
+
1155
+ ```vue
1156
+ <template>
1157
+ <div class="editor-with-table-menu">
1158
+ <editor-content :editor="editor" />
1159
+
1160
+ <TableBubbleMenu
1161
+ v-if="editor"
1162
+ :editor="editor"
1163
+ :show-row-controls="true"
1164
+ :show-column-controls="true"
1165
+ :show-cell-controls="true"
1166
+ :show-table-controls="true"
1167
+ @add-row-before="addRowBefore"
1168
+ @add-row-after="addRowAfter"
1169
+ @delete-row="deleteRow"
1170
+ @add-column-before="addColumnBefore"
1171
+ @add-column-after="addColumnAfter"
1172
+ @delete-column="deleteColumn"
1173
+ @merge-cells="mergeCells"
1174
+ @split-cell="splitCell"
1175
+ @delete-table="deleteTable"
1176
+ />
1177
+ </div>
1178
+ </template>
1179
+
1180
+ <script>
1181
+ import { EditorContent } from '@tiptap/vue-2'
1182
+ import { TableBubbleMenu, useEditor } from 'vue2-premium-bbl-editor'
1183
+
1184
+ export default {
1185
+ components: {
1186
+ EditorContent,
1187
+ TableBubbleMenu
1188
+ },
1189
+
1190
+ setup() {
1191
+ const { editor } = useEditor({
1192
+ content: `
1193
+ <table>
1194
+ <tr><th>Header 1</th><th>Header 2</th></tr>
1195
+ <tr><td>Cell 1</td><td>Cell 2</td></tr>
1196
+ </table>
1197
+ `
1198
+ })
1199
+
1200
+ return { editor }
1201
+ },
1202
+
1203
+ methods: {
1204
+ addRowBefore() {
1205
+ this.editor.chain().focus().addRowBefore().run()
1206
+ },
1207
+
1208
+ addRowAfter() {
1209
+ this.editor.chain().focus().addRowAfter().run()
1210
+ },
1211
+
1212
+ deleteRow() {
1213
+ this.editor.chain().focus().deleteRow().run()
1214
+ },
1215
+
1216
+ addColumnBefore() {
1217
+ this.editor.chain().focus().addColumnBefore().run()
1218
+ },
1219
+
1220
+ addColumnAfter() {
1221
+ this.editor.chain().focus().addColumnAfter().run()
1222
+ },
1223
+
1224
+ deleteColumn() {
1225
+ this.editor.chain().focus().deleteColumn().run()
1226
+ },
1227
+
1228
+ mergeCells() {
1229
+ this.editor.chain().focus().mergeCells().run()
1230
+ },
1231
+
1232
+ splitCell() {
1233
+ this.editor.chain().focus().splitCell().run()
1234
+ },
1235
+
1236
+ deleteTable() {
1237
+ this.editor.chain().focus().deleteTable().run()
1238
+ }
1239
+ }
1240
+ }
1241
+ </script>
1242
+ ```
1243
+
1244
+ ### ImageBubbleMenu
1245
+
1246
+ Image-specific bubble menu for image operations.
1247
+
1248
+ ```vue
1249
+ <template>
1250
+ <div class="editor-with-image-menu">
1251
+ <editor-content :editor="editor" />
1252
+
1253
+ <ImageBubbleMenu
1254
+ v-if="editor"
1255
+ :editor="editor"
1256
+ :show-alignment-controls="true"
1257
+ :show-resize-controls="true"
1258
+ :show-delete-control="true"
1259
+ :show-alt-text-control="true"
1260
+ @align-left="alignImageLeft"
1261
+ @align-center="alignImageCenter"
1262
+ @align-right="alignImageRight"
1263
+ @resize-image="resizeImage"
1264
+ @delete-image="deleteImage"
1265
+ @edit-alt-text="editAltText"
1266
+ />
1267
+ </div>
1268
+ </template>
1269
+
1270
+ <script>
1271
+ import { EditorContent } from '@tiptap/vue-2'
1272
+ import { ImageBubbleMenu, useEditor } from 'vue2-premium-bbl-editor'
1273
+
1274
+ export default {
1275
+ components: {
1276
+ EditorContent,
1277
+ ImageBubbleMenu
1278
+ },
1279
+
1280
+ setup() {
1281
+ const { editor } = useEditor({
1282
+ content: '<p>Click on an image to see the image menu</p>'
1283
+ })
1284
+
1285
+ return { editor }
1286
+ },
1287
+
1288
+ methods: {
1289
+ alignImageLeft() {
1290
+ // Custom image alignment logic
1291
+ console.log('Align image left')
1292
+ },
1293
+
1294
+ alignImageCenter() {
1295
+ console.log('Align image center')
1296
+ },
1297
+
1298
+ alignImageRight() {
1299
+ console.log('Align image right')
1300
+ },
1301
+
1302
+ resizeImage(size) {
1303
+ console.log('Resize image to:', size)
1304
+ },
1305
+
1306
+ deleteImage() {
1307
+ console.log('Delete image')
1308
+ },
1309
+
1310
+ editAltText() {
1311
+ console.log('Edit alt text')
1312
+ }
1313
+ }
1314
+ }
1315
+ </script>
1316
+ ```
1317
+
1318
+ ### VideoBubbleMenu
1319
+
1320
+ Video-specific bubble menu for video operations.
1321
+
1322
+ ```vue
1323
+ <template>
1324
+ <div class="editor-with-video-menu">
1325
+ <editor-content :editor="editor" />
1326
+
1327
+ <VideoBubbleMenu
1328
+ v-if="editor"
1329
+ :editor="editor"
1330
+ :show-alignment-controls="true"
1331
+ :show-resize-controls="true"
1332
+ :show-playback-controls="true"
1333
+ :show-delete-control="true"
1334
+ @align-left="alignVideoLeft"
1335
+ @align-center="alignVideoCenter"
1336
+ @align-right="alignVideoRight"
1337
+ @resize-video="resizeVideo"
1338
+ @toggle-autoplay="toggleAutoplay"
1339
+ @toggle-controls="toggleControls"
1340
+ @delete-video="deleteVideo"
1341
+ />
1342
+ </div>
1343
+ </template>
1344
+
1345
+ <script>
1346
+ import { EditorContent } from '@tiptap/vue-2'
1347
+ import { VideoBubbleMenu, useEditor } from 'vue2-premium-bbl-editor'
1348
+
1349
+ export default {
1350
+ components: {
1351
+ EditorContent,
1352
+ VideoBubbleMenu
1353
+ },
1354
+
1355
+ setup() {
1356
+ const { editor } = useEditor({
1357
+ content: '<p>Add a video to see the video menu</p>'
1358
+ })
1359
+
1360
+ return { editor }
1361
+ },
1362
+
1363
+ methods: {
1364
+ alignVideoLeft() {
1365
+ console.log('Align video left')
1366
+ },
1367
+
1368
+ alignVideoCenter() {
1369
+ console.log('Align video center')
1370
+ },
1371
+
1372
+ alignVideoRight() {
1373
+ console.log('Align video right')
1374
+ },
1375
+
1376
+ resizeVideo(size) {
1377
+ console.log('Resize video to:', size)
1378
+ },
1379
+
1380
+ toggleAutoplay() {
1381
+ console.log('Toggle video autoplay')
1382
+ },
1383
+
1384
+ toggleControls() {
1385
+ console.log('Toggle video controls')
1386
+ },
1387
+
1388
+ deleteVideo() {
1389
+ console.log('Delete video')
1390
+ }
1391
+ }
1392
+ }
1393
+ </script>
1394
+ ```
1395
+
1396
+ ## Modal Components
1397
+
1398
+ ### ImageModal
1399
+
1400
+ Modal for uploading and configuring images.
1401
+
1402
+ ```vue
1403
+ <template>
1404
+ <div>
1405
+ <button @click="showImageModal = true">Insert Image</button>
1406
+
1407
+ <ImageModal
1408
+ :visible="showImageModal"
1409
+ :upload-handler="uploadHandler"
1410
+ :max-file-size="maxFileSize"
1411
+ :allowed-types="allowedImageTypes"
1412
+ :show-url-input="true"
1413
+ :show-alt-text-input="true"
1414
+ :show-alignment-options="true"
1415
+ :show-size-options="true"
1416
+ @close="showImageModal = false"
1417
+ @insert="insertImage"
1418
+ @upload-progress="handleUploadProgress"
1419
+ @upload-error="handleUploadError"
1420
+ />
1421
+ </div>
1422
+ </template>
1423
+
1424
+ <script>
1425
+ import { ImageModal } from 'vue2-premium-bbl-editor'
1426
+
1427
+ export default {
1428
+ components: {
1429
+ ImageModal
1430
+ },
1431
+
1432
+ data() {
1433
+ return {
1434
+ showImageModal: false,
1435
+ maxFileSize: 5 * 1024 * 1024, // 5MB
1436
+ allowedImageTypes: [
1437
+ 'image/jpeg',
1438
+ 'image/png',
1439
+ 'image/gif',
1440
+ 'image/webp'
1441
+ ]
1442
+ }
1443
+ },
1444
+
1445
+ methods: {
1446
+ async uploadHandler(file) {
1447
+ try {
1448
+ const formData = new FormData()
1449
+ formData.append('image', file)
1450
+
1451
+ const response = await fetch('/api/upload/image', {
1452
+ method: 'POST',
1453
+ body: formData
1454
+ })
1455
+
1456
+ if (!response.ok) {
1457
+ throw new Error(`Upload failed: ${response.statusText}`)
1458
+ }
1459
+
1460
+ const data = await response.json()
1461
+ return {
1462
+ src: data.url,
1463
+ alt: file.name,
1464
+ width: data.width,
1465
+ height: data.height
1466
+ }
1467
+ } catch (error) {
1468
+ console.error('Image upload error:', error)
1469
+ throw error
1470
+ }
1471
+ },
1472
+
1473
+ insertImage(imageData) {
1474
+ console.log('Insert image:', imageData)
1475
+ // Insert image into editor
1476
+ // this.editor.chain().focus().setImage(imageData).run()
1477
+ },
1478
+
1479
+ handleUploadProgress(progress) {
1480
+ console.log('Upload progress:', progress)
1481
+ },
1482
+
1483
+ handleUploadError(error) {
1484
+ console.error('Upload error:', error)
1485
+ alert(`Upload failed: ${error.message}`)
1486
+ }
1487
+ }
1488
+ }
1489
+ </script>
1490
+ ```
1491
+
1492
+ ### VideoModal
1493
+
1494
+ Modal for uploading and configuring videos.
1495
+
1496
+ ```vue
1497
+ <template>
1498
+ <div>
1499
+ <button @click="showVideoModal = true">Insert Video</button>
1500
+
1501
+ <VideoModal
1502
+ :visible="showVideoModal"
1503
+ :upload-handler="uploadHandler"
1504
+ :max-file-size="maxFileSize"
1505
+ :allowed-types="allowedVideoTypes"
1506
+ :show-url-input="true"
1507
+ :show-embed-input="true"
1508
+ :show-playback-options="true"
1509
+ :show-size-options="true"
1510
+ @close="showVideoModal = false"
1511
+ @insert="insertVideo"
1512
+ @upload-progress="handleUploadProgress"
1513
+ @upload-error="handleUploadError"
1514
+ />
1515
+ </div>
1516
+ </template>
1517
+
1518
+ <script>
1519
+ import { VideoModal } from 'vue2-premium-bbl-editor'
1520
+
1521
+ export default {
1522
+ components: {
1523
+ VideoModal
1524
+ },
1525
+
1526
+ data() {
1527
+ return {
1528
+ showVideoModal: false,
1529
+ maxFileSize: 50 * 1024 * 1024, // 50MB
1530
+ allowedVideoTypes: [
1531
+ 'video/mp4',
1532
+ 'video/webm',
1533
+ 'video/ogg'
1534
+ ]
1535
+ }
1536
+ },
1537
+
1538
+ methods: {
1539
+ async uploadHandler(file) {
1540
+ try {
1541
+ const formData = new FormData()
1542
+ formData.append('video', file)
1543
+
1544
+ const response = await fetch('/api/upload/video', {
1545
+ method: 'POST',
1546
+ body: formData
1547
+ })
1548
+
1549
+ if (!response.ok) {
1550
+ throw new Error(`Upload failed: ${response.statusText}`)
1551
+ }
1552
+
1553
+ const data = await response.json()
1554
+ return {
1555
+ src: data.url,
1556
+ poster: data.poster,
1557
+ width: data.width,
1558
+ height: data.height
1559
+ }
1560
+ } catch (error) {
1561
+ console.error('Video upload error:', error)
1562
+ throw error
1563
+ }
1564
+ },
1565
+
1566
+ insertVideo(videoData) {
1567
+ console.log('Insert video:', videoData)
1568
+ // Insert video into editor
1569
+ // this.editor.chain().focus().setVideo(videoData).run()
1570
+ },
1571
+
1572
+ handleUploadProgress(progress) {
1573
+ console.log('Upload progress:', progress)
1574
+ },
1575
+
1576
+ handleUploadError(error) {
1577
+ console.error('Upload error:', error)
1578
+ alert(`Upload failed: ${error.message}`)
1579
+ }
1580
+ }
1581
+ }
1582
+ </script>
1583
+ ```
1584
+
1585
+ ### LinkModal
1586
+
1587
+ Modal for creating and editing links.
1588
+
1589
+ ```vue
1590
+ <template>
1591
+ <div>
1592
+ <button @click="showLinkModal = true">Insert Link</button>
1593
+
1594
+ <LinkModal
1595
+ :visible="showLinkModal"
1596
+ :initial-text="linkData.text"
1597
+ :initial-url="linkData.url"
1598
+ :initial-target="linkData.target"
1599
+ :show-text-input="true"
1600
+ :show-target-options="true"
1601
+ :show-title-input="true"
1602
+ :validate-url="true"
1603
+ @close="showLinkModal = false"
1604
+ @insert="insertLink"
1605
+ @update="updateLink"
1606
+ @remove="removeLink"
1607
+ />
1608
+ </div>
1609
+ </template>
1610
+
1611
+ <script>
1612
+ import { LinkModal } from 'vue2-premium-bbl-editor'
1613
+
1614
+ export default {
1615
+ components: {
1616
+ LinkModal
1617
+ },
1618
+
1619
+ data() {
1620
+ return {
1621
+ showLinkModal: false,
1622
+ linkData: {
1623
+ text: '',
1624
+ url: '',
1625
+ target: '_blank'
1626
+ }
1627
+ }
1628
+ },
1629
+
1630
+ methods: {
1631
+ insertLink(linkData) {
1632
+ console.log('Insert link:', linkData)
1633
+ // Insert link into editor
1634
+ // this.editor.chain().focus().setLink(linkData).run()
1635
+ },
1636
+
1637
+ updateLink(linkData) {
1638
+ console.log('Update link:', linkData)
1639
+ // Update existing link
1640
+ },
1641
+
1642
+ removeLink() {
1643
+ console.log('Remove link')
1644
+ // Remove link from editor
1645
+ // this.editor.chain().focus().unsetLink().run()
1646
+ }
1647
+ }
1648
+ }
1649
+ </script>
1650
+ ```
1651
+
1652
+ ## Upload System Components
1653
+
1654
+ ### UploadManager Integration
1655
+
1656
+ ```vue
1657
+ <template>
1658
+ <div class="upload-system-demo">
1659
+ <PremiumBblEditor
1660
+ v-model="content"
1661
+ @ready="onEditorReady"
1662
+ />
1663
+
1664
+ <div class="upload-status" v-if="uploadStatus">
1665
+ <div class="progress-bar">
1666
+ <div
1667
+ class="progress-fill"
1668
+ :style="{ width: uploadProgress + '%' }"
1669
+ ></div>
1670
+ </div>
1671
+ <p>{{ uploadStatus }} - {{ uploadProgress }}%</p>
1672
+ </div>
1673
+ </div>
1674
+ </template>
1675
+
1676
+ <script>
1677
+ import {
1678
+ PremiumBblEditor,
1679
+ UploadManager,
1680
+ LocalStorageAdapter,
1681
+ integrateTipTapUpload,
1682
+ createUploadConfig
1683
+ } from 'vue2-premium-bbl-editor'
1684
+
1685
+ export default {
1686
+ components: {
1687
+ PremiumBblEditor
1688
+ },
1689
+
1690
+ data() {
1691
+ return {
1692
+ content: '<p>Upload system integration example</p>',
1693
+ uploadManager: null,
1694
+ uploadStatus: null,
1695
+ uploadProgress: 0
1696
+ }
1697
+ },
1698
+
1699
+ async created() {
1700
+ await this.initializeUploadSystem()
1701
+ },
1702
+
1703
+ methods: {
1704
+ async initializeUploadSystem() {
1705
+ try {
1706
+ // Create upload configuration
1707
+ const config = createUploadConfig('localStorage', {
1708
+ validation: {
1709
+ maxSize: 10 * 1024 * 1024, // 10MB
1710
+ allowedTypes: [
1711
+ 'image/jpeg', 'image/png', 'image/gif',
1712
+ 'video/mp4', 'video/webm'
1713
+ ]
1714
+ },
1715
+ retry: {
1716
+ maxAttempts: 3,
1717
+ baseDelay: 1000
1718
+ }
1719
+ })
1720
+
1721
+ // Initialize upload manager
1722
+ this.uploadManager = new UploadManager(config)
1723
+
1724
+ // Register adapters
1725
+ const localAdapter = new LocalStorageAdapter({
1726
+ generateObjectUrl: true
1727
+ })
1728
+ this.uploadManager.registerAdapter(localAdapter)
1729
+ this.uploadManager.setDefaultAdapter('local-storage')
1730
+
1731
+ // Set up event listeners
1732
+ this.setupUploadEvents()
1733
+
1734
+ } catch (error) {
1735
+ console.error('Upload system initialization failed:', error)
1736
+ }
1737
+ },
1738
+
1739
+ setupUploadEvents() {
1740
+ // Progress tracking
1741
+ this.uploadManager.on('progress', (progress) => {
1742
+ this.uploadProgress = Math.round(progress.percentage)
1743
+ this.uploadStatus = 'Uploading...'
1744
+ })
1745
+
1746
+ // Upload completed
1747
+ this.uploadManager.on('uploadCompleted', (event) => {
1748
+ this.uploadStatus = 'Upload completed!'
1749
+ this.uploadProgress = 100
1750
+
1751
+ setTimeout(() => {
1752
+ this.uploadStatus = null
1753
+ this.uploadProgress = 0
1754
+ }, 2000)
1755
+ })
1756
+
1757
+ // Upload failed
1758
+ this.uploadManager.on('uploadFailed', (event) => {
1759
+ this.uploadStatus = `Upload failed: ${event.error.message}`
1760
+ this.uploadProgress = 0
1761
+
1762
+ setTimeout(() => {
1763
+ this.uploadStatus = null
1764
+ }, 3000)
1765
+ })
1766
+ },
1767
+
1768
+ onEditorReady(editor) {
1769
+ // Integrate upload system with editor
1770
+ integrateTipTapUpload(editor, this.uploadManager, {
1771
+ onStart: () => {
1772
+ this.uploadStatus = 'Starting upload...'
1773
+ this.uploadProgress = 0
1774
+ },
1775
+ onEnd: () => {
1776
+ // Upload process ended (success or failure)
1777
+ }
1778
+ })
1779
+ }
1780
+ },
1781
+
1782
+ beforeDestroy() {
1783
+ if (this.uploadManager) {
1784
+ this.uploadManager.destroy()
1785
+ }
1786
+ }
1787
+ }
1788
+ </script>
1789
+
1790
+ <style scoped>
1791
+ .upload-status {
1792
+ margin-top: 20px;
1793
+ padding: 15px;
1794
+ background: #f8f9fa;
1795
+ border-radius: 6px;
1796
+ border: 1px solid #e9ecef;
1797
+ }
1798
+
1799
+ .progress-bar {
1800
+ width: 100%;
1801
+ height: 8px;
1802
+ background: #e9ecef;
1803
+ border-radius: 4px;
1804
+ overflow: hidden;
1805
+ margin-bottom: 10px;
1806
+ }
1807
+
1808
+ .progress-fill {
1809
+ height: 100%;
1810
+ background: #007bff;
1811
+ transition: width 0.3s ease;
1812
+ }
1813
+ </style>
1814
+ ```
1815
+
1816
+ ## Development Tools
1817
+
1818
+ ### DiagnosticTool
1819
+
1820
+ Built-in diagnostic tool for troubleshooting.
1821
+
1822
+ ```vue
1823
+ <template>
1824
+ <div class="development-tools">
1825
+ <h3>Development Tools</h3>
1826
+
1827
+ <!-- Main Editor -->
1828
+ <PremiumBblEditor
1829
+ v-model="content"
1830
+ @ready="handleReady"
1831
+ @error="handleError"
1832
+ />
1833
+
1834
+ <!-- Diagnostic Tool -->
1835
+ <div class="diagnostic-section">
1836
+ <h4>Diagnostic Information</h4>
1837
+ <DiagnosticTool
1838
+ :show-dependency-check="true"
1839
+ :show-browser-info="true"
1840
+ :show-editor-state="true"
1841
+ :show-performance-metrics="true"
1842
+ :editor="editor"
1843
+ @diagnostic-complete="handleDiagnostic"
1844
+ />
1845
+ </div>
1846
+ </div>
1847
+ </template>
1848
+
1849
+ <script>
1850
+ import { PremiumBblEditor, DiagnosticTool } from 'vue2-premium-bbl-editor'
1851
+
1852
+ export default {
1853
+ components: {
1854
+ PremiumBblEditor,
1855
+ DiagnosticTool
1856
+ },
1857
+
1858
+ data() {
1859
+ return {
1860
+ content: '<p>Editor with diagnostic tools</p>',
1861
+ editor: null
1862
+ }
1863
+ },
1864
+
1865
+ methods: {
1866
+ handleReady(editor) {
1867
+ this.editor = editor
1868
+ console.log('Editor ready for diagnostics')
1869
+ },
1870
+
1871
+ handleError(error) {
1872
+ console.error('Editor error detected:', error)
1873
+ },
1874
+
1875
+ handleDiagnostic(diagnosticData) {
1876
+ console.log('Diagnostic complete:', diagnosticData)
1877
+
1878
+ // Check for issues
1879
+ if (diagnosticData.issues.length > 0) {
1880
+ console.warn('Issues found:', diagnosticData.issues)
1881
+ }
1882
+
1883
+ // Performance metrics
1884
+ if (diagnosticData.performance) {
1885
+ console.log('Performance metrics:', diagnosticData.performance)
1886
+ }
1887
+ }
1888
+ }
1889
+ }
1890
+ </script>
1891
+
1892
+ <style scoped>
1893
+ .development-tools {
1894
+ max-width: 1000px;
1895
+ margin: 0 auto;
1896
+ padding: 20px;
1897
+ }
1898
+
1899
+ .diagnostic-section {
1900
+ margin-top: 30px;
1901
+ padding: 20px;
1902
+ background: #f8f9fa;
1903
+ border-radius: 8px;
1904
+ border: 1px solid #e9ecef;
1905
+ }
1906
+ </style>
1907
+ ```
1908
+
1909
+ ### DebugHelper
1910
+
1911
+ Development debugging helper component.
1912
+
1913
+ ```vue
1914
+ <template>
1915
+ <div class="debug-environment" v-if="isDevelopment">
1916
+ <h3>Debug Environment</h3>
1917
+
1918
+ <!-- Main Editor -->
1919
+ <PremiumBblEditor
1920
+ v-model="content"
1921
+ @ready="handleReady"
1922
+ @input="handleInput"
1923
+ @focus="handleFocus"
1924
+ @blur="handleBlur"
1925
+ />
1926
+
1927
+ <!-- Debug Helper -->
1928
+ <div class="debug-section">
1929
+ <h4>Debug Information</h4>
1930
+ <DebugHelper
1931
+ :editor="editor"
1932
+ :show-editor-state="true"
1933
+ :show-content-analysis="true"
1934
+ :show-event-log="true"
1935
+ :show-performance-monitor="true"
1936
+ :auto-refresh="true"
1937
+ :refresh-interval="1000"
1938
+ @state-change="handleStateChange"
1939
+ @performance-update="handlePerformanceUpdate"
1940
+ />
1941
+ </div>
1942
+ </div>
1943
+ </template>
1944
+
1945
+ <script>
1946
+ import { PremiumBblEditor, DebugHelper } from 'vue2-premium-bbl-editor'
1947
+
1948
+ export default {
1949
+ components: {
1950
+ PremiumBblEditor,
1951
+ DebugHelper
1952
+ },
1953
+
1954
+ data() {
1955
+ return {
1956
+ content: '<p>Editor with debug helper</p>',
1957
+ editor: null
1958
+ }
1959
+ },
1960
+
1961
+ computed: {
1962
+ isDevelopment() {
1963
+ return process.env.NODE_ENV === 'development'
1964
+ }
1965
+ },
1966
+
1967
+ methods: {
1968
+ handleReady(editor) {
1969
+ this.editor = editor
1970
+ console.log('Editor ready for debugging')
1971
+ },
1972
+
1973
+ handleInput(content) {
1974
+ console.log('Content changed:', content.length, 'characters')
1975
+ },
1976
+
1977
+ handleFocus() {
1978
+ console.log('Editor focused')
1979
+ },
1980
+
1981
+ handleBlur() {
1982
+ console.log('Editor blurred')
1983
+ },
1984
+
1985
+ handleStateChange(state) {
1986
+ console.log('Editor state changed:', state)
1987
+ },
1988
+
1989
+ handlePerformanceUpdate(metrics) {
1990
+ console.log('Performance metrics:', metrics)
1991
+
1992
+ // Alert if performance is poor
1993
+ if (metrics.renderTime > 100) {
1994
+ console.warn('Slow render time detected:', metrics.renderTime, 'ms')
1995
+ }
1996
+ }
1997
+ }
1998
+ }
1999
+ </script>
2000
+
2001
+ <style scoped>
2002
+ .debug-environment {
2003
+ max-width: 1200px;
2004
+ margin: 0 auto;
2005
+ padding: 20px;
2006
+ }
2007
+
2008
+ .debug-section {
2009
+ margin-top: 30px;
2010
+ padding: 20px;
2011
+ background: #1e1e1e;
2012
+ color: #d4d4d4;
2013
+ border-radius: 8px;
2014
+ font-family: 'Courier New', monospace;
2015
+ }
2016
+ </style>
2017
+ ```
2018
+
2019
+ ## Composables
2020
+
2021
+ ### useEditor
2022
+
2023
+ Core editor composable for custom implementations.
2024
+
2025
+ ```vue
2026
+ <template>
2027
+ <div class="custom-editor-implementation">
2028
+ <div class="editor-toolbar" v-if="editor">
2029
+ <button
2030
+ @click="executeCommand('toggleBold')"
2031
+ :class="{ active: isActive('bold') }"
2032
+ >
2033
+ Bold
2034
+ </button>
2035
+ <button
2036
+ @click="executeCommand('toggleItalic')"
2037
+ :class="{ active: isActive('italic') }"
2038
+ >
2039
+ Italic
2040
+ </button>
2041
+ <button @click="focus">Focus</button>
2042
+ <button @click="getContentExample">Get Content</button>
2043
+ </div>
2044
+
2045
+ <div class="editor-content">
2046
+ <editor-content :editor="editor" />
2047
+ </div>
2048
+
2049
+ <div class="editor-status">
2050
+ <p>Ready: {{ isEditorReady }}</p>
2051
+ <p>Content Length: {{ currentContent.length }}</p>
2052
+ <p v-if="editorError" class="error">Error: {{ editorError }}</p>
2053
+ </div>
2054
+ </div>
2055
+ </template>
2056
+
2057
+ <script>
2058
+ import { EditorContent } from '@tiptap/vue-2'
2059
+ import { useEditor } from 'vue2-premium-bbl-editor'
2060
+
2061
+ export default {
2062
+ components: {
2063
+ EditorContent
2064
+ },
2065
+
2066
+ setup(props, { emit }) {
2067
+ // Use the editor composable
2068
+ const {
2069
+ editor,
2070
+ isEditorReady,
2071
+ currentContent,
2072
+ editorError,
2073
+ missingDependencies,
2074
+ executeCommand,
2075
+ isActive,
2076
+ getContent,
2077
+ setContent,
2078
+ focus,
2079
+ blur
2080
+ } = useEditor({
2081
+ value: '<p>Custom editor using useEditor composable</p>',
2082
+ placeholder: 'Start typing...',
2083
+ editable: true,
2084
+ autofocus: false,
2085
+ toolbarConfig: {
2086
+ bold: true,
2087
+ italic: true,
2088
+ underline: true
2089
+ },
2090
+ extensionConfig: {
2091
+ image: {
2092
+ allowResize: true
2093
+ }
2094
+ }
2095
+ }, emit)
2096
+
2097
+ return {
2098
+ editor,
2099
+ isEditorReady,
2100
+ currentContent,
2101
+ editorError,
2102
+ missingDependencies,
2103
+ executeCommand,
2104
+ isActive,
2105
+ getContent,
2106
+ setContent,
2107
+ focus,
2108
+ blur
2109
+ }
2110
+ },
2111
+
2112
+ methods: {
2113
+ getContentExample() {
2114
+ const htmlContent = this.getContent('html')
2115
+ const jsonContent = this.getContent('json')
2116
+
2117
+ console.log('HTML Content:', htmlContent)
2118
+ console.log('JSON Content:', jsonContent)
2119
+ }
2120
+ }
2121
+ }
2122
+ </script>
2123
+
2124
+ <style scoped>
2125
+ .custom-editor-implementation {
2126
+ max-width: 800px;
2127
+ margin: 0 auto;
2128
+ padding: 20px;
2129
+ }
2130
+
2131
+ .editor-toolbar {
2132
+ display: flex;
2133
+ gap: 8px;
2134
+ margin-bottom: 16px;
2135
+ padding: 12px;
2136
+ background: #f8f9fa;
2137
+ border-radius: 6px;
2138
+ }
2139
+
2140
+ .editor-toolbar button {
2141
+ padding: 6px 12px;
2142
+ border: 1px solid #dee2e6;
2143
+ background: white;
2144
+ border-radius: 4px;
2145
+ cursor: pointer;
2146
+ }
2147
+
2148
+ .editor-toolbar button:hover {
2149
+ background: #e9ecef;
2150
+ }
2151
+
2152
+ .editor-toolbar button.active {
2153
+ background: #007bff;
2154
+ color: white;
2155
+ border-color: #007bff;
2156
+ }
2157
+
2158
+ .editor-content {
2159
+ border: 1px solid #dee2e6;
2160
+ border-radius: 6px;
2161
+ min-height: 200px;
2162
+ padding: 16px;
2163
+ }
2164
+
2165
+ .editor-status {
2166
+ margin-top: 16px;
2167
+ padding: 12px;
2168
+ background: #f8f9fa;
2169
+ border-radius: 6px;
2170
+ font-size: 14px;
2171
+ }
2172
+
2173
+ .error {
2174
+ color: #dc3545;
2175
+ font-weight: bold;
2176
+ }
2177
+ </style>
2178
+ ```
2179
+
2180
+ ## Advanced Usage Patterns
2181
+
2182
+ ### Multi-Editor Setup
2183
+
2184
+ ```vue
2185
+ <template>
2186
+ <div class="multi-editor-setup">
2187
+ <h2>Multi-Editor Setup</h2>
2188
+
2189
+ <div class="editor-tabs">
2190
+ <button
2191
+ v-for="(tab, index) in tabs"
2192
+ :key="index"
2193
+ @click="activeTab = index"
2194
+ :class="{ active: activeTab === index }"
2195
+ >
2196
+ {{ tab.title }}
2197
+ </button>
2198
+ </div>
2199
+
2200
+ <div class="editor-content">
2201
+ <PremiumBblEditor
2202
+ v-for="(tab, index) in tabs"
2203
+ :key="index"
2204
+ v-show="activeTab === index"
2205
+ v-model="tab.content"
2206
+ :placeholder="tab.placeholder"
2207
+ :toolbar-config="tab.toolbarConfig"
2208
+ @ready="handleEditorReady(index, $event)"
2209
+ />
2210
+ </div>
2211
+ </div>
2212
+ </template>
2213
+
2214
+ <script>
2215
+ import { PremiumBblEditor } from 'vue2-premium-bbl-editor'
2216
+
2217
+ export default {
2218
+ components: {
2219
+ PremiumBblEditor
2220
+ },
2221
+
2222
+ data() {
2223
+ return {
2224
+ activeTab: 0,
2225
+ tabs: [
2226
+ {
2227
+ title: 'Article Content',
2228
+ content: '<h2>Article Title</h2><p>Article content...</p>',
2229
+ placeholder: 'Write your article...',
2230
+ toolbarConfig: {
2231
+ bold: true,
2232
+ italic: true,
2233
+ underline: true,
2234
+ headings: true,
2235
+ lists: true,
2236
+ link: true,
2237
+ image: true
2238
+ }
2239
+ },
2240
+ {
2241
+ title: 'Summary',
2242
+ content: '<p>Article summary...</p>',
2243
+ placeholder: 'Write a summary...',
2244
+ toolbarConfig: {
2245
+ bold: true,
2246
+ italic: true,
2247
+ lists: true
2248
+ }
2249
+ },
2250
+ {
2251
+ title: 'Notes',
2252
+ content: '<p>Internal notes...</p>',
2253
+ placeholder: 'Add notes...',
2254
+ toolbarConfig: {
2255
+ bold: true,
2256
+ italic: true,
2257
+ code: true,
2258
+ lists: true
2259
+ }
2260
+ }
2261
+ ],
2262
+ editors: []
2263
+ }
2264
+ },
2265
+
2266
+ methods: {
2267
+ handleEditorReady(index, editor) {
2268
+ this.editors[index] = editor
2269
+ console.log(`Editor ${index} ready:`, editor)
2270
+ },
2271
+
2272
+ getAllContent() {
2273
+ return this.tabs.map((tab, index) => ({
2274
+ title: tab.title,
2275
+ content: tab.content,
2276
+ editor: this.editors[index]
2277
+ }))
2278
+ },
2279
+
2280
+ saveAllContent() {
2281
+ const allContent = this.getAllContent()
2282
+ console.log('Saving all content:', allContent)
2283
+ // Save to backend
2284
+ }
2285
+ }
2286
+ }
2287
+ </script>
2288
+
2289
+ <style scoped>
2290
+ .multi-editor-setup {
2291
+ max-width: 1000px;
2292
+ margin: 0 auto;
2293
+ padding: 20px;
2294
+ }
2295
+
2296
+ .editor-tabs {
2297
+ display: flex;
2298
+ gap: 4px;
2299
+ margin-bottom: 20px;
2300
+ border-bottom: 1px solid #dee2e6;
2301
+ }
2302
+
2303
+ .editor-tabs button {
2304
+ padding: 12px 20px;
2305
+ border: none;
2306
+ background: transparent;
2307
+ cursor: pointer;
2308
+ border-bottom: 2px solid transparent;
2309
+ font-weight: 500;
2310
+ }
2311
+
2312
+ .editor-tabs button:hover {
2313
+ background: #f8f9fa;
2314
+ }
2315
+
2316
+ .editor-tabs button.active {
2317
+ border-bottom-color: #007bff;
2318
+ color: #007bff;
2319
+ }
2320
+
2321
+ .editor-content {
2322
+ min-height: 400px;
2323
+ }
2324
+ </style>
2325
+ ```
2326
+
2327
+ ### Form Integration
2328
+
2329
+ ```vue
2330
+ <template>
2331
+ <form @submit.prevent="submitForm" class="editor-form">
2332
+ <h2>Article Form with Editor</h2>
2333
+
2334
+ <div class="bbl-form-group">
2335
+ <label for="title">Title:</label>
2336
+ <input
2337
+ id="title"
2338
+ v-model="form.title"
2339
+ type="text"
2340
+ required
2341
+ class="form-control"
2342
+ />
2343
+ </div>
2344
+
2345
+ <div class="bbl-form-group">
2346
+ <label for="category">Category:</label>
2347
+ <select id="category" v-model="form.category" class="form-control">
2348
+ <option value="news">News</option>
2349
+ <option value="tutorial">Tutorial</option>
2350
+ <option value="review">Review</option>
2351
+ </select>
2352
+ </div>
2353
+
2354
+ <div class="bbl-form-group">
2355
+ <label>Content:</label>
2356
+ <PremiumBblEditor
2357
+ v-model="form.content"
2358
+ placeholder="Write your article content..."
2359
+ :auto-save="true"
2360
+ :auto-save-interval="30000"
2361
+ :max-content-length="10000"
2362
+ @auto-save="handleAutoSave"
2363
+ @content-limit-exceeded="handleLimitExceeded"
2364
+ @ready="handleEditorReady"
2365
+ />
2366
+ </div>
2367
+
2368
+ <div class="bbl-form-group">
2369
+ <label for="tags">Tags:</label>
2370
+ <input
2371
+ id="tags"
2372
+ v-model="form.tags"
2373
+ type="text"
2374
+ placeholder="Comma-separated tags"
2375
+ class="form-control"
2376
+ />
2377
+ </div>
2378
+
2379
+ <div class="form-actions">
2380
+ <button type="button" @click="saveDraft" class="btn btn-secondary">
2381
+ Save Draft
2382
+ </button>
2383
+ <button type="submit" class="btn btn-primary">
2384
+ Publish Article
2385
+ </button>
2386
+ </div>
2387
+
2388
+ <div class="form-status" v-if="status">
2389
+ <p :class="statusClass">{{ status }}</p>
2390
+ </div>
2391
+ </form>
2392
+ </template>
2393
+
2394
+ <script>
2395
+ import { PremiumBblEditor } from 'vue2-premium-bbl-editor'
2396
+
2397
+ export default {
2398
+ components: {
2399
+ PremiumBblEditor
2400
+ },
2401
+
2402
+ data() {
2403
+ return {
2404
+ form: {
2405
+ title: '',
2406
+ category: 'news',
2407
+ content: '<p>Start writing your article...</p>',
2408
+ tags: ''
2409
+ },
2410
+ editor: null,
2411
+ status: '',
2412
+ statusClass: ''
2413
+ }
2414
+ },
2415
+
2416
+ methods: {
2417
+ handleEditorReady(editor) {
2418
+ this.editor = editor
2419
+ console.log('Form editor ready')
2420
+ },
2421
+
2422
+ handleAutoSave(content) {
2423
+ this.form.content = content
2424
+ this.saveDraft(true) // Auto-save as draft
2425
+ },
2426
+
2427
+ handleLimitExceeded({ current, max }) {
2428
+ this.showStatus(`Content too long: ${current}/${max} characters`, 'error')
2429
+ },
2430
+
2431
+ async saveDraft(isAutoSave = false) {
2432
+ try {
2433
+ const response = await fetch('/api/articles/draft', {
2434
+ method: 'POST',
2435
+ headers: {
2436
+ 'Content-Type': 'application/json'
2437
+ },
2438
+ body: JSON.stringify({
2439
+ ...this.form,
2440
+ isDraft: true
2441
+ })
2442
+ })
2443
+
2444
+ if (response.ok) {
2445
+ const message = isAutoSave ? 'Auto-saved' : 'Draft saved'
2446
+ this.showStatus(message, 'success')
2447
+ } else {
2448
+ throw new Error('Save failed')
2449
+ }
2450
+ } catch (error) {
2451
+ this.showStatus('Save failed', 'error')
2452
+ }
2453
+ },
2454
+
2455
+ async submitForm() {
2456
+ try {
2457
+ // Validate form
2458
+ if (!this.form.title.trim()) {
2459
+ this.showStatus('Title is required', 'error')
2460
+ return
2461
+ }
2462
+
2463
+ if (!this.form.content.trim() || this.form.content === '<p></p>') {
2464
+ this.showStatus('Content is required', 'error')
2465
+ return
2466
+ }
2467
+
2468
+ // Submit form
2469
+ const response = await fetch('/api/articles', {
2470
+ method: 'POST',
2471
+ headers: {
2472
+ 'Content-Type': 'application/json'
2473
+ },
2474
+ body: JSON.stringify({
2475
+ ...this.form,
2476
+ isDraft: false,
2477
+ publishedAt: new Date().toISOString()
2478
+ })
2479
+ })
2480
+
2481
+ if (response.ok) {
2482
+ this.showStatus('Article published successfully!', 'success')
2483
+ // Reset form or redirect
2484
+ } else {
2485
+ throw new Error('Publish failed')
2486
+ }
2487
+ } catch (error) {
2488
+ this.showStatus('Publish failed', 'error')
2489
+ }
2490
+ },
2491
+
2492
+ showStatus(message, type) {
2493
+ this.status = message
2494
+ this.statusClass = `status-${type}`
2495
+
2496
+ setTimeout(() => {
2497
+ this.status = ''
2498
+ this.statusClass = ''
2499
+ }, 3000)
2500
+ }
2501
+ }
2502
+ }
2503
+ </script>
2504
+
2505
+ <style scoped>
2506
+ .editor-form {
2507
+ max-width: 800px;
2508
+ margin: 0 auto;
2509
+ padding: 20px;
2510
+ }
2511
+
2512
+ .bbl-form-group {
2513
+ margin-bottom: 20px;
2514
+ }
2515
+
2516
+ .bbl-form-group label {
2517
+ display: block;
2518
+ margin-bottom: 8px;
2519
+ font-weight: 500;
2520
+ color: #374151;
2521
+ }
2522
+
2523
+ .form-control {
2524
+ width: 100%;
2525
+ padding: 10px 12px;
2526
+ border: 1px solid #d1d5db;
2527
+ border-radius: 6px;
2528
+ font-size: 14px;
2529
+ }
2530
+
2531
+ .form-control:focus {
2532
+ outline: none;
2533
+ border-color: #3b82f6;
2534
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
2535
+ }
2536
+
2537
+ .form-actions {
2538
+ display: flex;
2539
+ gap: 12px;
2540
+ margin-top: 30px;
2541
+ }
2542
+
2543
+ .btn {
2544
+ padding: 10px 20px;
2545
+ border: none;
2546
+ border-radius: 6px;
2547
+ font-weight: 500;
2548
+ cursor: pointer;
2549
+ transition: background-color 0.2s;
2550
+ }
2551
+
2552
+ .btn-primary {
2553
+ background: #3b82f6;
2554
+ color: white;
2555
+ }
2556
+
2557
+ .btn-primary:hover {
2558
+ background: #2563eb;
2559
+ }
2560
+
2561
+ .btn-secondary {
2562
+ background: #6b7280;
2563
+ color: white;
2564
+ }
2565
+
2566
+ .btn-secondary:hover {
2567
+ background: #4b5563;
2568
+ }
2569
+
2570
+ .form-status {
2571
+ margin-top: 20px;
2572
+ padding: 12px;
2573
+ border-radius: 6px;
2574
+ }
2575
+
2576
+ .status-success {
2577
+ background: #d1fae5;
2578
+ color: #065f46;
2579
+ border: 1px solid #a7f3d0;
2580
+ }
2581
+
2582
+ .status-error {
2583
+ background: #fee2e2;
2584
+ color: #991b1b;
2585
+ border: 1px solid #fca5a5;
2586
+ }
2587
+ </style>
2588
+ ```
2589
+
2590
+ This comprehensive guide covers all the components and usage patterns available in the Vue2 Premium BBL Editor package. Each example includes complete implementation details, event handling, and styling to help you integrate the components effectively in your projects.