tiptapify 0.0.7 → 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.
- package/README.md +2 -1
- package/dist/tiptapify.css +1 -1
- package/dist/tiptapify.mjs +22244 -20247
- package/dist/tiptapify.umd.js +34 -34
- package/package.json +1 -1
- package/src/components/MenuBubble.vue +5 -12
- package/src/components/Tiptapify.vue +6 -4
- package/src/components/Toolbar/Index.vue +10 -10
- package/src/components/Toolbar/items/media.ts +31 -22
- package/src/components/Toolbar/items.ts +2 -3
- package/src/components/editorExtensions.ts +5 -3
- 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/TableBuilder.vue +3 -3
- 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 +32 -18
- package/src/i18n/locales/en.json +17 -3
- package/src/i18n/locales/es.json +23 -9
- package/src/i18n/locales/fr.json +31 -17
- package/src/i18n/locales/it.json +25 -11
- package/src/i18n/locales/pl.json +27 -13
- package/src/i18n/locales/ru.json +16 -2
- package/src/i18n/locales/ua.json +16 -2
- package/src/index.ts +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { Editor } from "@tiptap/vue-3";
|
|
3
|
-
import LinkDialog from "@tiptapify/extensions/components/LinkDialog.vue";
|
|
4
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'
|
|
@@ -12,8 +11,6 @@ defineProps({
|
|
|
12
11
|
|
|
13
12
|
const editor = inject('tiptapifyEditor') as Ref<Editor>
|
|
14
13
|
|
|
15
|
-
const bubbleMenuLinkButton = ref(null)
|
|
16
|
-
|
|
17
14
|
const items = ref([
|
|
18
15
|
{
|
|
19
16
|
name: 'bold',
|
|
@@ -76,7 +73,7 @@ const items = ref([
|
|
|
76
73
|
color: computed(() => editor.value.isActive('link') ? 'primary' : ''),
|
|
77
74
|
disabled: computed(() => editor.value.isActive('code') || editor.value.isActive('codeBlock')),
|
|
78
75
|
},
|
|
79
|
-
click: () =>
|
|
76
|
+
click: () => editor.value.commands.showLink()
|
|
80
77
|
},
|
|
81
78
|
{
|
|
82
79
|
name: 'format clear',
|
|
@@ -84,12 +81,6 @@ const items = ref([
|
|
|
84
81
|
click: () => editor.value.chain().focus().unsetAllMarks().clearNodes().run(),
|
|
85
82
|
}
|
|
86
83
|
])
|
|
87
|
-
|
|
88
|
-
function linkAction() {
|
|
89
|
-
return editor.value.isActive('link')
|
|
90
|
-
? editor.value.chain().focus().unsetLink().run()
|
|
91
|
-
: bubbleMenuLinkButton.value?.open()
|
|
92
|
-
}
|
|
93
84
|
</script>
|
|
94
85
|
|
|
95
86
|
<template>
|
|
@@ -98,6 +89,10 @@ function linkAction() {
|
|
|
98
89
|
:editor="editor"
|
|
99
90
|
:options="{ placement: 'bottom' }"
|
|
100
91
|
:shouldShow="({ editor, view, state, from, to }) => {
|
|
92
|
+
if (editor.isActive('image') || editor.isActive('code') || editor.isActive('codeBlock')) {
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
|
|
101
96
|
const docSize = editor.state.doc.content.size
|
|
102
97
|
const isAllSelected = from === 0 && to === docSize
|
|
103
98
|
|
|
@@ -120,8 +115,6 @@ function linkAction() {
|
|
|
120
115
|
</VCard>
|
|
121
116
|
</div>
|
|
122
117
|
</BubbleMenu>
|
|
123
|
-
|
|
124
|
-
<LinkDialog ref="bubbleMenuLinkButton" />
|
|
125
118
|
</template>
|
|
126
119
|
|
|
127
120
|
<style scoped lang="scss">
|
|
@@ -13,7 +13,8 @@ import { useTheme } from "vuetify/framework";
|
|
|
13
13
|
|
|
14
14
|
const props = defineProps({
|
|
15
15
|
content: String|Object,
|
|
16
|
-
|
|
16
|
+
variantBtn: { type: String, default () { return 'elevated' } },
|
|
17
|
+
variantField: { type: String, default () { return 'solo' } },
|
|
17
18
|
toolbar: { type: Boolean, default () { return true } },
|
|
18
19
|
items: { type: Array<string>, default() { return [] }},
|
|
19
20
|
itemsExclude: { type: Boolean, default() { return false } },
|
|
@@ -62,7 +63,8 @@ onBeforeUnmount(() => {
|
|
|
62
63
|
<template v-if="toolbar">
|
|
63
64
|
<Toolbar
|
|
64
65
|
v-if="editor"
|
|
65
|
-
:variant="
|
|
66
|
+
:variant-btn="variantBtn"
|
|
67
|
+
:variant-field="variantField"
|
|
66
68
|
:font-measure="fontMeasure"
|
|
67
69
|
:items="items"
|
|
68
70
|
:items-exclude="itemsExclude"
|
|
@@ -72,9 +74,9 @@ onBeforeUnmount(() => {
|
|
|
72
74
|
|
|
73
75
|
<div :class="`border border-t-0 rounded-b-${rounded}`">
|
|
74
76
|
<div class="pa-2 tiptapify-container">
|
|
75
|
-
<MenuFloating v-if="floatingMenu" :variant="
|
|
77
|
+
<MenuFloating v-if="floatingMenu" :variant="variantBtn" :theme="theme" />
|
|
76
78
|
|
|
77
|
-
<MenuBubble v-if="bubbleMenu" :variant="
|
|
79
|
+
<MenuBubble v-if="bubbleMenu" :variant="variantBtn" :theme="theme" />
|
|
78
80
|
|
|
79
81
|
<EditorContent :editor="editor" class="tiptapify-editor" />
|
|
80
82
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { Editor } from "@tiptap/vue-3";
|
|
3
|
+
import ImageDialog from "@tiptapify/extensions/components/ImageDialog.vue";
|
|
3
4
|
import LinkDialog from "@tiptapify/extensions/components/LinkDialog.vue";
|
|
4
5
|
import ShowSourceDialog from "@tiptapify/extensions/components/ShowSourceDialog.vue";
|
|
5
6
|
import PreviewDialog from "@tiptapify/extensions/components/PreviewDialog.vue";
|
|
@@ -11,7 +12,8 @@ import { useI18n } from "vue-i18n";
|
|
|
11
12
|
import { toolbarItems, ToolbarItemSections } from "@tiptapify/components/Toolbar/items";
|
|
12
13
|
|
|
13
14
|
const props = defineProps({
|
|
14
|
-
|
|
15
|
+
variantBtn: { type: String, default () { return 'elevated' }},
|
|
16
|
+
variantField: { type: String, default () { return 'solo' }},
|
|
15
17
|
items: { type: Array<string>, default() { return [] }},
|
|
16
18
|
itemsExclude: { type: Boolean, default() { return false } },
|
|
17
19
|
headingLevels: { type: Array<number>, default() { return [] }},
|
|
@@ -26,14 +28,11 @@ const { t } = useI18n();
|
|
|
26
28
|
|
|
27
29
|
const editor = inject('tiptapifyEditor') as Ref<Editor>
|
|
28
30
|
|
|
29
|
-
const toolbarLinkButton = ref(null)
|
|
30
|
-
|
|
31
31
|
const items = toolbarItems(
|
|
32
32
|
editor,
|
|
33
33
|
computed(() => props.fontMeasure).value,
|
|
34
34
|
{ list: computed(() => props.items).value, exclude: computed(() => props.itemsExclude).value },
|
|
35
|
-
computed(() => props.headingLevels).value
|
|
36
|
-
toolbarLinkButton
|
|
35
|
+
computed(() => props.headingLevels).value
|
|
37
36
|
)
|
|
38
37
|
const toolbarItemsRef: Ref<ToolbarItemSections> = ref(items)
|
|
39
38
|
|
|
@@ -44,15 +43,15 @@ const toolbarItemsRef: Ref<ToolbarItemSections> = ref(items)
|
|
|
44
43
|
<VToolbar elevation="1" :theme="theme" height="auto" :class="`ps-1 rounded-t-${rounded}`">
|
|
45
44
|
<VToolbarItems class="py-2">
|
|
46
45
|
<template v-for="(toolbarSection, sectionKey) in toolbarItemsRef" :key="sectionKey">
|
|
47
|
-
<Group v-if="toolbarSection.group" :variant="
|
|
46
|
+
<Group v-if="toolbarSection.group" :variant="variantBtn" :toolbar-section="toolbarSection" />
|
|
48
47
|
|
|
49
|
-
<Toggle v-else-if="toolbarSection.toggle" :variant="
|
|
48
|
+
<Toggle v-else-if="toolbarSection.toggle" :variant="variantBtn" :toolbar-section="toolbarSection" />
|
|
50
49
|
|
|
51
50
|
<VBtn
|
|
52
51
|
v-else
|
|
53
52
|
v-for="(toolbarItem, itemKey) in toolbarSection.items"
|
|
54
53
|
:key="itemKey"
|
|
55
|
-
:variant="
|
|
54
|
+
:variant="variantBtn"
|
|
56
55
|
v-bind="toolbarItem.props"
|
|
57
56
|
v-on="toolbarItem.attrs"
|
|
58
57
|
class="menu-button"
|
|
@@ -73,9 +72,10 @@ const toolbarItemsRef: Ref<ToolbarItemSections> = ref(items)
|
|
|
73
72
|
</VToolbarItems>
|
|
74
73
|
</VToolbar>
|
|
75
74
|
|
|
76
|
-
<
|
|
75
|
+
<ImageDialog :variant-field="variantField" />
|
|
76
|
+
<LinkDialog :variant-field="variantField" />
|
|
77
77
|
<PreviewDialog />
|
|
78
|
-
<ShowSourceDialog />
|
|
78
|
+
<ShowSourceDialog :variant-field="variantField" />
|
|
79
79
|
</div>
|
|
80
80
|
</template>
|
|
81
81
|
|
|
@@ -3,11 +3,11 @@ import { Editor } from "@tiptap/vue-3";
|
|
|
3
3
|
import TableBuilder from "@tiptapify/extensions/components/TableBuilder.vue";
|
|
4
4
|
import { computed, markRaw, Ref } 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: {
|
|
@@ -49,8 +49,7 @@ export function toolbarItems(
|
|
|
49
49
|
editor: any,
|
|
50
50
|
fontMeasure: string,
|
|
51
51
|
items: { list: Array<string>, exclude: boolean },
|
|
52
|
-
customHeadingLevels: Array<number
|
|
53
|
-
toolbarLinkButton: Ref,
|
|
52
|
+
customHeadingLevels: Array<number>
|
|
54
53
|
): ToolbarItemSections {
|
|
55
54
|
const styleItems = ref(getStyleItems(editor.value, fontMeasure, customHeadingLevels))
|
|
56
55
|
const formatItems = ref(getFormatItems(editor.value))
|
|
@@ -59,7 +58,7 @@ export function toolbarItems(
|
|
|
59
58
|
const listItems = ref(getListItems(editor.value))
|
|
60
59
|
const actionsItems = ref(getActionsItems(editor.value))
|
|
61
60
|
const miscItems = ref(getMiscItems(editor.value))
|
|
62
|
-
const mediaItems = ref(getMediaItems(editor.value
|
|
61
|
+
const mediaItems = ref(getMediaItems(editor.value))
|
|
63
62
|
|
|
64
63
|
const allMenuItems: ToolbarItemSections = {
|
|
65
64
|
/**
|
|
@@ -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'
|
|
@@ -69,11 +70,12 @@ export function editorExtensions (placeholder: string, slashCommands: boolean) {
|
|
|
69
70
|
Typography,
|
|
70
71
|
Underline,
|
|
71
72
|
Highlight,
|
|
72
|
-
|
|
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,
|
|
@@ -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>
|
|
@@ -4,78 +4,82 @@ import * as mdi from '@mdi/js'
|
|
|
4
4
|
import { Editor } from "@tiptap/vue-3";
|
|
5
5
|
|
|
6
6
|
import { useI18n } from 'vue-i18n'
|
|
7
|
-
import { computed, inject, Ref, ref
|
|
7
|
+
import { computed, inject, onMounted, onUnmounted, Ref, ref } from 'vue'
|
|
8
8
|
|
|
9
9
|
import helpers from '@tiptapify/utils/helpers'
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
defineProps({
|
|
12
|
+
variantBtn: { type: String, default() { return 'elevated' }},
|
|
13
|
+
variantField: { type: String, default() { return 'solo' }}
|
|
14
|
+
})
|
|
12
15
|
|
|
13
16
|
const { ucFirst } = helpers
|
|
14
17
|
|
|
15
|
-
interface Props {
|
|
16
|
-
value?: string
|
|
17
|
-
target?: '_self' | '_blank'
|
|
18
|
-
destroy?: () => void
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
22
|
-
value: undefined,
|
|
23
|
-
target: '_blank',
|
|
24
|
-
destroy: undefined
|
|
25
|
-
})
|
|
26
|
-
|
|
27
18
|
const editor = inject('tiptapifyEditor') as Ref<Editor>
|
|
28
|
-
|
|
29
19
|
const { t } = useI18n()
|
|
30
20
|
|
|
31
21
|
const generateLinkAttrs = () => ({
|
|
32
22
|
href: '',
|
|
33
|
-
target: '_blank'
|
|
23
|
+
target: '_blank',
|
|
24
|
+
cssClass: '',
|
|
25
|
+
rel: ''
|
|
34
26
|
})
|
|
35
27
|
|
|
28
|
+
const relAttrs = ['alternate', 'author', 'bookmark', 'external', 'help', 'license', 'next', 'nofollow', 'noreferrer', 'noopener', 'prev', 'search', 'tag']
|
|
29
|
+
|
|
36
30
|
const attrs = ref(generateLinkAttrs())
|
|
37
31
|
|
|
38
32
|
const dialog = ref<boolean>(false)
|
|
39
33
|
|
|
40
34
|
const isDisabled = computed(() => {
|
|
41
|
-
const { href
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return props.value === href && props.target === target
|
|
35
|
+
const { href } = attrs.value
|
|
36
|
+
return !href
|
|
45
37
|
})
|
|
46
38
|
|
|
47
39
|
function apply() {
|
|
48
|
-
|
|
40
|
+
let { href, target, rel, cssClass } = attrs.value
|
|
41
|
+
target = target ? '_blank' : '_self'
|
|
42
|
+
rel = rel.join(' ')
|
|
49
43
|
|
|
50
44
|
if (href) {
|
|
51
|
-
editor.value.chain().focus().extendMarkRange('link').setLink({ href, target }).run()
|
|
45
|
+
editor.value.chain().focus().extendMarkRange('link').setLink({ href, target, rel, class: cssClass }).run()
|
|
52
46
|
}
|
|
47
|
+
|
|
53
48
|
close()
|
|
54
49
|
}
|
|
55
50
|
|
|
56
|
-
function
|
|
57
|
-
|
|
51
|
+
function clear() {
|
|
52
|
+
editor.value.chain().focus().extendMarkRange('link').unsetLink().run()
|
|
53
|
+
|
|
54
|
+
close()
|
|
58
55
|
}
|
|
59
56
|
|
|
60
57
|
function close() {
|
|
61
58
|
dialog.value = false
|
|
59
|
+
|
|
62
60
|
attrs.value = generateLinkAttrs()
|
|
61
|
+
}
|
|
63
62
|
|
|
64
|
-
|
|
63
|
+
const showLink = (event: CustomEvent) => {
|
|
64
|
+
attrs.value.href = event.detail.link?.href
|
|
65
|
+
attrs.value.target = event.detail.link?.target === '_blank'
|
|
66
|
+
attrs.value.rel = event.detail.link?.rel?.split(' ')
|
|
67
|
+
attrs.value.cssClass = event.detail.link?.class
|
|
68
|
+
|
|
69
|
+
dialog.value = true;
|
|
65
70
|
}
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
onMounted(() => {
|
|
73
|
+
window.addEventListener('tiptapify-show-link', showLink as EventListener)
|
|
74
|
+
})
|
|
69
75
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
target: props.target
|
|
73
|
-
}
|
|
76
|
+
onUnmounted(() => {
|
|
77
|
+
window.removeEventListener('tiptapify-show-link', showLink as EventListener)
|
|
74
78
|
})
|
|
75
79
|
</script>
|
|
76
80
|
|
|
77
81
|
<template>
|
|
78
|
-
<VDialog v-model="dialog" max-width="
|
|
82
|
+
<VDialog v-model="dialog" max-width="800" absolute @click:outside="close">
|
|
79
83
|
<VCard>
|
|
80
84
|
<VToolbar class="px-6" density="compact">
|
|
81
85
|
<span class="headline">{{ ucFirst(t('dialog.link.title')) }}</span>
|
|
@@ -88,13 +92,50 @@ watch(dialog, val => {
|
|
|
88
92
|
</VToolbar>
|
|
89
93
|
|
|
90
94
|
<VCardText>
|
|
91
|
-
<
|
|
95
|
+
<VRow>
|
|
96
|
+
<VCol cols="12" md="9">
|
|
97
|
+
<VTextField v-model="attrs.href" :variant="variantField" :label="ucFirst(t('dialog.link.href'))" autofocus />
|
|
98
|
+
</VCol>
|
|
99
|
+
|
|
100
|
+
<VCol cols="12" md="3">
|
|
101
|
+
<VCheckbox v-model="attrs.target" color="primary" :label="ucFirst(t('dialog.link.target'))" />
|
|
102
|
+
</VCol>
|
|
103
|
+
|
|
104
|
+
<VCol cols="12">
|
|
105
|
+
<VSelect
|
|
106
|
+
v-model="attrs.rel"
|
|
107
|
+
:items="relAttrs"
|
|
108
|
+
:label="ucFirst(t('dialog.link.rel'))"
|
|
109
|
+
:variant="variantField"
|
|
110
|
+
multiple
|
|
111
|
+
chips
|
|
112
|
+
closable-chips
|
|
113
|
+
clearable
|
|
114
|
+
/>
|
|
115
|
+
</VCol>
|
|
116
|
+
|
|
117
|
+
<VCol cols="12">
|
|
118
|
+
<VTextField v-model="attrs.cssClass" :variant="variantField" :label="ucFirst(t('dialog.link.class'))" />
|
|
119
|
+
</VCol>
|
|
120
|
+
</VRow>
|
|
92
121
|
</VCardText>
|
|
93
122
|
|
|
94
123
|
<VCardActions>
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
124
|
+
<VRow>
|
|
125
|
+
<VCol class="d-flex justify-start">
|
|
126
|
+
<VBtn color="warning" v-if="editor.isActive('link')" :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>
|
|
98
139
|
</VCardActions>
|
|
99
140
|
</VCard>
|
|
100
141
|
</VDialog>
|