tiptapify 0.0.6 → 0.0.8

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.
Files changed (42) hide show
  1. package/README.md +4 -2
  2. package/dist/tiptapify.css +1 -1
  3. package/dist/{tiptapify.es.js → tiptapify.mjs} +54979 -52123
  4. package/dist/tiptapify.umd.js +41 -43
  5. package/package.json +8 -8
  6. package/src/components/Footer.vue +5 -6
  7. package/src/components/MenuBubble.vue +41 -39
  8. package/src/components/MenuFloating.vue +10 -10
  9. package/src/components/Tiptapify.vue +95 -14
  10. package/src/components/Toolbar/Group.vue +8 -43
  11. package/src/components/Toolbar/GroupDropdown.vue +85 -0
  12. package/src/components/Toolbar/Index.vue +20 -19
  13. package/src/components/Toolbar/items/media.ts +195 -9
  14. package/src/components/Toolbar/items/misc.ts +10 -1
  15. package/src/components/Toolbar/items/style.ts +2 -2
  16. package/src/components/Toolbar/items.ts +3 -3
  17. package/src/components/editorExtensions.ts +11 -7
  18. package/src/components/index.ts +13 -0
  19. package/src/extensions/components/ImageDialog.vue +142 -0
  20. package/src/extensions/components/LinkDialog.vue +142 -0
  21. package/src/extensions/components/PreviewDialog.vue +44 -0
  22. package/src/{components/extensions/components/ShowSource.vue → extensions/components/ShowSourceDialog.vue} +16 -10
  23. package/src/extensions/components/TableBuilder.vue +138 -0
  24. package/src/extensions/image.ts +31 -0
  25. package/src/extensions/link.ts +24 -0
  26. package/src/extensions/preview.ts +40 -0
  27. package/src/{components/extensions → extensions}/view-source.ts +1 -6
  28. package/src/i18n/locales/de.json +78 -45
  29. package/src/i18n/locales/en.json +37 -4
  30. package/src/i18n/locales/es.json +46 -13
  31. package/src/i18n/locales/fr.json +54 -21
  32. package/src/i18n/locales/it.json +51 -18
  33. package/src/i18n/locales/pl.json +45 -12
  34. package/src/i18n/locales/ru.json +36 -3
  35. package/src/i18n/locales/ua.json +36 -3
  36. package/src/index.ts +0 -1
  37. package/src/utils/helpers.ts +17 -0
  38. package/src/components/extensions/components/LinkDialog.vue +0 -98
  39. package/src/composable/useEditor.ts +0 -35
  40. /package/src/{components/extensions → extensions}/components/slashCommands/CommandsList.vue +0 -0
  41. /package/src/{components/extensions → extensions}/components/slashCommands/suggestion.ts +0 -0
  42. /package/src/{components/extensions → extensions}/slash-commands.ts +0 -0
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "tiptapify",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Tiptap3 editor with Vuetify3 menu implementation",
5
5
  "exports": {
6
6
  ".": {
7
7
  "import": {
8
- "default": "./dist/tiptapify.es.js",
8
+ "default": "./dist/tiptapify.mjs",
9
9
  "development": "./src/index.ts"
10
10
  },
11
11
  "require": {
@@ -18,7 +18,7 @@
18
18
  "./style.css": "./dist/tiptapify.css"
19
19
  },
20
20
  "main": "./dist/tiptapify.umd.cjs",
21
- "module": "./dist/tiptapify.js",
21
+ "module": "./dist/tiptapify.mjs",
22
22
  "source": "./src/index.ts",
23
23
  "files": [
24
24
  "dist",
@@ -49,7 +49,7 @@
49
49
  "author": "Igor Voytovich",
50
50
  "license": "MIT",
51
51
  "repository": "https://github.com/IVoyt/tiptapify",
52
- "packageManager": "pnpm@10.11.0",
52
+ "packageManager": "pnpm@10.11.1",
53
53
  "dependencies": {
54
54
  "@tiptap/core": "next",
55
55
  "@tiptap/extension-blockquote": "next",
@@ -92,7 +92,7 @@
92
92
  "highlight.js": "^11.11.1",
93
93
  "linkifyjs": "^4.3.1",
94
94
  "lowlight": "^3.3.0",
95
- "vue-i18n": "^11.1.4"
95
+ "vue-i18n": "^11.1.5"
96
96
  },
97
97
  "peerDependencies": {
98
98
  "@mdi/js": "^7.4.47",
@@ -102,13 +102,13 @@
102
102
  "devDependencies": {
103
103
  "@intlify/unplugin-vue-i18n": "^6.0.8",
104
104
  "@rollup/plugin-alias": "^5.1.1",
105
- "@types/node": "^22.15.21",
105
+ "@types/node": "^22.15.30",
106
106
  "@vitejs/plugin-vue": "^5.2.4",
107
107
  "@vitejs/plugin-vue-jsx": "^4.2.0",
108
108
  "rollup-plugin-tsconfig-paths": "^1.5.2",
109
- "sass-embedded": "^1.89.0",
109
+ "sass-embedded": "^1.89.1",
110
110
  "typescript": "^5.8.3",
111
- "unplugin-vue-components": "^28.5.0",
111
+ "unplugin-vue-components": "^28.7.0",
112
112
  "vite": "^6.3.5",
113
113
  "vite-plugin-vuetify": "^2.1.1",
114
114
  "vite-svg-loader": "^5.1.0",
@@ -1,17 +1,16 @@
1
1
  <script setup lang="ts">
2
- import { useEditor } from "@tiptapify/composable/useEditor";
3
- import { ref } from 'vue'
2
+ import { Editor } from "@tiptap/vue-3";
3
+ import { inject, Ref } from "vue";
4
4
 
5
- const { editor } = useEditor()
6
- const editorInstance = ref(editor.getInstance())
5
+ const editor = inject('tiptapifyEditor') as Ref<Editor>
7
6
  </script>
8
7
 
9
8
  <template>
10
- <div v-if="editorInstance" class="tiptapify-footer">
9
+ <div v-if="editor" class="tiptapify-footer">
11
10
  <VRow>
12
11
  <VCol class="d-flex justify-end">
13
12
  <span class="words-count">
14
- {{ editorInstance.storage.characterCount.words() }} words
13
+ {{ editor.storage.characterCount.words() }} words
15
14
  </span>
16
15
  </VCol>
17
16
  </VRow>
@@ -1,7 +1,6 @@
1
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";
2
+ import { Editor } from "@tiptap/vue-3";
3
+ import { computed, defineProps, inject, Ref, ref } from "vue";
5
4
  import { BubbleMenu } from '@tiptap/vue-3/menus'
6
5
  import * as mdi from '@mdi/js'
7
6
 
@@ -10,91 +9,96 @@ defineProps({
10
9
  theme: { type: String, default () { return 'light' }},
11
10
  })
12
11
 
13
- const { editor } = useEditor()
14
- const editorInstance = ref(editor.getInstance())
15
-
16
- const bubbleMenuLinkButton = ref(null)
12
+ const editor = inject('tiptapifyEditor') as Ref<Editor>
17
13
 
18
14
  const items = ref([
19
15
  {
20
16
  name: 'bold',
21
17
  icon: mdi.mdiFormatBold,
22
18
  props: {
23
- disabled: computed(() => !editorInstance.value.can().chain().focus().toggleBold().run()),
24
- color: computed(() => editorInstance.value.isActive('bold') ? 'primary' : ''),
19
+ disabled: computed(() => !editor?.value.can().chain().focus().toggleBold().run()),
20
+ color: computed(() => editor.value.isActive('bold') ? 'primary' : ''),
25
21
  },
26
- click: () => editorInstance.value.chain().focus().toggleBold().run(),
22
+ click: () => editor.value.chain().focus().toggleBold().run(),
27
23
  },
28
24
  {
29
25
  name: 'italic',
30
26
  icon: mdi.mdiFormatItalic,
31
27
  props: {
32
- disabled: computed(() => !editorInstance.value.can().chain().focus().toggleItalic().run()),
33
- color: computed(() => editorInstance.value.isActive('italic') ? 'primary' : ''),
28
+ disabled: computed(() => !editor.value.can().chain().focus().toggleItalic().run()),
29
+ color: computed(() => editor.value.isActive('italic') ? 'primary' : ''),
34
30
  },
35
- click: () => editorInstance.value.chain().focus().toggleItalic().run(),
31
+ click: () => editor.value.chain().focus().toggleItalic().run(),
36
32
  },
37
33
  {
38
34
  name: 'strike',
39
35
  icon: mdi.mdiFormatStrikethroughVariant,
40
36
  props: {
41
- disabled: computed(() => !editorInstance.value.can().chain().focus().toggleStrike().run()),
42
- color: computed(() => editorInstance.value.isActive('strike') ? 'primary' : ''),
37
+ disabled: computed(() => !editor.value.can().chain().focus().toggleStrike().run()),
38
+ color: computed(() => editor.value.isActive('strike') ? 'primary' : ''),
43
39
  },
44
- click: () => editorInstance.value.chain().focus().toggleStrike().run(),
40
+ click: () => editor.value.chain().focus().toggleStrike().run(),
45
41
  },
46
42
  {
47
43
  name: 'underline',
48
44
  icon: mdi.mdiFormatUnderline,
49
45
  props: {
50
- disabled: computed(() => !editorInstance.value.can().chain().focus().toggleUnderline().run()),
51
- color: computed(() => editorInstance.value.isActive('underline') ? 'primary' : ''),
46
+ disabled: computed(() => !editor.value.can().chain().focus().toggleUnderline().run()),
47
+ color: computed(() => editor.value.isActive('underline') ? 'primary' : ''),
52
48
  },
53
- click: () => editorInstance.value.chain().focus().toggleUnderline().run(),
49
+ click: () => editor.value.chain().focus().toggleUnderline().run(),
54
50
  },
55
51
  {
56
52
  name: 'highlight',
57
53
  icon: mdi.mdiFormatColorHighlight,
58
54
  props: {
59
- disabled: computed(() => !editorInstance.value.can().chain().focus().toggleHighlight().run()),
60
- color: computed(() => editorInstance.value.isActive('highlight') ? 'primary' : ''),
55
+ disabled: computed(() => !editor.value.can().chain().focus().toggleHighlight().run()),
56
+ color: computed(() => editor.value.isActive('highlight') ? 'primary' : ''),
61
57
  },
62
- click: () => editorInstance.value.chain().focus().toggleHighlight().run(),
58
+ click: () => editor.value.chain().focus().toggleHighlight().run(),
63
59
  },
64
60
  {
65
61
  name: 'code',
66
62
  icon: mdi.mdiXml,
67
63
  props: {
68
- disabled: computed(() => !editorInstance.value.can().chain().focus().toggleCode().run()),
69
- color: computed(() => editorInstance.value.isActive('code') ? 'primary' : ''),
64
+ disabled: computed(() => !editor.value.can().chain().focus().toggleCode().run()),
65
+ color: computed(() => editor.value.isActive('code') ? 'primary' : ''),
70
66
  },
71
- click: () => editorInstance.value.chain().focus().toggleCode().run(),
67
+ click: () => editor.value.chain().focus().toggleCode().run(),
72
68
  },
73
69
  {
74
70
  name: 'link',
75
- icon: computed(() => editorInstance.value.isActive('link') ? mdi.mdiLinkOff : mdi.mdiLink),
71
+ icon: computed(() => editor.value.isActive('link') ? mdi.mdiLinkOff : mdi.mdiLink),
76
72
  props: {
77
- color: computed(() => editorInstance.value.isActive('link') ? 'primary' : ''),
78
- disabled: computed(() => editorInstance.value.isActive('code') || editorInstance.value.isActive('codeBlock')),
73
+ color: computed(() => editor.value.isActive('link') ? 'primary' : ''),
74
+ disabled: computed(() => editor.value.isActive('code') || editor.value.isActive('codeBlock')),
79
75
  },
80
- click: () => linkAction(),
76
+ click: () => editor.value.commands.showLink()
81
77
  },
82
78
  {
83
79
  name: 'format clear',
84
80
  icon: mdi.mdiFormatClear,
85
- click: () => editorInstance.value.chain().focus().unsetAllMarks().clearNodes().run(),
81
+ click: () => editor.value.chain().focus().unsetAllMarks().clearNodes().run(),
86
82
  }
87
83
  ])
88
-
89
- function linkAction() {
90
- return editorInstance.value.isActive('link')
91
- ? editorInstance.value.chain().focus().unsetLink().run()
92
- : bubbleMenuLinkButton.value?.open()
93
- }
94
84
  </script>
95
85
 
96
86
  <template>
97
- <BubbleMenu v-if="editorInstance" :editor="editorInstance" :options="{ placement: 'bottom' }">
87
+ <BubbleMenu
88
+ v-if="editor"
89
+ :editor="editor"
90
+ :options="{ placement: 'bottom' }"
91
+ :shouldShow="({ editor, view, state, from, to }) => {
92
+ if (editor.isActive('image') || editor.isActive('code') || editor.isActive('codeBlock')) {
93
+ return false
94
+ }
95
+
96
+ const docSize = editor.state.doc.content.size
97
+ const isAllSelected = from === 0 && to === docSize
98
+
99
+ return (from !== to) && !isAllSelected
100
+ }"
101
+ >
98
102
  <div class="bubble-menu">
99
103
  <VCard>
100
104
  <VCardText class="pa-0">
@@ -111,8 +115,6 @@ function linkAction() {
111
115
  </VCard>
112
116
  </div>
113
117
  </BubbleMenu>
114
-
115
- <LinkDialog ref="bubbleMenuLinkButton" />
116
118
  </template>
117
119
 
118
120
  <style scoped lang="scss">
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
- import { useEditor } from "@tiptapify/composable/useEditor";
3
- import { defineProps, ref } from "vue";
2
+ import { Editor } from "@tiptap/vue-3";
3
+ import { defineProps, inject, Ref } from "vue";
4
4
  import { FloatingMenu } from '@tiptap/vue-3/menus'
5
5
  import * as mdi from '@mdi/js'
6
6
 
@@ -9,11 +9,11 @@ defineProps({
9
9
  theme: { type: String, default () { return 'light' }},
10
10
  })
11
11
 
12
- const editorInstance = ref(useEditor().editor.getInstance())
12
+ const editor = inject('tiptapifyEditor') as Ref<Editor>
13
13
  </script>
14
14
 
15
15
  <template>
16
- <FloatingMenu v-if="editorInstance" :editor="editorInstance">
16
+ <FloatingMenu v-if="editor" :editor="editor">
17
17
  <div class="floating-menu">
18
18
  <VCard>
19
19
  <VCardText class="pa-0">
@@ -21,22 +21,22 @@ const editorInstance = ref(useEditor().editor.getInstance())
21
21
  <VToolbarItems>
22
22
  <VBtnGroup divided density="compact">
23
23
  <VBtn
24
- @click="editorInstance.chain().focus().toggleHeading({ level: 1 }).run()"
25
- :color="`${editorInstance.isActive('heading', { level: 1 }) ? 'primary' : ''}`"
24
+ @click="editor.chain().focus().toggleHeading({ level: 1 }).run()"
25
+ :color="`${editor.isActive('heading', { level: 1 }) ? 'primary' : ''}`"
26
26
  size="small"
27
27
  >
28
28
  <VIcon :icon="mdi['mdiFormatHeader1']" size="16" />
29
29
  </VBtn>
30
30
  <VBtn
31
- @click="editorInstance.chain().focus().toggleHeading({ level: 2 }).run()"
32
- :color="`${editorInstance.isActive('heading', { level: 2 }) ? 'primary' : ''}`"
31
+ @click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
32
+ :color="`${editor.isActive('heading', { level: 2 }) ? 'primary' : ''}`"
33
33
  size="small"
34
34
  >
35
35
  <VIcon :icon="mdi['mdiFormatHeader2']" size="16" />
36
36
  </VBtn>
37
37
  <VBtn
38
- @click="editorInstance.chain().focus().toggleBulletList().run()"
39
- :color="`${editorInstance.isActive('bulletList') ? 'primary' : ''}`"
38
+ @click="editor.chain().focus().toggleBulletList().run()"
39
+ :color="`${editor.isActive('bulletList') ? 'primary' : ''}`"
40
40
  size="small"
41
41
  >
42
42
  <VIcon :icon="mdi['mdiFormatListBulleted']" size="16" />
@@ -1,18 +1,20 @@
1
1
  <script setup lang="ts">
2
2
 
3
- import { onBeforeUnmount, ref } from "vue";
3
+ import { onBeforeUnmount, provide, ref, ShallowRef, shallowRef, watch } from "vue";
4
4
  import { default as Toolbar } from "@tiptapify/components/Toolbar/Index.vue";
5
- import { EditorContent } from '@tiptap/vue-3'
6
- import { useEditor } from '@tiptapify/composable/useEditor'
5
+ import { Editor, EditorContent } from '@tiptap/vue-3'
7
6
  import MenuBubble from '@tiptapify/components/MenuBubble.vue'
8
7
  import MenuFloating from '@tiptapify/components/MenuFloating.vue'
9
8
 
9
+ import { getTiptapEditor } from "@tiptapify/components/index";
10
+
10
11
  import Footer from '@tiptapify/components/Footer.vue'
11
12
  import { useTheme } from "vuetify/framework";
12
13
 
13
14
  const props = defineProps({
14
15
  content: String|Object,
15
- variant: { type: String, default () { return 'elevated' } },
16
+ variantBtn: { type: String, default () { return 'elevated' } },
17
+ variantField: { type: String, default () { return 'solo' } },
16
18
  toolbar: { type: Boolean, default () { return true } },
17
19
  items: { type: Array<string>, default() { return [] }},
18
20
  itemsExclude: { type: Boolean, default() { return false } },
@@ -28,14 +30,30 @@ const props = defineProps({
28
30
 
29
31
  const theme = ref(useTheme().current.value.dark ? 'dark' : 'light')
30
32
 
31
- const editor = useEditor(props.content, props.placeholder, props.slashCommands).editor
32
- const editorInstance = ref(editor.getInstance())
33
- editorInstance?.value?.chain().setFontFamily(props.defaultFontFamily).run()
33
+ const editor: ShallowRef<Editor | undefined> = shallowRef(
34
+ getTiptapEditor(props.content, props.placeholder, props.slashCommands)
35
+ )
36
+
37
+ const emit = defineEmits(['update:modelValue', 'editor-ready']);
38
+
39
+ provide('tiptapifyEditor', editor)
40
+
41
+ editor.value?.chain().setFontFamily(props.defaultFontFamily).run()
42
+
43
+ defineExpose({ editor: editor });
44
+
45
+ watch(() => editor.value, (editorInstance) => {
46
+ if (editorInstance instanceof Editor) {
47
+ emit('editor-ready', {
48
+ getHTML: () => editorInstance.getHTML(),
49
+ getJSON: () => editorInstance.getJSON(),
50
+ });
51
+ }
52
+ }, { immediate: true });
34
53
 
35
54
  onBeforeUnmount(() => {
36
- editor.destroy()
55
+ editor.value?.destroy()
37
56
  })
38
-
39
57
  </script>
40
58
 
41
59
  <template>
@@ -44,8 +62,9 @@ onBeforeUnmount(() => {
44
62
  <VCol>
45
63
  <template v-if="toolbar">
46
64
  <Toolbar
47
- v-if="editorInstance"
48
- :variant="variant"
65
+ v-if="editor"
66
+ :variant-btn="variantBtn"
67
+ :variant-field="variantField"
49
68
  :font-measure="fontMeasure"
50
69
  :items="items"
51
70
  :items-exclude="itemsExclude"
@@ -55,11 +74,11 @@ onBeforeUnmount(() => {
55
74
 
56
75
  <div :class="`border border-t-0 rounded-b-${rounded}`">
57
76
  <div class="pa-2 tiptapify-container">
58
- <MenuFloating v-if="floatingMenu" :variant="variant" :theme="theme" />
77
+ <MenuFloating v-if="floatingMenu" :variant="variantBtn" :theme="theme" />
59
78
 
60
- <MenuBubble v-if="bubbleMenu" :variant="variant" :theme="theme" />
79
+ <MenuBubble v-if="bubbleMenu" :variant="variantBtn" :theme="theme" />
61
80
 
62
- <EditorContent :editor="editorInstance" class="tiptapify-editor" />
81
+ <EditorContent :editor="editor" class="tiptapify-editor" />
63
82
  </div>
64
83
 
65
84
  <template v-if="showCharacterCount">
@@ -297,5 +316,67 @@ onBeforeUnmount(() => {
297
316
  border-top: 1px solid var(--gray-2);
298
317
  margin: 2rem 0;
299
318
  }
319
+
320
+ /* Table-specific styling */
321
+ table {
322
+ border-collapse: collapse;
323
+ margin: 0;
324
+ overflow: hidden;
325
+ table-layout: fixed;
326
+ width: 100%;
327
+
328
+ td,
329
+ th {
330
+ border: 1px solid var(--gray-3);
331
+ box-sizing: border-box;
332
+ min-width: 1em;
333
+ padding: 6px 8px;
334
+ position: relative;
335
+ vertical-align: top;
336
+
337
+ > * {
338
+ margin-bottom: 0;
339
+ }
340
+ }
341
+
342
+ th {
343
+ background-color: var(--gray-1);
344
+ font-weight: bold;
345
+ text-align: left;
346
+ }
347
+
348
+ .selectedCell:after {
349
+ background: var(--gray-2);
350
+ content: '';
351
+ left: 0;
352
+ right: 0;
353
+ top: 0;
354
+ bottom: 0;
355
+ pointer-events: none;
356
+ position: absolute;
357
+ z-index: 2;
358
+ }
359
+
360
+ .column-resize-handle {
361
+ background-color: var(--purple);
362
+ bottom: -2px;
363
+ pointer-events: none;
364
+ position: absolute;
365
+ right: -2px;
366
+ top: 0;
367
+ width: 4px;
368
+ }
369
+ }
370
+
371
+ .tableWrapper {
372
+ margin: 1.5rem 0;
373
+ overflow-x: auto;
374
+ }
375
+
376
+ &.resize-cursor {
377
+ cursor: ew-resize;
378
+ cursor: col-resize;
379
+ }
380
+
300
381
  }
301
382
  </style>
@@ -1,9 +1,12 @@
1
1
  <script setup lang="ts">
2
+ import GroupDropdown from "@tiptapify/components/Toolbar/GroupDropdown.vue";
2
3
  import { defineProps, PropType } from 'vue'
3
4
  import { useI18n } from "vue-i18n";
4
5
 
5
6
  import { ToolbarItemSection } from "@tiptapify/components/Toolbar/items";
6
7
 
8
+ import helpers from "@tiptapify/utils/helpers";
9
+
7
10
  defineProps({
8
11
  variant: { type: String, default () { return 'flat' }},
9
12
  section: { type: String, default() { return '' }},
@@ -12,61 +15,23 @@ defineProps({
12
15
 
13
16
  const { t } = useI18n();
14
17
 
18
+ const { ucFirst } = helpers;
19
+
15
20
  </script>
16
21
 
17
22
  <template>
18
23
  <VBtnGroup :variant="variant" elevation="4">
19
24
  <template v-for="(toolbarItem, toolbarItemKey) in toolbarSection.items" :key="toolbarItemKey">
20
25
  <template v-if="toolbarItem.children">
21
- <VMenu>
22
- <template #activator="{ props: menuProps }">
23
- <VBtn v-bind="{ ...menuProps, ...toolbarItem.props }" size="32">
24
- <VTooltip :text="t(toolbarItem.tooltip)" location="top" activator="parent" />
25
-
26
- <VIcon v-if="toolbarItem.icon" :icon="toolbarItem.icon" size="small" />
27
- <span v-else class="menu-item-title">
28
- {{ t(toolbarItem.name) }}
29
- </span>
30
- </VBtn>
31
- </template>
32
-
33
- <VList v-model="toolbarItem.modelValue" max-height="430px">
34
- <VListItem
35
- v-for="(item, menuItemKey) in toolbarItem.children"
36
- :key="menuItemKey"
37
- :value="item.name"
38
- density="compact"
39
- class="pa-0"
40
- >
41
- <VBtn
42
- variant="flat"
43
- rounded="0"
44
- v-bind="item.props"
45
- v-on="item.attrs"
46
- >
47
- <VTooltip v-if="item.tooltip" :text="t(item.tooltip)" location="top" activator="parent" />
48
-
49
- <VIcon v-if="item.icon" :icon="item.icon" size="small" />
50
- <span v-else class="menu-item-title">
51
- <template v-if="item.noI18n">
52
- {{ item.name }}
53
- </template>
54
- <template v-else>
55
- {{ t(item.toggle) }}
56
- </template>
57
- </span>
58
- </VBtn>
59
- </VListItem>
60
- </VList>
61
- </VMenu>
26
+ <GroupDropdown :toolbar-item="toolbarItem" :variant="variant" />
62
27
  </template>
63
28
 
64
29
  <VBtn v-else v-bind="toolbarItem.props" v-on="toolbarItem.attrs" size="32">
65
- <VTooltip :text="t(toolbarItem.tooltip)" location="top" activator="parent" />
30
+ <VTooltip :text="ucFirst(t(toolbarItem.tooltip))" location="top" activator="parent" />
66
31
 
67
32
  <VIcon v-if="toolbarItem.icon" :icon="toolbarItem.icon" size="small" />
68
33
  <span v-else class="menu-item-title">
69
- {{ t(toolbarItem.name) }}
34
+ {{ ucFirst(t(toolbarItem.name)) }}
70
35
  </span>
71
36
  </VBtn>
72
37
  </template>
@@ -0,0 +1,85 @@
1
+ <script setup lang="ts">
2
+ import { defineProps, PropType } from 'vue'
3
+ import { useI18n } from "vue-i18n";
4
+
5
+ import helpers from "@tiptapify/utils/helpers";
6
+
7
+ import { ToolbarItem } from "@tiptapify/components/Toolbar/items";
8
+
9
+ const { ucFirst } = helpers;
10
+
11
+ defineProps({
12
+ variant: { type: String, default () { return 'flat' }},
13
+ nested: { type: Boolean, default () { return false }},
14
+ toolbarItem: { type: Object as PropType<ToolbarItem>, default() { return {} }}
15
+ })
16
+
17
+ const { t } = useI18n();
18
+
19
+ </script>
20
+
21
+ <template>
22
+ <VMenu v-model="toolbarItem.modelValue" v-bind="toolbarItem.props">
23
+ <template v-if="!nested" #activator="{ props: menuProps }">
24
+ <VBtn v-bind="{ ...menuProps, ...toolbarItem.props }" size="32">
25
+ <VTooltip :text="ucFirst(t(toolbarItem.tooltip))" location="top" activator="parent" />
26
+
27
+ <VIcon v-if="toolbarItem.icon" :icon="toolbarItem.icon" size="small" />
28
+ <span v-else class="menu-item-title">
29
+ {{ ucFirst(t(toolbarItem.name)) }}
30
+ </span>
31
+ </VBtn>
32
+ </template>
33
+
34
+ <VList v-model="toolbarItem.modelValue" max-height="430px">
35
+ <VListItem
36
+ v-for="(item, menuItemKey) in toolbarItem.children"
37
+ :key="menuItemKey"
38
+ :value="item.name"
39
+ density="compact"
40
+ class="pa-0"
41
+ >
42
+ <VBtn
43
+ variant="text"
44
+ block
45
+ class="justify-start"
46
+ rounded="0"
47
+ v-bind="item.props ?? {}"
48
+ v-on="item?.attrs ?? {}"
49
+ >
50
+ <VTooltip v-if="item.tooltip" :text="ucFirst(t(item.tooltip))" location="top" activator="parent" />
51
+
52
+ <VIcon v-if="item.icon" :icon="item.icon" size="small" />
53
+
54
+ <span v-else class="menu-item-title">
55
+ <template v-if="item.noI18n">
56
+ {{ item.name }}
57
+ </template>
58
+ <template v-else>
59
+ {{ ucFirst(t(item.toggle)) }}
60
+ </template>
61
+ </span>
62
+
63
+ <VMenu
64
+ v-if="item.component"
65
+ v-bind="item.props"
66
+ >
67
+ <VList>
68
+ <VListItem density="compact">
69
+ <component :is="item.component" @close="toolbarItem.modelValue = false" />
70
+ </VListItem>
71
+ </VList>
72
+ </VMenu>
73
+
74
+ <template v-if="item.children?.length">
75
+ <GroupDropdown :toolbar-item="item" variant="outline" :nested="true" />
76
+ </template>
77
+ </VBtn>
78
+ </VListItem>
79
+ </VList>
80
+ </VMenu>
81
+ </template>
82
+
83
+ <style lang="scss" scoped>
84
+
85
+ </style>