@kvass/rich-text 1.4.18 → 1.5.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/index.js +38 -2
- package/package.json +5 -1
- package/src/Component.vue +183 -46
package/index.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import Component from './src/Component.vue'
|
|
1
|
+
import Component, { Tiptap } from './src/Component.vue'
|
|
2
|
+
import Link from '@tiptap/extension-link'
|
|
3
|
+
import Superscript from '@tiptap/extension-superscript'
|
|
4
|
+
import Table from '@tiptap/extension-table'
|
|
5
|
+
import TableRow from '@tiptap/extension-table-row'
|
|
6
|
+
import TableCell from '@tiptap/extension-table-cell'
|
|
7
|
+
import TableHeader from '@tiptap/extension-table-header'
|
|
8
|
+
|
|
9
|
+
import { NodeViewWrapper, NodeViewContent, nodeViewProps, VueNodeViewRenderer } from '@tiptap/vue-2'
|
|
10
|
+
|
|
2
11
|
export default Component
|
|
3
12
|
|
|
4
13
|
const Options = {
|
|
@@ -6,6 +15,16 @@ const Options = {
|
|
|
6
15
|
primary: 'Primærfarge',
|
|
7
16
|
secondary: 'Sekundærfarge',
|
|
8
17
|
},
|
|
18
|
+
commands: [
|
|
19
|
+
'heading',
|
|
20
|
+
'heading-h3',
|
|
21
|
+
'bold',
|
|
22
|
+
'italic',
|
|
23
|
+
'superscript',
|
|
24
|
+
'orderedList',
|
|
25
|
+
'bulletList',
|
|
26
|
+
'link',
|
|
27
|
+
],
|
|
9
28
|
}
|
|
10
29
|
const setup = options => {
|
|
11
30
|
for (let key in options) {
|
|
@@ -14,4 +33,21 @@ const setup = options => {
|
|
|
14
33
|
}
|
|
15
34
|
}
|
|
16
35
|
|
|
17
|
-
|
|
36
|
+
const DefaultCommands = Options.commands
|
|
37
|
+
|
|
38
|
+
export {
|
|
39
|
+
setup,
|
|
40
|
+
Options,
|
|
41
|
+
DefaultCommands,
|
|
42
|
+
Tiptap,
|
|
43
|
+
Link,
|
|
44
|
+
Superscript,
|
|
45
|
+
Table,
|
|
46
|
+
TableRow,
|
|
47
|
+
TableCell,
|
|
48
|
+
TableHeader,
|
|
49
|
+
NodeViewWrapper,
|
|
50
|
+
NodeViewContent,
|
|
51
|
+
nodeViewProps,
|
|
52
|
+
VueNodeViewRenderer,
|
|
53
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kvass/rich-text",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "rich text component for kvass",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
"@tiptap/extension-image": "^2.2.2",
|
|
38
38
|
"@tiptap/extension-link": "^2.2.2",
|
|
39
39
|
"@tiptap/extension-superscript": "^2.2.2",
|
|
40
|
+
"@tiptap/extension-table": "^2.27.2",
|
|
41
|
+
"@tiptap/extension-table-cell": "^2.27.2",
|
|
42
|
+
"@tiptap/extension-table-header": "^2.27.2",
|
|
43
|
+
"@tiptap/extension-table-row": "^2.27.2",
|
|
40
44
|
"@tiptap/pm": "^2.2.2",
|
|
41
45
|
"@tiptap/starter-kit": "^2.2.2",
|
|
42
46
|
"@tiptap/vue-2": "^2.2.2"
|
package/src/Component.vue
CHANGED
|
@@ -36,11 +36,7 @@
|
|
|
36
36
|
'is-active': editor.isActive(item.isActiveKey || item.stateKey, item.commandProps),
|
|
37
37
|
}"
|
|
38
38
|
:disabled="disabled || item.disabled"
|
|
39
|
-
@click="
|
|
40
|
-
item.customFunc
|
|
41
|
-
? item.customFunc()
|
|
42
|
-
: editor.chain().focus()[item.commandKey](item.commandProps).run()
|
|
43
|
-
"
|
|
39
|
+
@click="() => onMenuBarButtonClick(item)"
|
|
44
40
|
type="button"
|
|
45
41
|
>
|
|
46
42
|
<FontAwesomeIcon :icon="item.icon" />
|
|
@@ -61,20 +57,39 @@
|
|
|
61
57
|
'is-active': editor.isActive(item.isActiveKey || item.stateKey, item.commandProps),
|
|
62
58
|
}"
|
|
63
59
|
:disabled="disabled || item.disabled"
|
|
64
|
-
@click="
|
|
65
|
-
item.customFunc
|
|
66
|
-
? item.customFunc()
|
|
67
|
-
: editor.chain().focus()[item.commandKey](item.commandProps).run()
|
|
68
|
-
"
|
|
60
|
+
@click="() => onMenuBarButtonClick(item)"
|
|
69
61
|
>
|
|
70
62
|
<FontAwesomeIcon :icon="item.icon" size="sm" />
|
|
71
63
|
</button>
|
|
72
64
|
</div>
|
|
65
|
+
|
|
66
|
+
<transition name="toolbar">
|
|
67
|
+
<div v-if="editor.isActive('table') && tableExtension" class="richtext__menubar-table">
|
|
68
|
+
<div class="richtext__menubar-buttons">
|
|
69
|
+
<template v-for="(ctrl, index) in tableExtension.controls">
|
|
70
|
+
<div v-if="ctrl.separator" :key="'sep-' + index" class="richtext__separator"></div>
|
|
71
|
+
<button
|
|
72
|
+
v-else
|
|
73
|
+
:key="'tbl-' + index"
|
|
74
|
+
type="button"
|
|
75
|
+
:class="{ 'is-confirming': confirmationIndex === index }"
|
|
76
|
+
:disabled="!editor.can()[ctrl.command]()"
|
|
77
|
+
@mousedown.prevent
|
|
78
|
+
@click="handleTableControl(ctrl, index)"
|
|
79
|
+
:title="ctrl.label"
|
|
80
|
+
>
|
|
81
|
+
<Icon :icon="ctrl.icon" size="lg" />
|
|
82
|
+
</button>
|
|
83
|
+
</template>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</transition>
|
|
73
87
|
</div>
|
|
74
88
|
</div>
|
|
75
89
|
</template>
|
|
76
90
|
|
|
77
91
|
<script>
|
|
92
|
+
import * as Tiptap from '@tiptap/core'
|
|
78
93
|
import { Editor, EditorContent, BubbleMenu } from '@tiptap/vue-2'
|
|
79
94
|
import Link from '@tiptap/extension-link'
|
|
80
95
|
import Image from '@tiptap/extension-image'
|
|
@@ -82,22 +97,12 @@ import Superscript from '@tiptap/extension-superscript'
|
|
|
82
97
|
import CallToAction from './extensions/cta'
|
|
83
98
|
import StarterKit from '@tiptap/starter-kit'
|
|
84
99
|
|
|
85
|
-
const DefaultCommands = [
|
|
86
|
-
'heading',
|
|
87
|
-
'heading-h3',
|
|
88
|
-
'bold',
|
|
89
|
-
'italic',
|
|
90
|
-
'superscript',
|
|
91
|
-
'orderedList',
|
|
92
|
-
'bulletList',
|
|
93
|
-
'link',
|
|
94
|
-
'cta',
|
|
95
|
-
]
|
|
96
|
-
|
|
97
100
|
import { Options } from '../index'
|
|
98
101
|
|
|
99
|
-
export {
|
|
102
|
+
export { Tiptap }
|
|
100
103
|
export default {
|
|
104
|
+
name: 'RichText',
|
|
105
|
+
inject: ['RichTextOptions'],
|
|
101
106
|
props: {
|
|
102
107
|
value: {
|
|
103
108
|
type: String,
|
|
@@ -106,7 +111,10 @@ export default {
|
|
|
106
111
|
disabled: Boolean,
|
|
107
112
|
commands: {
|
|
108
113
|
type: Array,
|
|
109
|
-
default
|
|
114
|
+
default() {
|
|
115
|
+
const hasInject = this.RichTextOptions && this.RichTextOptions.commands
|
|
116
|
+
return hasInject ? this.RichTextOptions.commands : Options.commands
|
|
117
|
+
},
|
|
110
118
|
},
|
|
111
119
|
labels: {
|
|
112
120
|
type: Object,
|
|
@@ -134,6 +142,7 @@ export default {
|
|
|
134
142
|
linkUrl: null,
|
|
135
143
|
bubbleState: null,
|
|
136
144
|
bubbleOptions: { theme: 'primary' },
|
|
145
|
+
confirmationIndex: null,
|
|
137
146
|
}
|
|
138
147
|
},
|
|
139
148
|
computed: {
|
|
@@ -217,11 +226,24 @@ export default {
|
|
|
217
226
|
},
|
|
218
227
|
disabled: this.editor && this.editor.state.selection.empty,
|
|
219
228
|
icon: ['far', 'link'],
|
|
220
|
-
extension: Link
|
|
229
|
+
extension: Link.extend({
|
|
230
|
+
priority: 100,
|
|
231
|
+
parseHTML() {
|
|
232
|
+
return [{ tag: 'a[href]:not([data-cta])' }]
|
|
233
|
+
},
|
|
234
|
+
}).configure({
|
|
235
|
+
openOnClick: false,
|
|
236
|
+
autolink: true,
|
|
237
|
+
enableClickSelection: true,
|
|
238
|
+
HTMLAttributes: {
|
|
239
|
+
rel: 'noopener noreferrer',
|
|
240
|
+
},
|
|
241
|
+
}),
|
|
221
242
|
appearance: {
|
|
222
243
|
menu: true,
|
|
223
244
|
bubble: true,
|
|
224
245
|
},
|
|
246
|
+
priority: 100,
|
|
225
247
|
},
|
|
226
248
|
{
|
|
227
249
|
stateKey: 'image',
|
|
@@ -234,31 +256,28 @@ export default {
|
|
|
234
256
|
bubble: false,
|
|
235
257
|
},
|
|
236
258
|
},
|
|
237
|
-
{
|
|
238
|
-
stateKey: 'cta',
|
|
239
|
-
commandKey: 'toggleCta',
|
|
240
|
-
customFunc: () => {
|
|
241
|
-
this.bubbleState = 'cta'
|
|
242
|
-
},
|
|
243
|
-
disabled: this.editor && this.editor.state.selection.empty,
|
|
244
|
-
icon: ['far', 'mouse-pointer'],
|
|
245
|
-
extension: CallToAction,
|
|
246
|
-
appearance: {
|
|
247
|
-
menu: true,
|
|
248
|
-
bubble: true,
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
259
|
]
|
|
252
260
|
},
|
|
261
|
+
tableExtension() {
|
|
262
|
+
return this.commands.find(cmd => cmd.stateKey === 'table')
|
|
263
|
+
},
|
|
253
264
|
},
|
|
254
265
|
methods: {
|
|
255
266
|
getCommands(appearance) {
|
|
256
267
|
return this.commands
|
|
257
|
-
.map(cmd =>
|
|
268
|
+
.map(cmd => {
|
|
269
|
+
if (typeof cmd === 'string') return this.availableCommands.find(a => a.stateKey === cmd)
|
|
270
|
+
if (typeof cmd === 'function') return cmd.call(this)
|
|
271
|
+
return cmd
|
|
272
|
+
})
|
|
258
273
|
.filter(Boolean)
|
|
259
274
|
.filter(cmd => !appearance || cmd.appearance[appearance])
|
|
260
275
|
},
|
|
276
|
+
onMenuBarButtonClick(item) {
|
|
277
|
+
if (item.customFunc) return item.customFunc.call(this, { editor: this.editor })
|
|
261
278
|
|
|
279
|
+
return this.editor.chain().focus()[item.commandKey](item.commandProps).run()
|
|
280
|
+
},
|
|
262
281
|
setLink(value) {
|
|
263
282
|
let editor = this.editor.chain().focus()
|
|
264
283
|
switch (this.bubbleState) {
|
|
@@ -304,6 +323,19 @@ export default {
|
|
|
304
323
|
if (!typeof value === 'string') return
|
|
305
324
|
return ['/', '#'].some(v => value.startsWith(v))
|
|
306
325
|
},
|
|
326
|
+
handleTableControl(ctrl, index) {
|
|
327
|
+
if (ctrl.requiresConfirmation) {
|
|
328
|
+
if (this.confirmationIndex === index) {
|
|
329
|
+
this.editor.chain().focus()[ctrl.command]().run()
|
|
330
|
+
this.confirmationIndex = null
|
|
331
|
+
} else {
|
|
332
|
+
this.confirmationIndex = index
|
|
333
|
+
}
|
|
334
|
+
} else {
|
|
335
|
+
this.confirmationIndex = null
|
|
336
|
+
this.editor.chain().focus()[ctrl.command]().run()
|
|
337
|
+
}
|
|
338
|
+
},
|
|
307
339
|
},
|
|
308
340
|
mounted() {
|
|
309
341
|
this.editor = new Editor({
|
|
@@ -312,13 +344,12 @@ export default {
|
|
|
312
344
|
StarterKit,
|
|
313
345
|
...this.getCommands()
|
|
314
346
|
.map(({ extension }) => extension)
|
|
347
|
+
.flat()
|
|
315
348
|
.filter(Boolean),
|
|
316
349
|
],
|
|
317
350
|
content: this.value,
|
|
318
351
|
onUpdate: () => {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
this.$emit('input', html)
|
|
352
|
+
this.$emit('input', this.editor.getHTML())
|
|
322
353
|
},
|
|
323
354
|
onSelectionUpdate: ({ editor }) => {
|
|
324
355
|
if (!this.bubbleShouldShow({ editor })) this.bubbleState = null
|
|
@@ -331,8 +362,6 @@ export default {
|
|
|
331
362
|
components: {
|
|
332
363
|
EditorContent,
|
|
333
364
|
BubbleMenu,
|
|
334
|
-
// EditorMenuBar,
|
|
335
|
-
// EditorMenuBubble,
|
|
336
365
|
},
|
|
337
366
|
}
|
|
338
367
|
</script>
|
|
@@ -343,6 +372,8 @@ export default {
|
|
|
343
372
|
flex-grow: 1;
|
|
344
373
|
display: flex;
|
|
345
374
|
flex-direction: column-reverse;
|
|
375
|
+
background-color: #ffffff;
|
|
376
|
+
border-radius: var(--border-radius, 3px);
|
|
346
377
|
|
|
347
378
|
&--disabled {
|
|
348
379
|
background-color: var(--vue-elder-input-color, #fafafa);
|
|
@@ -397,7 +428,8 @@ export default {
|
|
|
397
428
|
border-radius: var(--border-radius);
|
|
398
429
|
text-decoration: none;
|
|
399
430
|
|
|
400
|
-
display: inline-
|
|
431
|
+
display: inline-flex;
|
|
432
|
+
gap: 0.5em;
|
|
401
433
|
}
|
|
402
434
|
|
|
403
435
|
a[data-cta='primary'] {
|
|
@@ -481,17 +513,41 @@ export default {
|
|
|
481
513
|
}
|
|
482
514
|
}
|
|
483
515
|
|
|
516
|
+
&__separator {
|
|
517
|
+
width: 1px;
|
|
518
|
+
height: 1.25rem;
|
|
519
|
+
background-color: var(--border-color);
|
|
520
|
+
margin: 0 0.5rem;
|
|
521
|
+
align-self: center;
|
|
522
|
+
}
|
|
523
|
+
|
|
484
524
|
&__menubar {
|
|
485
525
|
padding: 0.75rem;
|
|
486
526
|
position: sticky;
|
|
487
527
|
top: 0;
|
|
488
528
|
z-index: 1;
|
|
489
529
|
|
|
530
|
+
display: flex;
|
|
531
|
+
flex-direction: column;
|
|
532
|
+
|
|
490
533
|
&-buttons {
|
|
491
534
|
width: max-content;
|
|
492
535
|
box-shadow: 0 2px 4px -2px rgba(black, 0.1);
|
|
493
536
|
}
|
|
494
537
|
|
|
538
|
+
&-table {
|
|
539
|
+
animation: fadeIn 0.2s ease-in-out;
|
|
540
|
+
margin-top: 0.5rem;
|
|
541
|
+
|
|
542
|
+
.richtext__menubar-buttons {
|
|
543
|
+
background-color: #f8f9fa;
|
|
544
|
+
border: 1px solid var(--border-color);
|
|
545
|
+
border-radius: var(--border-radius);
|
|
546
|
+
display: flex;
|
|
547
|
+
margin-top: 0.5rem;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
495
551
|
button {
|
|
496
552
|
padding: 0.5rem;
|
|
497
553
|
min-width: 2.5rem;
|
|
@@ -527,6 +583,16 @@ export default {
|
|
|
527
583
|
&.is-active {
|
|
528
584
|
background-color: darken(#ffffff, 3%);
|
|
529
585
|
}
|
|
586
|
+
|
|
587
|
+
&.is-confirming {
|
|
588
|
+
background-color: #e3342f;
|
|
589
|
+
color: white !important;
|
|
590
|
+
border-color: #e3342f !important;
|
|
591
|
+
|
|
592
|
+
&:hover {
|
|
593
|
+
background-color: darken($color: #e3342f, $amount: 10%) !important;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
530
596
|
}
|
|
531
597
|
}
|
|
532
598
|
|
|
@@ -537,5 +603,76 @@ export default {
|
|
|
537
603
|
.tippy-box {
|
|
538
604
|
max-width: initial !important;
|
|
539
605
|
}
|
|
606
|
+
|
|
607
|
+
.toolbar-enter-active,
|
|
608
|
+
.toolbar-leave-active {
|
|
609
|
+
transition: all 0.25s ease-out;
|
|
610
|
+
max-height: 50px;
|
|
611
|
+
opacity: 1;
|
|
612
|
+
transform: translateY(0);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.toolbar-enter,
|
|
616
|
+
.toolbar-leave-to {
|
|
617
|
+
max-height: 0;
|
|
618
|
+
opacity: 0;
|
|
619
|
+
margin-top: 0;
|
|
620
|
+
transform: translateY(-5px);
|
|
621
|
+
overflow: hidden;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
&__editor {
|
|
625
|
+
.ProseMirror {
|
|
626
|
+
table {
|
|
627
|
+
border-collapse: collapse;
|
|
628
|
+
table-layout: fixed;
|
|
629
|
+
width: 100%;
|
|
630
|
+
margin: 0;
|
|
631
|
+
overflow: hidden;
|
|
632
|
+
|
|
633
|
+
td,
|
|
634
|
+
th {
|
|
635
|
+
min-width: 1em;
|
|
636
|
+
border: 2px solid #ced4da;
|
|
637
|
+
padding: 3px 5px;
|
|
638
|
+
vertical-align: top;
|
|
639
|
+
box-sizing: border-box;
|
|
640
|
+
position: relative;
|
|
641
|
+
|
|
642
|
+
> * {
|
|
643
|
+
margin-bottom: 0;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
th {
|
|
648
|
+
font-weight: bold;
|
|
649
|
+
text-align: left;
|
|
650
|
+
background-color: #f1f3f5;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
.selectedCell:after {
|
|
654
|
+
z-index: 2;
|
|
655
|
+
position: absolute;
|
|
656
|
+
content: '';
|
|
657
|
+
left: 0;
|
|
658
|
+
right: 0;
|
|
659
|
+
top: 0;
|
|
660
|
+
bottom: 0;
|
|
661
|
+
background: rgba(200, 200, 255, 0.4);
|
|
662
|
+
pointer-events: none;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
.column-resize-handle {
|
|
666
|
+
position: absolute;
|
|
667
|
+
right: -2px;
|
|
668
|
+
top: 0;
|
|
669
|
+
bottom: 0;
|
|
670
|
+
width: 4px;
|
|
671
|
+
background-color: #adf;
|
|
672
|
+
pointer-events: none;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
540
677
|
}
|
|
541
678
|
</style>
|