tiptapify 0.0.7 → 0.0.9
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/README.md +27 -3
- package/dist/tiptapify.css +1 -1
- package/dist/tiptapify.mjs +27785 -23593
- package/dist/tiptapify.umd.js +35 -35
- package/index.d.ts +49 -0
- package/package.json +8 -6
- package/src/components/Footer.vue +48 -6
- package/src/components/MenuBubble.vue +5 -12
- package/src/components/Tiptapify.vue +41 -31
- package/src/components/Toolbar/Group.vue +13 -14
- package/src/components/Toolbar/GroupBtn.vue +34 -0
- package/src/components/Toolbar/Index.vue +28 -79
- package/src/components/Toolbar/Items.vue +77 -0
- package/src/components/Toolbar/defaultExtensionComponents.ts +32 -0
- package/src/components/Toolbar/items/format.ts +0 -13
- package/src/components/Toolbar/items/media.ts +32 -23
- package/src/components/Toolbar/items/misc.ts +1 -1
- package/src/components/Toolbar/items/style.ts +43 -2
- package/src/components/Toolbar/items.ts +8 -7
- package/src/components/editorExtensions.ts +8 -10
- package/src/extensions/components/ImageDialog.vue +142 -0
- package/src/extensions/components/LinkDialog.vue +77 -36
- package/src/extensions/components/PreviewDialog.vue +0 -1
- package/src/extensions/components/ShowSourceDialog.vue +5 -3
- package/src/extensions/components/StyleColor.vue +177 -0
- package/src/extensions/components/TableBuilder.vue +4 -5
- package/src/extensions/image.ts +31 -0
- package/src/extensions/link.ts +24 -0
- package/src/extensions/preview.ts +2 -15
- package/src/extensions/view-source.ts +0 -3
- package/src/i18n/locales/de.json +46 -20
- package/src/i18n/locales/en.json +31 -5
- package/src/i18n/locales/es.json +38 -12
- package/src/i18n/locales/fr.json +49 -23
- package/src/i18n/locales/it.json +42 -16
- package/src/i18n/locales/pl.json +46 -19
- package/src/i18n/locales/ru.json +30 -4
- package/src/i18n/locales/ua.json +30 -4
- package/src/index.ts +0 -1
- package/src/types/overridable-extensions.ts +6 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import Group from "@tiptapify/components/Toolbar/Group.vue";
|
|
3
|
+
import Toggle from "@tiptapify/components/Toolbar/Toggle.vue";
|
|
4
|
+
import { computed, defineProps, PropType, Ref, ref } from 'vue'
|
|
5
|
+
import { useI18n } from "vue-i18n";
|
|
6
|
+
|
|
7
|
+
import { ToolbarItemSections } from "@tiptapify/components/Toolbar/items";
|
|
8
|
+
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
variantBtn: { type: String, default () { return 'elevated' }},
|
|
11
|
+
items: { type: Object as PropType<ToolbarItemSections>, default() { return {} }},
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const { t } = useI18n();
|
|
15
|
+
|
|
16
|
+
const toolbarItemsRef: Ref<ToolbarItemSections> = ref(computed(() => props.items).value)
|
|
17
|
+
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<VToolbarItems class="py-2">
|
|
22
|
+
<template v-for="(toolbarSection, sectionKey) in toolbarItemsRef" :key="sectionKey">
|
|
23
|
+
<Group v-if="toolbarSection.group" :variant="variantBtn" :toolbar-section="toolbarSection" />
|
|
24
|
+
|
|
25
|
+
<Toggle v-else-if="toolbarSection.toggle" :variant="variantBtn" :toolbar-section="toolbarSection" />
|
|
26
|
+
|
|
27
|
+
<VBtn
|
|
28
|
+
v-else
|
|
29
|
+
v-for="(toolbarItem, itemKey) in toolbarSection.items"
|
|
30
|
+
:key="itemKey"
|
|
31
|
+
:variant="variantBtn"
|
|
32
|
+
v-bind="toolbarItem.props"
|
|
33
|
+
v-on="toolbarItem.attrs"
|
|
34
|
+
class="menu-button"
|
|
35
|
+
size="32"
|
|
36
|
+
elevation="4"
|
|
37
|
+
rounded="sm"
|
|
38
|
+
>
|
|
39
|
+
<VTooltip :text="t(toolbarItem.tooltip)" location="top" activator="parent" />
|
|
40
|
+
|
|
41
|
+
<VIcon v-if="toolbarItem.icon" :icon="toolbarItem.icon" size="16" />
|
|
42
|
+
<span v-else class="menu-item-title">
|
|
43
|
+
{{ t(toolbarItem.name) }}
|
|
44
|
+
</span>
|
|
45
|
+
</VBtn>
|
|
46
|
+
|
|
47
|
+
<div class="menu-divider"></div>
|
|
48
|
+
</template>
|
|
49
|
+
</VToolbarItems>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<style lang="scss" scoped>
|
|
53
|
+
:deep(.v-btn-group) {
|
|
54
|
+
height: 32px !important;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.menu-item-title {
|
|
58
|
+
font-size: 14px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.menu-button {
|
|
62
|
+
margin: 0 1px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.menu-divider {
|
|
66
|
+
margin: 0 4px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.menu-divider:nth-last-child(1) {
|
|
70
|
+
display: none;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.v-toolbar-items {
|
|
74
|
+
flex-wrap: wrap;
|
|
75
|
+
row-gap: 5px;
|
|
76
|
+
}
|
|
77
|
+
</style>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import ImageDialog from "@tiptapify/extensions/components/ImageDialog.vue";
|
|
2
|
+
import LinkDialog from "@tiptapify/extensions/components/LinkDialog.vue";
|
|
3
|
+
import PreviewDialog from "@tiptapify/extensions/components/PreviewDialog.vue";
|
|
4
|
+
import ShowSourceDialog from "@tiptapify/extensions/components/ShowSourceDialog.vue";
|
|
5
|
+
import { extensionsComponents } from "@tiptapify/types/overridable-extensions";
|
|
6
|
+
import { computed } from "vue";
|
|
7
|
+
|
|
8
|
+
export function getDefaultComponents(variantField: string): extensionsComponents {
|
|
9
|
+
return {
|
|
10
|
+
image: {
|
|
11
|
+
component: ImageDialog,
|
|
12
|
+
props: {
|
|
13
|
+
variantField: computed(() => variantField).value,
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
link: {
|
|
17
|
+
component: LinkDialog,
|
|
18
|
+
props: {
|
|
19
|
+
variantField: computed(() => variantField).value,
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
preview: {
|
|
23
|
+
component: PreviewDialog,
|
|
24
|
+
},
|
|
25
|
+
showSource: {
|
|
26
|
+
component: ShowSourceDialog,
|
|
27
|
+
props: {
|
|
28
|
+
variantField: computed(() => variantField).value,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -55,19 +55,6 @@ export function getFormatItems(editor: Editor) {
|
|
|
55
55
|
attrs: {
|
|
56
56
|
click: () => editor.chain().focus().toggleUnderline().run()
|
|
57
57
|
}
|
|
58
|
-
},
|
|
59
|
-
highlight: {
|
|
60
|
-
name: 'highlight',
|
|
61
|
-
tooltip: 'format.highlight',
|
|
62
|
-
icon: mdi.mdiMarker,
|
|
63
|
-
enabled: true,
|
|
64
|
-
props: {
|
|
65
|
-
disabled: computed(() => !editor.can().chain().focus().toggleHighlight().run()),
|
|
66
|
-
color: computed(() => editor.isActive('highlight') ? 'primary' : ''),
|
|
67
|
-
},
|
|
68
|
-
attrs: {
|
|
69
|
-
click: () => editor.chain().focus().toggleHighlight().run()
|
|
70
|
-
}
|
|
71
58
|
}
|
|
72
59
|
}
|
|
73
60
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import * as mdi from "@mdi/js";
|
|
2
2
|
import { Editor } from "@tiptap/vue-3";
|
|
3
3
|
import TableBuilder from "@tiptapify/extensions/components/TableBuilder.vue";
|
|
4
|
-
import { computed, markRaw
|
|
4
|
+
import { computed, markRaw } from "vue";
|
|
5
5
|
|
|
6
|
-
export function getMediaItems(editor: Editor
|
|
6
|
+
export function getMediaItems(editor: Editor) {
|
|
7
7
|
return {
|
|
8
8
|
link: {
|
|
9
|
-
name: '
|
|
10
|
-
tooltip: '
|
|
9
|
+
name: 'media.link',
|
|
10
|
+
tooltip: 'media.link',
|
|
11
11
|
icon: computed(() => editor.isActive('link') ? mdi.mdiLinkOff : mdi.mdiLink),
|
|
12
12
|
enabled: true,
|
|
13
13
|
props: {
|
|
@@ -15,16 +15,25 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
|
|
|
15
15
|
disabled: computed(() => editor.isActive('code') || editor.isActive('codeBlock')),
|
|
16
16
|
},
|
|
17
17
|
attrs: {
|
|
18
|
-
click:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
click: () => editor.commands.showLink()
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
image: {
|
|
22
|
+
name: 'media.image',
|
|
23
|
+
tooltip: 'media.image',
|
|
24
|
+
icon: mdi.mdiImage,
|
|
25
|
+
enabled: true,
|
|
26
|
+
props: {
|
|
27
|
+
color: computed(() => editor.isActive('image') ? 'primary' : ''),
|
|
28
|
+
disabled: computed(() => editor.isActive('code') || editor.isActive('codeBlock')),
|
|
29
|
+
},
|
|
30
|
+
attrs: {
|
|
31
|
+
click: () => editor.commands.showTiptapifyImage()
|
|
23
32
|
}
|
|
24
33
|
},
|
|
25
34
|
table: {
|
|
26
35
|
name: 'tables',
|
|
27
|
-
tooltip: '
|
|
36
|
+
tooltip: 'media.tables.table',
|
|
28
37
|
icon: mdi.mdiTable,
|
|
29
38
|
modelValue: false,
|
|
30
39
|
enabled: true,
|
|
@@ -35,7 +44,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
|
|
|
35
44
|
children: [
|
|
36
45
|
{
|
|
37
46
|
name: 'insert table',
|
|
38
|
-
tooltip: '
|
|
47
|
+
tooltip: 'media.tables.insertTable',
|
|
39
48
|
icon: mdi.mdiTablePlus,
|
|
40
49
|
enabled: true,
|
|
41
50
|
props: {
|
|
@@ -49,8 +58,8 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
|
|
|
49
58
|
component: markRaw(TableBuilder),
|
|
50
59
|
},
|
|
51
60
|
{
|
|
52
|
-
name: '
|
|
53
|
-
tooltip: '
|
|
61
|
+
name: 'delete table',
|
|
62
|
+
tooltip: 'media.tables.deleteTable',
|
|
54
63
|
icon: mdi.mdiTableMinus,
|
|
55
64
|
enabled: true,
|
|
56
65
|
props: {
|
|
@@ -62,7 +71,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
|
|
|
62
71
|
},
|
|
63
72
|
{
|
|
64
73
|
name: 'table row',
|
|
65
|
-
tooltip: '
|
|
74
|
+
tooltip: 'media.tables.row',
|
|
66
75
|
icon: mdi.mdiTableRow,
|
|
67
76
|
enabled: true,
|
|
68
77
|
props: {
|
|
@@ -80,7 +89,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
|
|
|
80
89
|
children: [
|
|
81
90
|
{
|
|
82
91
|
name: 'insert row before',
|
|
83
|
-
tooltip: '
|
|
92
|
+
tooltip: 'media.tables.insertRowBefore',
|
|
84
93
|
icon: mdi.mdiTableRowPlusBefore,
|
|
85
94
|
enabled: true,
|
|
86
95
|
props: {
|
|
@@ -92,7 +101,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
|
|
|
92
101
|
},
|
|
93
102
|
{
|
|
94
103
|
name: 'insert row after',
|
|
95
|
-
tooltip: '
|
|
104
|
+
tooltip: 'media.tables.insertRowAfter',
|
|
96
105
|
icon: mdi.mdiTableRowPlusAfter,
|
|
97
106
|
enabled: true,
|
|
98
107
|
props: {
|
|
@@ -104,7 +113,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
|
|
|
104
113
|
},
|
|
105
114
|
{
|
|
106
115
|
name: 'delete row',
|
|
107
|
-
tooltip: '
|
|
116
|
+
tooltip: 'media.tables.deleteRow',
|
|
108
117
|
icon: mdi.mdiTableRowRemove,
|
|
109
118
|
enabled: true,
|
|
110
119
|
props: {
|
|
@@ -118,7 +127,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
|
|
|
118
127
|
},
|
|
119
128
|
{
|
|
120
129
|
name: 'column',
|
|
121
|
-
tooltip: '
|
|
130
|
+
tooltip: 'media.tables.col',
|
|
122
131
|
icon: mdi.mdiTableColumn,
|
|
123
132
|
enabled: true,
|
|
124
133
|
props: {
|
|
@@ -136,7 +145,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
|
|
|
136
145
|
children: [
|
|
137
146
|
{
|
|
138
147
|
name: 'insert col before',
|
|
139
|
-
tooltip: '
|
|
148
|
+
tooltip: 'media.tables.insertColBefore',
|
|
140
149
|
icon: mdi.mdiTableColumnPlusBefore,
|
|
141
150
|
enabled: true,
|
|
142
151
|
props: {
|
|
@@ -148,7 +157,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
|
|
|
148
157
|
},
|
|
149
158
|
{
|
|
150
159
|
name: 'insert column after',
|
|
151
|
-
tooltip: '
|
|
160
|
+
tooltip: 'media.tables.insertColAfter',
|
|
152
161
|
icon: mdi.mdiTableColumnPlusAfter,
|
|
153
162
|
enabled: true,
|
|
154
163
|
props: {
|
|
@@ -160,7 +169,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
|
|
|
160
169
|
},
|
|
161
170
|
{
|
|
162
171
|
name: 'delete column',
|
|
163
|
-
tooltip: '
|
|
172
|
+
tooltip: 'media.tables.deleteCol',
|
|
164
173
|
icon: mdi.mdiTableColumnRemove,
|
|
165
174
|
enabled: true,
|
|
166
175
|
props: {
|
|
@@ -174,7 +183,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
|
|
|
174
183
|
},
|
|
175
184
|
{
|
|
176
185
|
name: 'merge cells',
|
|
177
|
-
tooltip: '
|
|
186
|
+
tooltip: 'media.tables.mergeCells',
|
|
178
187
|
icon: mdi.mdiTableMergeCells,
|
|
179
188
|
enabled: true,
|
|
180
189
|
props: {
|
|
@@ -186,7 +195,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
|
|
|
186
195
|
},
|
|
187
196
|
{
|
|
188
197
|
name: 'split cell',
|
|
189
|
-
tooltip: '
|
|
198
|
+
tooltip: 'media.tables.splitCell',
|
|
190
199
|
icon: mdi.mdiTableSplitCell,
|
|
191
200
|
enabled: true,
|
|
192
201
|
props: {
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import * as mdi from "@mdi/js";
|
|
2
2
|
import { Editor } from "@tiptap/vue-3";
|
|
3
3
|
import { fonts } from "@tiptapify/components/Toolbar/fonts";
|
|
4
|
-
import
|
|
4
|
+
import StyleColor from "@tiptapify/extensions/components/StyleColor.vue";
|
|
5
|
+
import { computed, markRaw, ref } from "vue";
|
|
5
6
|
|
|
6
7
|
interface MDIIcons {
|
|
7
8
|
[key: string]: string
|
|
8
9
|
}
|
|
9
10
|
const mdiIcons = mdi as MDIIcons
|
|
10
11
|
|
|
11
|
-
export function getStyleItems(editor: Editor, fontMeasure: string, customHeadingLevels: Array<number> = []) {
|
|
12
|
+
export function getStyleItems(editor: Editor, theme: any, fontMeasure: string, customHeadingLevels: Array<number> = []) {
|
|
12
13
|
const headingLevels = ref([1, 2, 3, 4, 5, 6])
|
|
13
14
|
if (customHeadingLevels.length) {
|
|
14
15
|
customHeadingLevels.forEach(level => {
|
|
@@ -141,6 +142,46 @@ export function getStyleItems(editor: Editor, fontMeasure: string, customHeading
|
|
|
141
142
|
}
|
|
142
143
|
}
|
|
143
144
|
})
|
|
145
|
+
},
|
|
146
|
+
highlight: {
|
|
147
|
+
name: 'highlight',
|
|
148
|
+
tooltip: 'style.color.highlight',
|
|
149
|
+
icon: mdi.mdiFormatColorFill,
|
|
150
|
+
icon2: mdi.mdiColorHelper,
|
|
151
|
+
enabled: true,
|
|
152
|
+
icon2Props: {
|
|
153
|
+
color: computed(() => {
|
|
154
|
+
const defaultColor = theme.global.current.value.dark ? '#fff' : '#000'
|
|
155
|
+
return editor.getAttributes('highlight').color || defaultColor
|
|
156
|
+
}),
|
|
157
|
+
style: 'filter: drop-shadow(rgba(0, 0, 0, .75) 1px 1px 2px);'
|
|
158
|
+
},
|
|
159
|
+
component: markRaw(StyleColor),
|
|
160
|
+
componentProps: {
|
|
161
|
+
fontColor: false,
|
|
162
|
+
backgroundColor: true,
|
|
163
|
+
color: computed(() => editor.getAttributes('highlight').color || ''),
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
color: {
|
|
167
|
+
name: 'color',
|
|
168
|
+
tooltip: 'style.color.text',
|
|
169
|
+
icon: mdi.mdiFormatColorText,
|
|
170
|
+
icon2: mdi.mdiColorHelper,
|
|
171
|
+
enabled: true,
|
|
172
|
+
icon2Props: {
|
|
173
|
+
color: computed(() => {
|
|
174
|
+
const defaultColor = theme.global.current.value.dark ? '#fff' : '#000'
|
|
175
|
+
return editor.getAttributes('textStyle').color || defaultColor
|
|
176
|
+
}),
|
|
177
|
+
style: 'filter: drop-shadow(rgba(0, 0, 0, .75) 1px 1px 2px);'
|
|
178
|
+
},
|
|
179
|
+
component: markRaw(StyleColor),
|
|
180
|
+
componentProps: {
|
|
181
|
+
fontColor: true,
|
|
182
|
+
backgroundColor: false,
|
|
183
|
+
color: computed(() => editor.getAttributes('textStyle').color || ''),
|
|
184
|
+
}
|
|
144
185
|
}
|
|
145
186
|
}
|
|
146
187
|
}
|
|
@@ -6,7 +6,7 @@ import { getListItems } from "@tiptapify/components/Toolbar/items/list";
|
|
|
6
6
|
import { getMediaItems } from "@tiptapify/components/Toolbar/items/media";
|
|
7
7
|
import { getMiscItems } from "@tiptapify/components/Toolbar/items/misc";
|
|
8
8
|
import { getStyleItems } from "@tiptapify/components/Toolbar/items/style";
|
|
9
|
-
import { ComputedRef,
|
|
9
|
+
import { ComputedRef, ref } from "vue";
|
|
10
10
|
|
|
11
11
|
interface ToolbarItemAttrs {
|
|
12
12
|
[key: string]: Function | any
|
|
@@ -20,12 +20,15 @@ export interface ToolbarItem {
|
|
|
20
20
|
name: string|number,
|
|
21
21
|
tooltip: string,
|
|
22
22
|
icon: string|ComputedRef<string>,
|
|
23
|
+
icon2?: string|ComputedRef<string>,
|
|
23
24
|
noI18n?: boolean,
|
|
24
25
|
enabled: boolean,
|
|
25
26
|
component?: any,
|
|
26
27
|
modelValue?: any,
|
|
27
28
|
group?: boolean,
|
|
28
29
|
toggle?: boolean,
|
|
30
|
+
icon2Props?: ToolbarItemProps,
|
|
31
|
+
componentProps?: ToolbarItemProps,
|
|
29
32
|
props?: ToolbarItemProps,
|
|
30
33
|
attrs?: ToolbarItemAttrs,
|
|
31
34
|
children?: ToolbarItems|ToolbarItem[],
|
|
@@ -47,26 +50,24 @@ export interface ToolbarItemSections {
|
|
|
47
50
|
|
|
48
51
|
export function toolbarItems(
|
|
49
52
|
editor: any,
|
|
53
|
+
theme: any,
|
|
50
54
|
fontMeasure: string,
|
|
51
55
|
items: { list: Array<string>, exclude: boolean },
|
|
52
|
-
customHeadingLevels: Array<number
|
|
53
|
-
toolbarLinkButton: Ref,
|
|
56
|
+
customHeadingLevels: Array<number>
|
|
54
57
|
): ToolbarItemSections {
|
|
55
|
-
const styleItems = ref(getStyleItems(editor.value, fontMeasure, customHeadingLevels))
|
|
58
|
+
const styleItems = ref(getStyleItems(editor.value, theme, fontMeasure, customHeadingLevels))
|
|
56
59
|
const formatItems = ref(getFormatItems(editor.value))
|
|
57
60
|
const formatExtraItems = ref(getFormatExtraItems(editor.value))
|
|
58
61
|
const alignmentItems = ref(getAlignmentItems(editor.value))
|
|
59
62
|
const listItems = ref(getListItems(editor.value))
|
|
60
63
|
const actionsItems = ref(getActionsItems(editor.value))
|
|
61
64
|
const miscItems = ref(getMiscItems(editor.value))
|
|
62
|
-
const mediaItems = ref(getMediaItems(editor.value
|
|
65
|
+
const mediaItems = ref(getMediaItems(editor.value))
|
|
63
66
|
|
|
64
67
|
const allMenuItems: ToolbarItemSections = {
|
|
65
68
|
/**
|
|
66
69
|
* todo
|
|
67
70
|
*
|
|
68
|
-
* font color, backgroundcolor
|
|
69
|
-
* tables
|
|
70
71
|
* media (image, video)
|
|
71
72
|
*/
|
|
72
73
|
style: { group: true, items: styleItems.value },
|
|
@@ -23,7 +23,8 @@ import { Underline } from '@tiptap/extension-underline'
|
|
|
23
23
|
import { TableKit } from '@tiptap/extension-table'
|
|
24
24
|
import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight'
|
|
25
25
|
|
|
26
|
-
import {
|
|
26
|
+
import { TiptapifyLink } from '@tiptapify/extensions/link'
|
|
27
|
+
import { TiptapifyImage } from '@tiptapify/extensions/image'
|
|
27
28
|
import CodeBlockComponent from '@tiptapify/components/CodeBlockComponent.vue'
|
|
28
29
|
import { ViewSource } from '@tiptapify/extensions/view-source'
|
|
29
30
|
import { Preview } from '@tiptapify/extensions/preview'
|
|
@@ -68,12 +69,13 @@ export function editorExtensions (placeholder: string, slashCommands: boolean) {
|
|
|
68
69
|
Dropcursor,
|
|
69
70
|
Typography,
|
|
70
71
|
Underline,
|
|
71
|
-
Highlight,
|
|
72
|
-
|
|
72
|
+
Highlight.configure({ multicolor: true }),
|
|
73
|
+
TiptapifyLink.configure({
|
|
73
74
|
openOnClick: false,
|
|
74
|
-
defaultProtocol: 'https'
|
|
75
|
+
defaultProtocol: 'https'
|
|
75
76
|
}),
|
|
76
77
|
Image,
|
|
78
|
+
TiptapifyImage,
|
|
77
79
|
Superscript,
|
|
78
80
|
Subscript,
|
|
79
81
|
TableKit,
|
|
@@ -87,12 +89,8 @@ export function editorExtensions (placeholder: string, slashCommands: boolean) {
|
|
|
87
89
|
},
|
|
88
90
|
})
|
|
89
91
|
.configure({ lowlight }),
|
|
90
|
-
Selection.configure({
|
|
91
|
-
|
|
92
|
-
}),
|
|
93
|
-
TextAlign.configure({
|
|
94
|
-
types: ['heading', 'paragraph'],
|
|
95
|
-
}),
|
|
92
|
+
Selection.configure({ className: 'selection' }),
|
|
93
|
+
TextAlign.configure({ types: ['heading', 'paragraph'] }),
|
|
96
94
|
Placeholder.configure({ placeholder }),
|
|
97
95
|
CharacterCount,
|
|
98
96
|
ViewSource,
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
|
|
3
|
+
import * as mdi from '@mdi/js'
|
|
4
|
+
import { Editor } from "@tiptap/vue-3";
|
|
5
|
+
|
|
6
|
+
import { useI18n } from 'vue-i18n'
|
|
7
|
+
import { computed, inject, onMounted, onUnmounted, Ref, ref } from 'vue'
|
|
8
|
+
|
|
9
|
+
import helpers from '@tiptapify/utils/helpers'
|
|
10
|
+
|
|
11
|
+
defineProps({
|
|
12
|
+
variantBtn: { type: String, default() { return 'elevated' }},
|
|
13
|
+
variantField: { type: String, default() { return 'solo' }}
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const { ucFirst } = helpers
|
|
17
|
+
|
|
18
|
+
const editor = inject('tiptapifyEditor') as Ref<Editor>
|
|
19
|
+
const { t } = useI18n()
|
|
20
|
+
|
|
21
|
+
const generateImageAttrs = () => ({
|
|
22
|
+
src: '',
|
|
23
|
+
alt: '',
|
|
24
|
+
height: null,
|
|
25
|
+
width: null
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const attrs = ref(generateImageAttrs())
|
|
29
|
+
|
|
30
|
+
const dialog = ref<boolean>(false)
|
|
31
|
+
|
|
32
|
+
const isDisabled = computed(() => {
|
|
33
|
+
const { src } = attrs.value
|
|
34
|
+
return !src
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
function apply() {
|
|
38
|
+
let { src, alt, width, height } = attrs.value
|
|
39
|
+
|
|
40
|
+
const imageOptions: { src: string, alt: string, width?: number, height?: number} = {
|
|
41
|
+
src,
|
|
42
|
+
alt
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (width) {
|
|
46
|
+
imageOptions.width = width
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (height) {
|
|
50
|
+
imageOptions.height = height
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (src) {
|
|
54
|
+
editor.value.chain().focus().setImage(imageOptions).run()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
close()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function clear() {
|
|
61
|
+
editor.value.commands.deleteSelection()
|
|
62
|
+
|
|
63
|
+
close()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function close() {
|
|
67
|
+
dialog.value = false
|
|
68
|
+
|
|
69
|
+
attrs.value = generateImageAttrs()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const showTiptapifyImage = (event: CustomEvent) => {
|
|
73
|
+
attrs.value.src = event.detail.image?.src
|
|
74
|
+
attrs.value.alt = event.detail.image?.alt
|
|
75
|
+
attrs.value.width = event.detail.image?.width
|
|
76
|
+
attrs.value.height = event.detail.image?.height
|
|
77
|
+
|
|
78
|
+
dialog.value = true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
onMounted(() => {
|
|
82
|
+
window.addEventListener('tiptapify-show-tiptapifyImage', showTiptapifyImage as EventListener)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
onUnmounted(() => {
|
|
86
|
+
window.removeEventListener('tiptapify-show-tiptapifyImage', showTiptapifyImage as EventListener)
|
|
87
|
+
})
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<template>
|
|
91
|
+
<VDialog v-model="dialog" max-width="800" absolute @click:outside="close">
|
|
92
|
+
<VCard>
|
|
93
|
+
<VToolbar class="px-6" density="compact">
|
|
94
|
+
<span class="headline">{{ ucFirst(t('dialog.image.title')) }}</span>
|
|
95
|
+
|
|
96
|
+
<VSpacer />
|
|
97
|
+
|
|
98
|
+
<VBtn class="mx-0" icon @click="close">
|
|
99
|
+
<VIcon :icon="mdi.mdiClose" />
|
|
100
|
+
</VBtn>
|
|
101
|
+
</VToolbar>
|
|
102
|
+
|
|
103
|
+
<VCardText>
|
|
104
|
+
<VRow>
|
|
105
|
+
<VCol cols="12">
|
|
106
|
+
<VTextField v-model="attrs.src" :variant="variantField" :label="ucFirst(t('dialog.image.src'))" />
|
|
107
|
+
</VCol>
|
|
108
|
+
|
|
109
|
+
<VCol cols="12" md="6">
|
|
110
|
+
<VTextField v-model="attrs.alt" :variant="variantField" :label="ucFirst(t('dialog.image.alt'))" />
|
|
111
|
+
</VCol>
|
|
112
|
+
|
|
113
|
+
<VCol cols="12" md="3">
|
|
114
|
+
<VTextField v-model="attrs.width" type="number" :variant="variantField" :precision="0" :min="1" :label="ucFirst(t('dialog.image.width'))" />
|
|
115
|
+
</VCol>
|
|
116
|
+
|
|
117
|
+
<VCol cols="12" md="3">
|
|
118
|
+
<VTextField v-model="attrs.height" type="number" :variant="variantField" :precision="0" :min="1" :label="ucFirst(t('dialog.image.height'))" />
|
|
119
|
+
</VCol>
|
|
120
|
+
</VRow>
|
|
121
|
+
</VCardText>
|
|
122
|
+
|
|
123
|
+
<VCardActions>
|
|
124
|
+
<VRow>
|
|
125
|
+
<VCol class="d-flex justify-start">
|
|
126
|
+
<VBtn color="warning" v-if="editor.isActive('image')" :variant="variantBtn" :disabled="isDisabled" @click="clear">
|
|
127
|
+
{{ ucFirst(t('dialog.clear')) }}
|
|
128
|
+
</VBtn>
|
|
129
|
+
</VCol>
|
|
130
|
+
<VCol class="d-flex justify-end">
|
|
131
|
+
<VBtn :variant="variantBtn" @click="close" class="mr-2">
|
|
132
|
+
{{ ucFirst(t('dialog.close')) }}
|
|
133
|
+
</VBtn>
|
|
134
|
+
<VBtn color="primary" :variant="variantBtn" :disabled="isDisabled" @click="apply">
|
|
135
|
+
{{ ucFirst(t('dialog.apply')) }}
|
|
136
|
+
</VBtn>
|
|
137
|
+
</VCol>
|
|
138
|
+
</VRow>
|
|
139
|
+
</VCardActions>
|
|
140
|
+
</VCard>
|
|
141
|
+
</VDialog>
|
|
142
|
+
</template>
|