tiptapify 0.0.1

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,651 @@
1
+ <script setup lang="ts">
2
+ import LinkDialog from "@tiptapify/components/extensions/components/LinkDialog.vue";
3
+ import { useEditor } from "@tiptapify/composable/useEditor";
4
+ import { computed, defineProps, ref } from 'vue'
5
+ import * as mdi from '@mdi/js'
6
+ import { useI18n } from "vue-i18n";
7
+
8
+ const props = defineProps({
9
+ variant: String,
10
+ })
11
+
12
+ const { t } = useI18n();
13
+
14
+ const { editor } = useEditor()
15
+ const editorInstance = ref(editor.getInstance())
16
+
17
+ const toolbarLinkButton = ref(null)
18
+
19
+ const fonts = [
20
+ {
21
+ name: 'Inter',
22
+ fontFamily: 'Inter'
23
+ },
24
+ {
25
+ name: 'Comic Sans',
26
+ fontFamily: 'Comic Sans MS, Comic Sans'
27
+ },
28
+ {
29
+ name: 'Serif',
30
+ fontFamily: 'serif'
31
+ },
32
+ {
33
+ name: 'Monospace',
34
+ fontFamily: 'monospace'
35
+ },
36
+ {
37
+ name: 'Cursive',
38
+ fontFamily: 'cursive'
39
+ },
40
+ ]
41
+
42
+ const fontSizes = [
43
+ 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 32, 48, 64, 96
44
+ ]
45
+
46
+ const lineHeights = [
47
+ 1, 1.5, 2, 3, 4
48
+ ]
49
+
50
+ const allMenuItems = ref([
51
+ /**
52
+ * todo
53
+ *
54
+ * font color, backgroundcolor
55
+ * tables
56
+ * unsetmarks, clearnodes
57
+ * media (image, video)
58
+ * unsetmarks, clearnodes
59
+ */
60
+ {
61
+ name: 'heading',
62
+ tooltip: 'style.heading',
63
+ icon: 'FormatHeaderPound',
64
+ modelValue: null,
65
+ enabled: true,
66
+ clearable: true,
67
+ attrs: {
68
+ click: () => editorInstance.value.chain().focus().setParagraph().run()
69
+ },
70
+ props: {
71
+ color: computed(() => editorInstance.value.isActive('heading') ? 'primary' : ''),
72
+ },
73
+ children: [
74
+ {
75
+ name: 'H1',
76
+ icon: 'FormatHeader1',
77
+ enabled: true,
78
+ props: {
79
+ color: computed(() => {
80
+ return editorInstance.value.isActive('heading', { level: 1 }) ? 'primary' : ''
81
+ }),
82
+ },
83
+ attrs: {
84
+ click: () => editorInstance.value.chain().focus().toggleHeading({ level: 1 }).run()
85
+ }
86
+ },
87
+ {
88
+ name: 'H2',
89
+ icon: 'FormatHeader2',
90
+ enabled: true,
91
+ props: {
92
+ color: computed(() => editorInstance.value.isActive('heading', { level: 2 }) ? 'primary' : ''),
93
+ },
94
+ attrs: {
95
+ click: () => editorInstance.value.chain().focus().toggleHeading({ level: 2 }).run()
96
+ }
97
+ },
98
+ {
99
+ name: 'H3',
100
+ icon: 'FormatHeader3',
101
+ enabled: true,
102
+ props: {
103
+ color: computed(() => editorInstance.value.isActive('heading', { level: 3 }) ? 'primary' : ''),
104
+ },
105
+ attrs: {
106
+ click: () => editorInstance.value.chain().focus().toggleHeading({ level: 3 }).run()
107
+ }
108
+ },
109
+ {
110
+ name: 'H4',
111
+ icon: 'FormatHeader4',
112
+ enabled: true,
113
+ props: {
114
+ color: computed(() => editorInstance.value.isActive('heading', { level: 4 }) ? 'primary' : ''),
115
+ },
116
+ attrs: {
117
+ click: () => editorInstance.value.chain().focus().toggleHeading({ level: 4 }).run()
118
+ }
119
+ },
120
+ {
121
+ name: 'H5',
122
+ icon: 'FormatHeader5',
123
+ enabled: true,
124
+ props: {
125
+ color: computed(() => editorInstance.value.isActive('heading', { level: 5 }) ? 'primary' : ''),
126
+ },
127
+ attrs: {
128
+ click: () => editorInstance.value.chain().focus().toggleHeading({ level: 5 }).run()
129
+ }
130
+ },
131
+ {
132
+ name: 'H6',
133
+ icon: 'FormatHeader6',
134
+ enabled: true,
135
+ props: {
136
+ color: computed(() => editorInstance.value.isActive('heading', { level: 6 }) ? 'primary' : ''),
137
+ },
138
+ attrs: {
139
+ click: () => editorInstance.value.chain().focus().toggleHeading({ level: 6 }).run()
140
+ }
141
+ },
142
+ ],
143
+ },
144
+ {
145
+ name: 'font-family',
146
+ tooltip: 'style.fontFamily',
147
+ icon: 'FormatFont',
148
+ modelValue: null,
149
+ enabled: true,
150
+ clearable: true,
151
+ attrs: {
152
+ click: () => editorInstance.value.chain().focus().unsetFontFamily().run()
153
+ },
154
+ children: fonts.map((font) => {
155
+ return {
156
+ name: font.name,
157
+ icon: '',
158
+ enabled: true,
159
+ props: {
160
+ color: computed(() => editorInstance.value.isActive('textStyle', { fontFamily: font.fontFamily }) ? 'primary' : ''),
161
+ style: `font-family: ${font.fontFamily};`
162
+ },
163
+ attrs: {
164
+ click: () => editorInstance.value.chain().focus().setFontFamily(font.fontFamily).run()
165
+ }
166
+ }
167
+ })
168
+ },
169
+ {
170
+ name: 'font-size',
171
+ tooltip: 'style.fontSize',
172
+ icon: 'FormatSize',
173
+ modelValue: computed(() => editorInstance.value.getAttributes('textStyle').fontSize || null),
174
+ enabled: true,
175
+ clearable: true,
176
+ attrs: {
177
+ click: () => editorInstance.value.chain().focus().unsetFontSize().run()
178
+ },
179
+ children: fontSizes.map((fontSize) => {
180
+ return {
181
+ name: `${fontSize}px`,
182
+ icon: '',
183
+ enabled: true,
184
+ props: {
185
+ color: computed(() => editorInstance.value.isActive('textStyle', { fontSizes: fontSize }) ? 'primary' : ''),
186
+ },
187
+ attrs: {
188
+ click: () => editorInstance.value.chain().focus().setFontSize(`${fontSize}px`).run()
189
+ }
190
+ }
191
+ })
192
+ },
193
+ {
194
+ name: 'line-height',
195
+ tooltip: 'style.lineHeight',
196
+ icon: 'FormatLineHeight',
197
+ modelValue: null,
198
+ enabled: true,
199
+ clearable: true,
200
+ attrs: {
201
+ click: () => editorInstance.value.chain().focus().unsetLineHeight().run()
202
+ },
203
+ children: lineHeights.map((lineHeight) => {
204
+ return {
205
+ name: lineHeight,
206
+ icon: '',
207
+ enabled: true,
208
+ props: {
209
+ color: computed(() => editorInstance.value.isActive('textStyle', { lineHeights: lineHeight }) ? 'primary' : ''),
210
+ },
211
+ attrs: {
212
+ click: () => editorInstance.value.chain().focus().setLineHeight(lineHeight).run()
213
+ }
214
+ }
215
+ })
216
+ },
217
+ {
218
+ name: '|'
219
+ },
220
+ {
221
+ name: 'bold',
222
+ tooltip: 'format.bold',
223
+ icon: 'FormatBold',
224
+ enabled: true,
225
+ props: {
226
+ disabled: computed(() => !editorInstance.value.can().chain().focus().toggleBold().run()),
227
+ color: computed(() => editorInstance.value.isActive('bold') ? 'primary' : ''),
228
+ },
229
+ attrs: {
230
+ click: () => editorInstance.value.chain().focus().toggleBold().run()
231
+ }
232
+ },
233
+ {
234
+ name: 'italic',
235
+ tooltip: 'format.italic',
236
+ icon: 'FormatItalic',
237
+ enabled: true,
238
+ props: {
239
+ disabled: computed(() => !editorInstance.value.can().chain().focus().toggleItalic().run()),
240
+ color: computed(() => editorInstance.value.isActive('italic') ? 'primary' : ''),
241
+ },
242
+ attrs: {
243
+ click: () => editorInstance.value.chain().focus().toggleItalic().run()
244
+ }
245
+ },
246
+ {
247
+ name: 'strike',
248
+ tooltip: 'format.strike',
249
+ icon: 'FormatStrikethroughVariant',
250
+ enabled: true,
251
+ props: {
252
+ disabled: computed(() => !editorInstance.value.can().chain().focus().toggleStrike().run()),
253
+ color: computed(() => editorInstance.value.isActive('strike') ? 'primary' : ''),
254
+ },
255
+ attrs: {
256
+ click: () => editorInstance.value.chain().focus().toggleStrike().run()
257
+ }
258
+ },
259
+ {
260
+ name: 'underline',
261
+ tooltip: 'format.underline',
262
+ icon: 'FormatUnderline',
263
+ enabled: true,
264
+ props: {
265
+ disabled: computed(() => !editorInstance.value.can().chain().focus().toggleUnderline().run()),
266
+ color: computed(() => editorInstance.value.isActive('underline') ? 'primary' : ''),
267
+ },
268
+ attrs: {
269
+ click: () => editorInstance.value.chain().focus().toggleUnderline().run()
270
+ }
271
+ },
272
+ {
273
+ name: 'highlight',
274
+ tooltip: 'format.highlight',
275
+ icon: 'Marker',
276
+ enabled: true,
277
+ props: {
278
+ disabled: computed(() => !editorInstance.value.can().chain().focus().toggleHighlight().run()),
279
+ color: computed(() => editorInstance.value.isActive('highlight') ? 'primary' : ''),
280
+ },
281
+ attrs: {
282
+ click: () => editorInstance.value.chain().focus().toggleHighlight().run()
283
+ }
284
+ },
285
+ {
286
+ name: '|'
287
+ },
288
+ {
289
+ name: 'code',
290
+ tooltip: 'format.code',
291
+ icon: 'Xml',
292
+ enabled: true,
293
+ props: {
294
+ disabled: computed(() => !editorInstance.value.can().chain().focus().toggleCode().run()),
295
+ color: computed(() => editorInstance.value.isActive('code') ? 'primary' : ''),
296
+ },
297
+ attrs: {
298
+ click: () => editorInstance.value.chain().focus().toggleCode().run()
299
+ }
300
+ },
301
+ {
302
+ name: 'codeblock_syntax',
303
+ tooltip: 'format.codeblock_syntax',
304
+ icon: 'CodeBlockBraces',
305
+ enabled: true,
306
+ props: {
307
+ disabled: computed(() => !editorInstance.value.can().chain().focus().toggleCodeBlock().run()),
308
+ color: computed(() => editorInstance.value.isActive('codeblock-lowlight') ? 'primary' : ''),
309
+ },
310
+ attrs: {
311
+ click: () => editorInstance.value.chain().focus().toggleCodeBlock().run()
312
+ }
313
+ },
314
+ {
315
+ name: 'blockquote',
316
+ tooltip: 'format.blockquote',
317
+ icon: 'CommentQuote',
318
+ enabled: true,
319
+ props: {
320
+ disabled: computed(() => !editorInstance.value.can().chain().focus().toggleBlockquote().run()),
321
+ color: computed(() => editorInstance.value.isActive('blockquote') ? 'primary' : ''),
322
+ },
323
+ attrs: {
324
+ click: () => editorInstance.value.chain().focus().toggleBlockquote().run()
325
+ }
326
+ },
327
+ {
328
+ name: '|'
329
+ },
330
+ {
331
+ name: 'sup',
332
+ tooltip: 'format.sup',
333
+ icon: 'FormatSuperscript',
334
+ enabled: true,
335
+ props: {
336
+ disabled: computed(() => !editorInstance.value.can().chain().focus().toggleSuperscript().run()),
337
+ color: computed(() => editorInstance.value.isActive('superscript') ? 'primary' : ''),
338
+ },
339
+ attrs: {
340
+ click: () => editorInstance.value.chain().focus().toggleSuperscript().run()
341
+ }
342
+ },
343
+ {
344
+ name: 'sub',
345
+ tooltip: 'format.sub',
346
+ icon: 'FormatSubscript',
347
+ enabled: true,
348
+ props: {
349
+ disabled: computed(() => !editorInstance.value.can().chain().focus().toggleSubscript().run()),
350
+ color: computed(() => editorInstance.value.isActive('subscript') ? 'primary' : ''),
351
+ },
352
+ attrs: {
353
+ click: () => editorInstance.value.chain().focus().toggleSubscript().run()
354
+ }
355
+ },
356
+ {
357
+ name: '|'
358
+ },
359
+ {
360
+ name: 'alignment',
361
+ tooltip: 'alignment',
362
+ icon: '',
363
+ enabled: true,
364
+ group: true,
365
+ children: [
366
+ {
367
+ name: 'alignments.left',
368
+ tooltip: 'alignments.left',
369
+ icon: 'FormatAlignLeft',
370
+ enabled: true,
371
+ props: {
372
+ color: computed(() => editorInstance.value.isActive('text-align', { align: 'left' }) ? 'primary' : ''),
373
+ },
374
+ attrs: {
375
+ click: () => editorInstance.value.chain().focus().toggleTextAlign('left').run()
376
+ }
377
+ },
378
+ {
379
+ name: 'alignments.center',
380
+ tooltip: 'alignments.center',
381
+ icon: 'FormatAlignCenter',
382
+ enabled: true,
383
+ props: {
384
+ color: computed(() => editorInstance.value.isActive('text-align', { align: 'center' }) ? 'primary' : ''),
385
+ },
386
+ attrs: {
387
+ click: () => editorInstance.value.chain().focus().toggleTextAlign('center').run()
388
+ }
389
+ },
390
+ {
391
+ name: 'alignments.right',
392
+ tooltip: 'alignments.right',
393
+ icon: 'FormatAlignRight',
394
+ enabled: true,
395
+ props: {
396
+ color: computed(() => editorInstance.value.isActive('text-align', { align: 'right' }) ? 'primary' : ''),
397
+ },
398
+ attrs: {
399
+ click: () => editorInstance.value.chain().focus().toggleTextAlign('right').run()
400
+ }
401
+ },
402
+ {
403
+ name: 'alignments.justify',
404
+ tooltip: 'alignments.justify',
405
+ icon: 'FormatAlignJustify',
406
+ enabled: true,
407
+ props: {
408
+ color: computed(() => editorInstance.value.isActive('text-align', { align: 'justify' }) ? 'primary' : ''),
409
+ },
410
+ attrs: {
411
+ click: () => editorInstance.value.chain().focus().toggleTextAlign('justify').run()
412
+ }
413
+ },
414
+ ]
415
+ },
416
+ {
417
+ name: '|'
418
+ },
419
+ {
420
+ name: 'list',
421
+ tooltip: 'list',
422
+ icon: 'FormatListGroup',
423
+ enabled: true,
424
+ group: true,
425
+ children: [
426
+ {
427
+ name: 'lists.bullet',
428
+ tooltip: 'lists.bullet',
429
+ icon: 'FormatListBulleted',
430
+ enabled: true,
431
+ props: {
432
+ color: computed(() => editorInstance.value.isActive('bulletList') ? 'primary' : ''),
433
+ },
434
+ attrs: {
435
+ click: () => editorInstance.value.chain().focus().toggleBulletList().run()
436
+ }
437
+ },
438
+ {
439
+ name: 'lists.numbered',
440
+ tooltip: 'lists.numbered',
441
+ icon: 'FormatListNumbered',
442
+ enabled: true,
443
+ props: {
444
+ color: computed(() => editorInstance.value.isActive('orderedList') ? 'primary' : ''),
445
+ },
446
+ attrs: {
447
+ click: () => editorInstance.value.chain().focus().toggleOrderedList().run()
448
+ }
449
+ },
450
+ {
451
+ name: 'lists.task',
452
+ tooltip: 'lists.task',
453
+ icon: 'FormatListChecks',
454
+ enabled: true,
455
+ props: {
456
+ color: computed(() => editorInstance.value.isActive('taskList') ? 'primary' : ''),
457
+ },
458
+ attrs: {
459
+ click: () => editorInstance.value.chain().focus().toggleTaskList().run()
460
+ }
461
+ },
462
+ {
463
+ name: 'lists.indent',
464
+ tooltip: 'lists.indent',
465
+ icon: 'FormatIndentIncrease',
466
+ enabled: true,
467
+ props: {
468
+ disabled: computed(() => !editorInstance.value.isActive('listItem')),
469
+ },
470
+ attrs: {
471
+ click: () => editorInstance.value.chain().focus().sinkListItem().run()
472
+ }
473
+ },
474
+ {
475
+ name: 'lists.outdent',
476
+ tooltip: 'lists.outdent',
477
+ icon: 'FormatIndentDecrease',
478
+ enabled: true,
479
+ props: {
480
+ disabled: computed(() => !editorInstance.value.isActive('listItem')),
481
+ },
482
+ attrs: {
483
+ click: () => editorInstance.value.chain().focus().liftListItem().run()
484
+ }
485
+ },
486
+ ]
487
+ },
488
+ {
489
+ name: '|'
490
+ },
491
+ {
492
+ name: 'format.link',
493
+ tooltip: 'format.link',
494
+ icon: computed(() => !editorInstance.value.isActive('link') ? 'Link' : 'LinkOff'),
495
+ enabled: true,
496
+ props: {
497
+ color: computed(() => editorInstance.value.isActive('link') ? 'primary' : ''),
498
+ },
499
+ attrs: {
500
+ click: computed(() => {
501
+ return editorInstance.value.isActive('link')
502
+ ? editorInstance.value.chain().focus().unsetLink().run
503
+ : toolbarLinkButton.value?.open
504
+ })
505
+ }
506
+ },
507
+ {
508
+ name: '|'
509
+ },
510
+ {
511
+ name: 'undo',
512
+ tooltip: 'action.undo',
513
+ icon: 'Undo',
514
+ enabled: true,
515
+ props: {
516
+ disabled: computed(() => !editorInstance.value.can().chain().focus().undo().run()),
517
+ },
518
+ attrs: {
519
+ click: () => editorInstance.value.chain().focus().undo().run()
520
+ }
521
+ },
522
+ {
523
+ name: 'redo',
524
+ tooltip: 'action.redo',
525
+ icon: 'Redo',
526
+ enabled: true,
527
+ props: {
528
+ disabled: computed(() => !editorInstance.value.can().chain().focus().redo().run()),
529
+ },
530
+ attrs: {
531
+ click: () => editorInstance.value.chain().focus().redo().run()
532
+ }
533
+ },
534
+ {
535
+ name: '|'
536
+ },
537
+ {
538
+ name: 'line',
539
+ tooltip: 'format.line',
540
+ icon: 'Minus',
541
+ enabled: true,
542
+ props: {},
543
+ attrs: {
544
+ click: () => editorInstance.value.chain().focus().setHorizontalRule().run()
545
+ }
546
+ },
547
+ {
548
+ name: 'break',
549
+ tooltip: 'format.break',
550
+ icon: 'FormatPageBreak',
551
+ enabled: true,
552
+ props: {},
553
+ attrs: {
554
+ click: () => editorInstance.value.chain().focus().setHardBreak().run()
555
+ }
556
+ },
557
+ ])
558
+
559
+ function getIcon(icon: string) {
560
+ return `${mdi[`mdi${icon}`]}`
561
+ }
562
+ </script>
563
+
564
+ <template>
565
+ <div v-if="editor" class="d-flex flex-wrap gap-x-4 gap-y-2 tiptapify-menu">
566
+ <template v-for="(menuItem, key) in allMenuItems" :key="key">
567
+ <VDivider v-if="menuItem.name === '|'" vertical class="menu-divider" />
568
+
569
+ <template v-else-if="menuItem.enabled">
570
+ <template v-if="menuItem.children">
571
+ <VBtnToggle v-if="menuItem.group" :variant="variant">
572
+ <VBtn
573
+ v-for="(item, key) of menuItem.children"
574
+ v-bind="{ ...props, ...item.props}" v-on="item.attrs" size="32"
575
+ :key="`${item.name}-${key}`"
576
+ >
577
+ <VTooltip :text="t(item.name)" location="top" activator="parent" />
578
+
579
+ <VIcon v-if="item.icon" :icon="getIcon(item.icon)" size="small" />
580
+ <span v-else class="menu-item-title">
581
+ {{ t(menuItem.name) }}
582
+ </span>
583
+ </VBtn>
584
+ </VBtnToggle>
585
+
586
+ <VMenu v-else>
587
+ <template #activator="{ props: menuProps }">
588
+ <VBtn :variant="variant" v-bind="menuProps" size="32" class="menu-button">
589
+ <VTooltip :text="t(menuItem.tooltip)" location="top" activator="parent" />
590
+
591
+ <VIcon v-if="menuItem.icon" :icon="getIcon(menuItem.icon)" size="small" />
592
+ <span v-else class="menu-item-title">
593
+ {{ t(menuItem.name) }}
594
+ </span>
595
+ </VBtn>
596
+ </template>
597
+
598
+ <VList v-model="menuItem.modelValue">
599
+ <template v-for="(item, itemKey) in menuItem.children" :key="itemKey">
600
+ <VListItem :value="item.name" density="compact" v-bind="item.props" v-on="item.attrs">
601
+ <VTooltip v-if="item.tooltip" :text="t(item.tooltip)" location="top" activator="parent" />
602
+
603
+ <VListItemTitle>
604
+ <VIcon v-if="item.icon" :icon="getIcon(item.icon)" size="small" />
605
+ <span v-else class="menu-item-title">
606
+ {{ t(item.name) }}
607
+ </span>
608
+ </VListItemTitle>
609
+ </VListItem>
610
+ </template>
611
+ </VList>
612
+ </VMenu>
613
+ </template>
614
+
615
+ <VBtn v-else :variant="variant" v-bind="menuItem.props" v-on="menuItem.attrs" class="menu-button" size="32">
616
+ <VTooltip :text="t(menuItem.tooltip)" location="top" activator="parent" />
617
+
618
+ <VIcon v-if="menuItem.icon" :icon="getIcon(menuItem.icon)" size="16" />
619
+ <span v-else class="menu-item-title">
620
+ {{ t(menuItem.name) }}
621
+ </span>
622
+ </VBtn>
623
+ </template>
624
+ </template>
625
+
626
+ <LinkDialog ref="toolbarLinkButton" />
627
+ </div>
628
+ </template>
629
+
630
+ <style lang="scss" scoped>
631
+ .tiptapify-menu {
632
+ padding: 8px;
633
+ border-bottom: var(--border);
634
+ }
635
+
636
+ :deep(.v-btn-group) {
637
+ height: 32px !important;
638
+ }
639
+
640
+ .menu-item-title {
641
+ font-size: 14px;
642
+ }
643
+
644
+ .menu-button {
645
+ margin: 0 1px;
646
+ }
647
+
648
+ .v-divider.menu-divider {
649
+ margin: 0 10px;
650
+ }
651
+ </style>