tiptapify 0.0.28 → 0.0.30
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 -0
- package/dist/tiptapify.css +1 -1
- package/dist/tiptapify.mjs +21902 -21229
- package/dist/tiptapify.umd.js +38 -38
- package/package.json +1 -1
- package/src/components/Toolbar/media.ts +5 -0
- package/src/components/UI/TiptapifyDialog.vue +37 -1
- package/src/components/editorExtensions.ts +3 -1
- package/src/extensions/components/list/bullet/Button.vue +8 -6
- package/src/extensions/components/list/numbered/Button.vue +9 -2
- package/src/extensions/components/list/task/Button.vue +9 -2
- package/src/extensions/components/media/iframe/Button.vue +42 -0
- package/src/extensions/components/media/iframe/IframeDialog.vue +192 -0
- package/src/extensions/components/media/iframe/index.ts +92 -0
- package/src/extensions/components/media/link/LinkDialog.vue +1 -1
- package/src/extensions/components/misc/fullscreen/Button.vue +1 -1
- package/src/i18n/locales/ch.json +12 -1
- package/src/i18n/locales/cz.json +12 -1
- package/src/i18n/locales/de.json +12 -1
- package/src/i18n/locales/en.json +13 -1
- package/src/i18n/locales/es.json +12 -1
- package/src/i18n/locales/fr.json +12 -1
- package/src/i18n/locales/it.json +12 -1
- package/src/i18n/locales/la.json +12 -1
- package/src/i18n/locales/lt.json +12 -1
- package/src/i18n/locales/nl.json +12 -1
- package/src/i18n/locales/pl.json +12 -1
- package/src/i18n/locales/pt.json +12 -1
- package/src/i18n/locales/ru.json +13 -1
- package/src/i18n/locales/se.json +12 -1
- package/src/i18n/locales/ua.json +13 -1
- package/src/types/iframe.ts +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { default as EmojiButton } from "@tiptapify/extensions/components/media/emoji/Button.vue";
|
|
2
2
|
import { default as LinkButton } from "@tiptapify/extensions/components/media/link/Button.vue";
|
|
3
3
|
import { default as ImageButton } from "@tiptapify/extensions/components/media/image/Button.vue";
|
|
4
|
+
import { default as IframeButton } from "@tiptapify/extensions/components/media/iframe/Button.vue";
|
|
4
5
|
import { default as TableButton } from "@tiptapify/extensions/components/media/table/Button.vue";
|
|
5
6
|
import { default as VideoButton } from "@tiptapify/extensions/components/media/video/Button.vue";
|
|
6
7
|
import { markRaw } from "vue";
|
|
@@ -21,6 +22,10 @@ export default {
|
|
|
21
22
|
name: 'video',
|
|
22
23
|
component: markRaw(VideoButton),
|
|
23
24
|
},
|
|
25
|
+
{
|
|
26
|
+
name: 'iframe',
|
|
27
|
+
component: markRaw(IframeButton),
|
|
28
|
+
},
|
|
24
29
|
{
|
|
25
30
|
name: 'emoji',
|
|
26
31
|
component: markRaw(EmojiButton),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
|
|
3
3
|
import * as mdi from "@mdi/js";
|
|
4
|
-
import { inject, nextTick, ref, watch } from "vue";
|
|
4
|
+
import { inject, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
|
5
5
|
|
|
6
6
|
const props = defineProps({
|
|
7
7
|
module: String,
|
|
@@ -100,6 +100,42 @@ watch(() => dialog.value, async () => {
|
|
|
100
100
|
dragElement(movableHandler.value.$el as HTMLElement, movableHandler.value.$el.parentNode as HTMLElement)
|
|
101
101
|
}
|
|
102
102
|
})
|
|
103
|
+
|
|
104
|
+
function resizeListener() {
|
|
105
|
+
if (!dialog.value) {
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const dialogCoordinates = movableHandler.value.$el.parentNode.getBoundingClientRect();
|
|
110
|
+
|
|
111
|
+
function checkBoundLeftTop(type) {
|
|
112
|
+
if (dialogCoordinates[type] < 0) {
|
|
113
|
+
const dialogOffset = movableHandler.value.$el.parentNode.style[type]
|
|
114
|
+
const newOffset = parseInt(dialogOffset) - dialogCoordinates[type]
|
|
115
|
+
movableHandler.value.$el.parentNode.style[type] = `${newOffset}px`
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
checkBoundLeftTop('left')
|
|
119
|
+
checkBoundLeftTop('top')
|
|
120
|
+
|
|
121
|
+
function checkBoundRightBottom(type: 'right' | 'bottom') {
|
|
122
|
+
const data = { side: type === 'right' ? 'left' : 'top', dim: type === 'right' ? 'width' : 'height', windowDim: type === 'right' ? 'innerWidth' : 'innerHeight' }
|
|
123
|
+
if ((dialogCoordinates[data.side] + dialogCoordinates[data.dim]) > window[data.windowDim]) {
|
|
124
|
+
const newOffset = ((window[data.windowDim] - dialogCoordinates[data.dim]) / 2).toFixed()
|
|
125
|
+
movableHandler.value.$el.parentNode.style[data.side] = `${newOffset}px`
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
checkBoundRightBottom('right')
|
|
129
|
+
checkBoundRightBottom('bottom')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
onMounted(() => {
|
|
133
|
+
addEventListener('resize', resizeListener)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
onBeforeUnmount(() => {
|
|
137
|
+
removeEventListener('resize', resizeListener)
|
|
138
|
+
})
|
|
103
139
|
</script>
|
|
104
140
|
|
|
105
141
|
<template>
|
|
@@ -25,9 +25,10 @@ import { TableKit } from '@tiptap/extension-table'
|
|
|
25
25
|
import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight'
|
|
26
26
|
import { InvisibleCharacters } from '@tiptap/extension-invisible-characters'
|
|
27
27
|
|
|
28
|
+
import { default as Iframe } from '@tiptapify/extensions/components/media/iframe'
|
|
28
29
|
import { BulletListCircle, BulletListSquare } from '@tiptapify/extensions/components/list/bullet'
|
|
29
|
-
import { TiptapifyLink } from '@tiptapify/extensions/components/media/link'
|
|
30
30
|
import { TiptapifyImage } from '@tiptapify/extensions/components/media/image'
|
|
31
|
+
import { TiptapifyLink } from '@tiptapify/extensions/components/media/link'
|
|
31
32
|
import { TiptapifyVideo } from '@tiptapify/extensions/components/media/video'
|
|
32
33
|
import CodeBlockComponent from '@tiptapify/extensions/components/CodeBlockComponent.vue'
|
|
33
34
|
import SlashCommands from '@tiptapify/extensions/slash-commands'
|
|
@@ -84,6 +85,7 @@ export function editorExtensions (placeholder: string, slashCommands: boolean, c
|
|
|
84
85
|
}),
|
|
85
86
|
TiptapifyImage,
|
|
86
87
|
TiptapifyVideo,
|
|
88
|
+
Iframe,
|
|
87
89
|
Superscript,
|
|
88
90
|
Subscript,
|
|
89
91
|
TableKit,
|
|
@@ -54,9 +54,9 @@ checkButtonOrMenu()
|
|
|
54
54
|
|
|
55
55
|
function toggleList(listType: string) {
|
|
56
56
|
switch(listType) {
|
|
57
|
-
case '
|
|
58
|
-
case '
|
|
59
|
-
case '
|
|
57
|
+
case 'bulletList': editor.value.commands.toggleBulletList(); break;
|
|
58
|
+
case 'bulletListCircle': editor.value.commands.toggleBulletListCircle(); break;
|
|
59
|
+
case 'bulletListSquare': editor.value.commands.toggleBulletListSquare(); break;
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -68,8 +68,10 @@ const buttonActive = computed(() => {
|
|
|
68
68
|
|
|
69
69
|
const buttonDisabled = computed(() => {
|
|
70
70
|
return !editor.value.can().chain().focus().toggleBulletList().run() &&
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
!editor.value.can().chain().focus().toggleBulletListCircle().run() &&
|
|
72
|
+
!editor.value.can().chain().focus().toggleBulletListSquare().run() &&
|
|
73
|
+
!editor.value.can().chain().focus().toggleOrderedList().run() &&
|
|
74
|
+
!editor.value.can().chain().focus().toggleTaskList().run()
|
|
73
75
|
})
|
|
74
76
|
|
|
75
77
|
watch(() => bulletLists.value, () => {
|
|
@@ -100,7 +102,7 @@ watch(() => bulletLists.value, () => {
|
|
|
100
102
|
:key="key"
|
|
101
103
|
link
|
|
102
104
|
:active="editor.isActive(bulletList.name)"
|
|
103
|
-
@click="toggleList(
|
|
105
|
+
@click="toggleList(bulletList.name)"
|
|
104
106
|
>
|
|
105
107
|
<VTooltip activator="parent">
|
|
106
108
|
{{ t('lists.bullet') }}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as mdi from '@mdi/js'
|
|
4
4
|
import { Editor } from "@tiptap/vue-3";
|
|
5
5
|
import BtnIcon from "@tiptapify/components/UI/BtnIcon.vue";
|
|
6
|
-
import { inject, Ref } from "vue";
|
|
6
|
+
import { computed, inject, Ref } from "vue";
|
|
7
7
|
|
|
8
8
|
import defaults from '@tiptapify/constants/defaults'
|
|
9
9
|
|
|
@@ -15,12 +15,19 @@ const editor = inject('tiptapifyEditor') as Ref<Editor>
|
|
|
15
15
|
|
|
16
16
|
const { t } = inject('tiptapifyI18n') as any
|
|
17
17
|
|
|
18
|
+
const buttonDisabled = computed(() => {
|
|
19
|
+
return !editor.value.can().chain().focus().toggleBulletList().run() &&
|
|
20
|
+
!editor.value.can().chain().focus().toggleBulletListCircle().run() &&
|
|
21
|
+
!editor.value.can().chain().focus().toggleBulletListSquare().run() &&
|
|
22
|
+
!editor.value.can().chain().focus().toggleOrderedList().run() &&
|
|
23
|
+
!editor.value.can().chain().focus().toggleTaskList().run()
|
|
24
|
+
})
|
|
18
25
|
</script>
|
|
19
26
|
|
|
20
27
|
<template>
|
|
21
28
|
<VBtn
|
|
22
29
|
:color="editor.isActive('orderedList') ? 'primary' : ''"
|
|
23
|
-
:disabled="
|
|
30
|
+
:disabled="buttonDisabled"
|
|
24
31
|
:variant="variantBtn"
|
|
25
32
|
@click="editor.commands.toggleOrderedList()"
|
|
26
33
|
size="32"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as mdi from '@mdi/js'
|
|
4
4
|
import { Editor } from "@tiptap/vue-3";
|
|
5
5
|
import BtnIcon from "@tiptapify/components/UI/BtnIcon.vue";
|
|
6
|
-
import { inject, Ref } from "vue";
|
|
6
|
+
import { computed, inject, Ref } from "vue";
|
|
7
7
|
|
|
8
8
|
import defaults from '@tiptapify/constants/defaults'
|
|
9
9
|
|
|
@@ -15,12 +15,19 @@ const editor = inject('tiptapifyEditor') as Ref<Editor>
|
|
|
15
15
|
|
|
16
16
|
const { t } = inject('tiptapifyI18n') as any
|
|
17
17
|
|
|
18
|
+
const buttonDisabled = computed(() => {
|
|
19
|
+
return !editor.value.can().chain().focus().toggleBulletList().run() &&
|
|
20
|
+
!editor.value.can().chain().focus().toggleBulletListCircle().run() &&
|
|
21
|
+
!editor.value.can().chain().focus().toggleBulletListSquare().run() &&
|
|
22
|
+
!editor.value.can().chain().focus().toggleOrderedList().run() &&
|
|
23
|
+
!editor.value.can().chain().focus().toggleTaskList().run()
|
|
24
|
+
})
|
|
18
25
|
</script>
|
|
19
26
|
|
|
20
27
|
<template>
|
|
21
28
|
<VBtn
|
|
22
29
|
:color="editor.isActive('taskList') ? 'primary' : ''"
|
|
23
|
-
:disabled="
|
|
30
|
+
:disabled="buttonDisabled"
|
|
24
31
|
:variant="variantBtn"
|
|
25
32
|
@click="editor.commands.toggleTaskList()"
|
|
26
33
|
size="32"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
|
|
3
|
+
import { Editor } from "@tiptap/vue-3";
|
|
4
|
+
import BtnIcon from "@tiptapify/components/UI/BtnIcon.vue";
|
|
5
|
+
import IframeDialog from "@tiptapify/extensions/components/media/iframe/IframeDialog.vue";
|
|
6
|
+
import { inject, ref, Ref } from "vue";
|
|
7
|
+
|
|
8
|
+
import defaults from '@tiptapify/constants/defaults'
|
|
9
|
+
|
|
10
|
+
defineProps({
|
|
11
|
+
variantBtn: { type: String, default: defaults.variantBtn }
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const editor = inject('tiptapifyEditor') as Ref<Editor>
|
|
15
|
+
|
|
16
|
+
const { t } = inject('tiptapifyI18n') as any
|
|
17
|
+
|
|
18
|
+
const dialog = ref(null)
|
|
19
|
+
|
|
20
|
+
const icon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M1 12h9.8L8.3 9.5l1.4-1.4l4.9 4.9l-4.9 4.9l-1.4-1.4l2.5-2.5H1zM21 2H3c-1.1 0-2 .9-2 2v6.1h2V6h18v14H3v-4H1v4c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2"/></svg>'
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<VBtn
|
|
25
|
+
:color="editor.isActive('iframe') ? 'primary' : ''"
|
|
26
|
+
:variant="variantBtn"
|
|
27
|
+
@click="dialog?.open()"
|
|
28
|
+
size="32"
|
|
29
|
+
>
|
|
30
|
+
<VTooltip activator="parent">
|
|
31
|
+
{{ t('media.iframe') }}
|
|
32
|
+
</VTooltip>
|
|
33
|
+
<BtnIcon :icon="icon" />
|
|
34
|
+
</VBtn>
|
|
35
|
+
|
|
36
|
+
<IframeDialog ref="dialog" />
|
|
37
|
+
<!-- Dialog here -->
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<style lang="scss" scoped>
|
|
41
|
+
|
|
42
|
+
</style>
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
|
|
3
|
+
import { Editor } from "@tiptap/vue-3";
|
|
4
|
+
import TiptapifyDialog from "@tiptapify/components/UI/TiptapifyDialog.vue";
|
|
5
|
+
import defaults from "@tiptapify/constants/defaults";
|
|
6
|
+
|
|
7
|
+
import type { iframeOptions } from '@tiptapify/types/iframe'
|
|
8
|
+
|
|
9
|
+
import { computed, inject, Ref, ref, watch } from 'vue'
|
|
10
|
+
|
|
11
|
+
defineProps({
|
|
12
|
+
variantBtn: { type: String, default() { return defaults.variantBtn }},
|
|
13
|
+
variantField: { type: String, default() { return defaults.variantField }}
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const editor = inject('tiptapifyEditor') as Ref<Editor>
|
|
17
|
+
const { t } = inject('tiptapifyI18n') as any
|
|
18
|
+
|
|
19
|
+
const generateIframeAttrs = (): iframeOptions => ({
|
|
20
|
+
src: '',
|
|
21
|
+
name: '',
|
|
22
|
+
title: '',
|
|
23
|
+
height: null,
|
|
24
|
+
width: null,
|
|
25
|
+
frameborder: 0,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const attrs = ref(generateIframeAttrs())
|
|
29
|
+
const srcInvalid = ref(false)
|
|
30
|
+
|
|
31
|
+
const dialog = ref(null)
|
|
32
|
+
|
|
33
|
+
const isDisabled = computed(() => {
|
|
34
|
+
const { src } = attrs.value
|
|
35
|
+
return !src
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
function apply() {
|
|
39
|
+
let { src, name, title, width, height, frameborder } = attrs.value
|
|
40
|
+
|
|
41
|
+
const options: iframeOptions = {
|
|
42
|
+
src,
|
|
43
|
+
name,
|
|
44
|
+
title,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (width) {
|
|
48
|
+
options.width = width
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (height) {
|
|
52
|
+
options.height = height
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (frameborder) {
|
|
56
|
+
options.frameborder = Number(frameborder)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (src) {
|
|
60
|
+
editor.value.chain().focus().setIframe(options).run()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
close()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function clear() {
|
|
67
|
+
editor.value.commands.deleteSelection()
|
|
68
|
+
|
|
69
|
+
close()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function close() {
|
|
73
|
+
dialog.value.close()
|
|
74
|
+
|
|
75
|
+
attrs.value = generateIframeAttrs()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function open () {
|
|
79
|
+
const iframe = editor.value.getAttributes('iframe')
|
|
80
|
+
|
|
81
|
+
attrs.value.src = iframe?.src
|
|
82
|
+
attrs.value.name = iframe?.name
|
|
83
|
+
attrs.value.title = iframe?.title
|
|
84
|
+
attrs.value.width = iframe?.width
|
|
85
|
+
attrs.value.height = iframe?.height
|
|
86
|
+
attrs.value.frameborder = !!iframe?.frameborder
|
|
87
|
+
|
|
88
|
+
dialog.value.open()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
defineExpose({ open })
|
|
92
|
+
|
|
93
|
+
watch(() => attrs.value.src, () => {
|
|
94
|
+
const azAZ09 = 'a-zA-Z0-9'
|
|
95
|
+
|
|
96
|
+
const regexHrefProto = 'https?:\\/\\/'
|
|
97
|
+
const regexHrefDomain = `[${azAZ09}]+(\\.[${azAZ09}]+(-?[${azAZ09}]+)?)+`
|
|
98
|
+
const regexHrefPath = `(\\/[${azAZ09}\\-]+)*`
|
|
99
|
+
const regexHrefFragment = '(#[^\\s]*)?'
|
|
100
|
+
const regexHrefQueryParam = `(\\?[${azAZ09}\\-_]+((\\[[${azAZ09}]+\\])?=[${azAZ09}\\-_%]+)?)?`
|
|
101
|
+
const regexHrefQueryParamExtra = `(&[${azAZ09}\\-_]+((\\[[${azAZ09}]+\\])?=[${azAZ09}\\-_%]+)?)*`
|
|
102
|
+
const regexHref = `${regexHrefProto}${regexHrefDomain}${regexHrefPath}${regexHrefFragment}${regexHrefQueryParam}${regexHrefQueryParamExtra}`
|
|
103
|
+
|
|
104
|
+
const regex = new RegExp(`^(${regexHref})$`, 'i')
|
|
105
|
+
|
|
106
|
+
srcInvalid.value = (attrs.value.src || '') !== '' && !regex.test(attrs.value.src)
|
|
107
|
+
})
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
<template>
|
|
111
|
+
<TiptapifyDialog ref="dialog" module="iframe" :title="t('dialog.iframe.dialog_title')" :max-width="800">
|
|
112
|
+
<template #content>
|
|
113
|
+
<VCardText>
|
|
114
|
+
<VRow>
|
|
115
|
+
<VCol cols="12">
|
|
116
|
+
<VTextField
|
|
117
|
+
v-model="attrs.src"
|
|
118
|
+
density="compact"
|
|
119
|
+
variant="outlined"
|
|
120
|
+
:label="t('dialog.iframe.src')"
|
|
121
|
+
:error-messages="srcInvalid ? t('dialog.iframe.src_invalid') : ''"
|
|
122
|
+
/>
|
|
123
|
+
</VCol>
|
|
124
|
+
|
|
125
|
+
<VCol cols="12" md="6">
|
|
126
|
+
<VRow>
|
|
127
|
+
<VCol cols="12">
|
|
128
|
+
<VTextField v-model="attrs.name" density="compact" variant="outlined" :label="t('dialog.iframe.name')" />
|
|
129
|
+
</VCol>
|
|
130
|
+
|
|
131
|
+
<VCol cols="12">
|
|
132
|
+
<VTextField v-model="attrs.title" density="compact" variant="outlined" :label="t('dialog.iframe.title')" />
|
|
133
|
+
</VCol>
|
|
134
|
+
</VRow>
|
|
135
|
+
</VCol>
|
|
136
|
+
|
|
137
|
+
<VCol cols="12" md="6">
|
|
138
|
+
<VRow>
|
|
139
|
+
<VCol cols="12" md="6">
|
|
140
|
+
<VTextField
|
|
141
|
+
v-model="attrs.width"
|
|
142
|
+
type="number"
|
|
143
|
+
density="compact"
|
|
144
|
+
variant="outlined"
|
|
145
|
+
:precision="0"
|
|
146
|
+
:min="1"
|
|
147
|
+
:label="t('dialog.iframe.width')"
|
|
148
|
+
/>
|
|
149
|
+
</VCol>
|
|
150
|
+
|
|
151
|
+
<VCol cols="12" md="6">
|
|
152
|
+
<VTextField
|
|
153
|
+
v-model="attrs.height"
|
|
154
|
+
type="number"
|
|
155
|
+
density="compact"
|
|
156
|
+
variant="outlined"
|
|
157
|
+
:precision="0"
|
|
158
|
+
:min="1"
|
|
159
|
+
:label="t('dialog.iframe.height')"
|
|
160
|
+
/>
|
|
161
|
+
</VCol>
|
|
162
|
+
|
|
163
|
+
<VCol cols="12" class="ml-2">
|
|
164
|
+
<VSwitch v-model="attrs.frameborder" density="compact" :hideDetails="true" :label="t('dialog.iframe.frameborder')" />
|
|
165
|
+
</VCol>
|
|
166
|
+
</VRow>
|
|
167
|
+
</VCol>
|
|
168
|
+
</VRow>
|
|
169
|
+
</VCardText>
|
|
170
|
+
</template>
|
|
171
|
+
|
|
172
|
+
<template #actions>
|
|
173
|
+
<VCardActions>
|
|
174
|
+
<VRow>
|
|
175
|
+
<VCol class="d-flex justify-start">
|
|
176
|
+
<VBtn color="warning" v-if="editor.isActive('image')" :variant="variantBtn" :disabled="isDisabled" @click="clear">
|
|
177
|
+
{{ t('dialog.clear') }}
|
|
178
|
+
</VBtn>
|
|
179
|
+
</VCol>
|
|
180
|
+
<VCol class="d-flex justify-end">
|
|
181
|
+
<VBtn :variant="variantBtn" @click="close" class="mr-2">
|
|
182
|
+
{{ t('dialog.close') }}
|
|
183
|
+
</VBtn>
|
|
184
|
+
<VBtn color="primary" :variant="variantBtn" :disabled="isDisabled" @click="apply">
|
|
185
|
+
{{ t('dialog.apply') }}
|
|
186
|
+
</VBtn>
|
|
187
|
+
</VCol>
|
|
188
|
+
</VRow>
|
|
189
|
+
</VCardActions>
|
|
190
|
+
</template>
|
|
191
|
+
</TiptapifyDialog>
|
|
192
|
+
</template>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Node } from '@tiptap/core'
|
|
2
|
+
|
|
3
|
+
export interface IframeOptions {
|
|
4
|
+
allowFullscreen: boolean
|
|
5
|
+
HTMLAttributes: {
|
|
6
|
+
[key: string]: any
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
declare module '@tiptap/core' {
|
|
11
|
+
interface Commands<ReturnType> {
|
|
12
|
+
iframe: {
|
|
13
|
+
/**
|
|
14
|
+
* Add an iframe
|
|
15
|
+
*/
|
|
16
|
+
setIframe: (options: { src: string }) => ReturnType
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default Node.create<IframeOptions>({
|
|
22
|
+
name: 'iframe',
|
|
23
|
+
|
|
24
|
+
group: 'block',
|
|
25
|
+
|
|
26
|
+
atom: true,
|
|
27
|
+
|
|
28
|
+
addOptions() {
|
|
29
|
+
return {
|
|
30
|
+
allowFullscreen: true,
|
|
31
|
+
HTMLAttributes: {
|
|
32
|
+
class: 'iframe-wrapper',
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
addAttributes() {
|
|
38
|
+
return {
|
|
39
|
+
src: {
|
|
40
|
+
default: null,
|
|
41
|
+
},
|
|
42
|
+
name: {
|
|
43
|
+
default: null,
|
|
44
|
+
},
|
|
45
|
+
title: {
|
|
46
|
+
default: null,
|
|
47
|
+
},
|
|
48
|
+
width: {
|
|
49
|
+
default: null,
|
|
50
|
+
},
|
|
51
|
+
height: {
|
|
52
|
+
default: null,
|
|
53
|
+
},
|
|
54
|
+
frameborder: {
|
|
55
|
+
default: 0,
|
|
56
|
+
},
|
|
57
|
+
allowfullscreen: {
|
|
58
|
+
default: this.options.allowFullscreen,
|
|
59
|
+
parseHTML: () => this.options.allowFullscreen,
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
parseHTML() {
|
|
65
|
+
return [
|
|
66
|
+
{
|
|
67
|
+
tag: 'iframe',
|
|
68
|
+
},
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
renderHTML({ HTMLAttributes }) {
|
|
73
|
+
return ['div', this.options.HTMLAttributes, ['iframe', HTMLAttributes]]
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
addCommands() {
|
|
77
|
+
return {
|
|
78
|
+
setIframe:
|
|
79
|
+
(options: { src: string }) =>
|
|
80
|
+
({ tr, dispatch }) => {
|
|
81
|
+
const { selection } = tr
|
|
82
|
+
const node = this.type.create(options)
|
|
83
|
+
|
|
84
|
+
if (dispatch) {
|
|
85
|
+
tr.replaceRangeWith(selection.from, selection.to, node)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return true
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
})
|
|
@@ -102,7 +102,7 @@ watch(() => attrs.value.href, () => {
|
|
|
102
102
|
|
|
103
103
|
const regex = new RegExp(`^(${regexAll})$`, 'i')
|
|
104
104
|
|
|
105
|
-
hrefInvalid.value = attrs.value.href !== '' && !regex.test(attrs.value.href)
|
|
105
|
+
hrefInvalid.value = (attrs.value.href || '') !== '' && !regex.test(attrs.value.href)
|
|
106
106
|
})
|
|
107
107
|
</script>
|
|
108
108
|
|
|
@@ -44,7 +44,7 @@ async function changeEditorContainer(source: string, target: string) {
|
|
|
44
44
|
<template>
|
|
45
45
|
<VBtn @click="dialog ? dialogClose() : dialogOpen()" size="32" :variant="variantBtn">
|
|
46
46
|
<VTooltip activator="parent">
|
|
47
|
-
{{ t('misc.
|
|
47
|
+
{{ t('misc.fullscreen') }}
|
|
48
48
|
</VTooltip>
|
|
49
49
|
<BtnIcon :icon="dialog ? `mdiSvg:${mdi.mdiFullscreenExit}` : `mdiSvg:${mdi.mdiFullscreen}`" />
|
|
50
50
|
</VBtn>
|
package/src/i18n/locales/ch.json
CHANGED
|
@@ -95,6 +95,16 @@
|
|
|
95
95
|
"apply": "应用",
|
|
96
96
|
"clear": "清除",
|
|
97
97
|
"close": "关闭",
|
|
98
|
+
"iframe": {
|
|
99
|
+
"dialog_title": "添加/编辑内嵌框架",
|
|
100
|
+
"src": "源地址",
|
|
101
|
+
"name": "名称",
|
|
102
|
+
"title": "标题",
|
|
103
|
+
"width": "宽度",
|
|
104
|
+
"height": "高度",
|
|
105
|
+
"frameborder": "框架边框",
|
|
106
|
+
"src_invalid": "输入的URL无效"
|
|
107
|
+
},
|
|
98
108
|
"image": {
|
|
99
109
|
"title": "添加/修改图片",
|
|
100
110
|
"src": "源",
|
|
@@ -127,8 +137,9 @@
|
|
|
127
137
|
}
|
|
128
138
|
},
|
|
129
139
|
"misc": {
|
|
130
|
-
"
|
|
140
|
+
"fullscreen": "全屏模式",
|
|
131
141
|
"preview": "预览",
|
|
142
|
+
"source": "查看源代码",
|
|
132
143
|
"toggleInvisibleCharacters": "显示/隐藏不可见字符"
|
|
133
144
|
},
|
|
134
145
|
"footer": {
|
package/src/i18n/locales/cz.json
CHANGED
|
@@ -95,6 +95,16 @@
|
|
|
95
95
|
"apply": "Použít",
|
|
96
96
|
"clear": "Vymazat",
|
|
97
97
|
"close": "Zavřít",
|
|
98
|
+
"iframe": {
|
|
99
|
+
"dialog_title": "Přidat/upravit iframe",
|
|
100
|
+
"src": "Zdroj",
|
|
101
|
+
"name": "Název",
|
|
102
|
+
"title": "Titulek",
|
|
103
|
+
"width": "Šířka",
|
|
104
|
+
"height": "Výška",
|
|
105
|
+
"frameborder": "Okraj rámce",
|
|
106
|
+
"src_invalid": "Zadaná URL adresa je neplatná"
|
|
107
|
+
},
|
|
98
108
|
"image": {
|
|
99
109
|
"title": "Přidání/úprava obrázku",
|
|
100
110
|
"src": "Zdroj",
|
|
@@ -127,8 +137,9 @@
|
|
|
127
137
|
}
|
|
128
138
|
},
|
|
129
139
|
"misc": {
|
|
130
|
-
"
|
|
140
|
+
"fullscreen": "Režim celé obrazovky",
|
|
131
141
|
"preview": "Náhled",
|
|
142
|
+
"source": "Zobrazit zdrojový kód",
|
|
132
143
|
"toggleInvisibleCharacters": "Zobrazit/skrýt neviditelné znaky"
|
|
133
144
|
},
|
|
134
145
|
"footer": {
|
package/src/i18n/locales/de.json
CHANGED
|
@@ -95,6 +95,16 @@
|
|
|
95
95
|
"apply": "Anwenden",
|
|
96
96
|
"clear": "Löschen",
|
|
97
97
|
"close": "Schließen",
|
|
98
|
+
"iframe": {
|
|
99
|
+
"dialog_title": "Iframe hinzufügen/bearbeiten",
|
|
100
|
+
"src": "Quelle",
|
|
101
|
+
"name": "Name",
|
|
102
|
+
"title": "Titel",
|
|
103
|
+
"width": "Breite",
|
|
104
|
+
"height": "Höhe",
|
|
105
|
+
"frameborder": "Rahmenrand",
|
|
106
|
+
"src_invalid": "Die eingegebene URL ist ungültig"
|
|
107
|
+
},
|
|
98
108
|
"image": {
|
|
99
109
|
"title": "Bild hinzufügen/ändern",
|
|
100
110
|
"src": "Quelle",
|
|
@@ -127,8 +137,9 @@
|
|
|
127
137
|
}
|
|
128
138
|
},
|
|
129
139
|
"misc": {
|
|
130
|
-
"
|
|
140
|
+
"fullscreen": "Vollbildmodus",
|
|
131
141
|
"preview": "Vorschau",
|
|
142
|
+
"source": "Quellcode anzeigen",
|
|
132
143
|
"toggleInvisibleCharacters": "Unsichtbare Zeichen anzeigen/ausblenden"
|
|
133
144
|
},
|
|
134
145
|
"footer": {
|
package/src/i18n/locales/en.json
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"media": {
|
|
42
42
|
"link": "External link",
|
|
43
|
+
"iframe": "iframe",
|
|
43
44
|
"image": "Image",
|
|
44
45
|
"emoji": {
|
|
45
46
|
"title": "Emoji",
|
|
@@ -97,6 +98,16 @@
|
|
|
97
98
|
"apply": "Apply",
|
|
98
99
|
"clear": "Clear",
|
|
99
100
|
"close": "Close",
|
|
101
|
+
"iframe": {
|
|
102
|
+
"dialog_title": "Add/edit iframe",
|
|
103
|
+
"src": "Source",
|
|
104
|
+
"name": "Name",
|
|
105
|
+
"title": "Title",
|
|
106
|
+
"width": "Width",
|
|
107
|
+
"height": "Height",
|
|
108
|
+
"frameborder": "iframe border",
|
|
109
|
+
"src_invalid": "The input is invalid URL"
|
|
110
|
+
},
|
|
100
111
|
"image": {
|
|
101
112
|
"title": "Add/edit image",
|
|
102
113
|
"src": "Source",
|
|
@@ -129,8 +140,9 @@
|
|
|
129
140
|
}
|
|
130
141
|
},
|
|
131
142
|
"misc": {
|
|
132
|
-
"
|
|
143
|
+
"fullscreen": "Fullscreen mode",
|
|
133
144
|
"preview": "Preview",
|
|
145
|
+
"source": "View source code",
|
|
134
146
|
"toggleInvisibleCharacters": "Show/hide invisible characters"
|
|
135
147
|
},
|
|
136
148
|
"footer": {
|