|
30 | 30 | @click="commands[btn].action(editor)"></button>
|
31 | 31 | </div>
|
32 | 32 | </div>
|
| 33 | + <bubble-menu :editor="editor" |
| 34 | + :tippy-options="{ duration: 100 }" |
| 35 | + v-if="editor && bubbleToolbar"> |
| 36 | + <div class="toolbar bubble"> |
| 37 | + <div v-for="btnGroup in bubbleToolbar" class="button-group" :class="{'gap' : btnGroup.length === 0}"> |
| 38 | + <button v-for="btn in btnGroup" |
| 39 | + type="button" :key="btn" |
| 40 | + :class="[commands[btn].class, {'is-active': commands[btn].active?.(editor)}]" |
| 41 | + :aria-pressed="commands[btn].active?.(editor)" |
| 42 | + :disabled="commands[btn].disabled?.(editor)" |
| 43 | + :aria-disabled="commands[btn].disabled?.(editor)" |
| 44 | + :title="$vui.i18n().wysiwyg[btn]" |
| 45 | + :aria-label="$vui.i18n().wysiwyg[btn]" |
| 46 | + @click="commands[btn].action(editor)"></button> |
| 47 | + </div> |
| 48 | + </div> |
| 49 | + </bubble-menu> |
33 | 50 | <editor-content :editor="editor" class="editor"></editor-content>
|
34 | 51 | </template>
|
35 | 52 | </div>
|
|
39 | 56 | import { watch, ref } from 'vue'
|
40 | 57 |
|
41 | 58 | // import mandatory tiptap components
|
42 |
| - import { useEditor, EditorContent } from '@tiptap/vue-3' |
| 59 | + import { useEditor, EditorContent, BubbleMenu } from '@tiptap/vue-3' |
43 | 60 | import Document from '@tiptap/extension-document'
|
44 | 61 | import Paragraph from '@tiptap/extension-paragraph'
|
45 | 62 | import Text from '@tiptap/extension-text'
|
|
64 | 81 | import Subscript from '@tiptap/extension-subscript'
|
65 | 82 | import Superscript from '@tiptap/extension-superscript'
|
66 | 83 |
|
67 |
| - const TextAlign = RawTextAlign.configure({ |
68 |
| - types: ['heading', 'paragraph'], |
69 |
| - }) |
70 |
| - |
71 |
| - // Consider exposing additional configuration, for example for domain whitelist https://tiptap.dev/docs/editor/extensions/marks/link#validate |
72 |
| - const Link = RawLink.configure({ |
73 |
| - openOnClick: false, |
74 |
| - }) |
75 |
| - |
| 84 | + // props and model |
76 | 85 | let displayHtml = ref(false);
|
77 | 86 |
|
78 | 87 | const model = defineModel();
|
|
81 | 90 | toolbar: {
|
82 | 91 | type: Array,
|
83 | 92 | default: [['bold', 'italic', 'underline'], ['unordered', 'ordered', 'outdent', 'indent'], [], ['undo', 'redo'], ['viewsource']]
|
| 93 | + }, |
| 94 | + bubbleToolbar: { |
| 95 | + type: Array, |
| 96 | + default: null |
| 97 | + }, |
| 98 | + linkConfiguration: { |
| 99 | + type: Object, |
| 100 | + default: {} |
84 | 101 | }
|
85 | 102 | })
|
86 | 103 |
|
| 104 | + // Configure extensions |
| 105 | + const TextAlign = RawTextAlign.configure({ |
| 106 | + types: ['heading', 'paragraph'], |
| 107 | + }) |
| 108 | + |
| 109 | + // Example for domain whitelist https://tiptap.dev/docs/editor/extensions/marks/link#validate |
| 110 | + const Link = RawLink.configure({ |
| 111 | + openOnClick: false, |
| 112 | + ...props.linkConfiguration |
| 113 | + }) |
87 | 114 |
|
| 115 | + // commands definitions |
88 | 116 | const commands = {
|
89 | 117 | 'bold' : {
|
90 | 118 | class: 'mdi mdi-format-bold',
|
|
331 | 359 | padding: 0.3rem;
|
332 | 360 | font-size: 18px;
|
333 | 361 |
|
| 362 | + &.bubble { |
| 363 | + background-color: white; |
| 364 | + border-radius: 0.7rem; |
| 365 | + box-shadow: 5px 5px 10px #ccc; |
| 366 | + padding: 0.2rem; |
| 367 | + } |
| 368 | + |
334 | 369 | .button-group {
|
335 | 370 | position: relative;
|
336 | 371 | margin: 0 4px;
|
|
0 commit comments