tiptapify 0.0.29 → 0.0.31
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 +21896 -21223
- package/dist/tiptapify.umd.js +38 -38
- package/package.json +48 -48
- 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/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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tiptapify",
|
|
3
3
|
"types": "./index.d.ts",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.31",
|
|
5
5
|
"description": "Tiptap3 editor with Vuetify3 menu implementation",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -59,70 +59,70 @@
|
|
|
59
59
|
"repository": "https://github.com/IVoyt/tiptapify",
|
|
60
60
|
"packageManager": "pnpm@10.15.1",
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@tiptap/core": "^3.
|
|
63
|
-
"@tiptap/extension-blockquote": "^3.
|
|
64
|
-
"@tiptap/extension-bold": "^3.
|
|
65
|
-
"@tiptap/extension-bubble-menu": "^3.
|
|
66
|
-
"@tiptap/extension-code": "^3.
|
|
67
|
-
"@tiptap/extension-code-block": "^3.
|
|
68
|
-
"@tiptap/extension-code-block-lowlight": "^3.
|
|
69
|
-
"@tiptap/extension-color": "^3.
|
|
70
|
-
"@tiptap/extension-document": "^3.
|
|
71
|
-
"@tiptap/extension-floating-menu": "^3.
|
|
72
|
-
"@tiptap/extension-font-family": "^3.
|
|
73
|
-
"@tiptap/extension-hard-break": "^3.
|
|
74
|
-
"@tiptap/extension-heading": "^3.
|
|
75
|
-
"@tiptap/extension-highlight": "^3.
|
|
76
|
-
"@tiptap/extension-horizontal-rule": "^3.
|
|
77
|
-
"@tiptap/extension-image": "^3.
|
|
78
|
-
"@tiptap/extension-invisible-characters": "^3.
|
|
79
|
-
"@tiptap/extension-italic": "^3.
|
|
80
|
-
"@tiptap/extension-link": "^3.
|
|
81
|
-
"@tiptap/extension-list": "^3.
|
|
82
|
-
"@tiptap/extension-list-item": "^3.
|
|
83
|
-
"@tiptap/extension-paragraph": "^3.
|
|
84
|
-
"@tiptap/extension-placeholder": "^3.
|
|
85
|
-
"@tiptap/extension-strike": "^3.
|
|
86
|
-
"@tiptap/extension-subscript": "^3.
|
|
87
|
-
"@tiptap/extension-superscript": "^3.
|
|
88
|
-
"@tiptap/extension-table": "^3.
|
|
89
|
-
"@tiptap/extension-task-item": "^3.
|
|
90
|
-
"@tiptap/extension-task-list": "^3.
|
|
91
|
-
"@tiptap/extension-text": "^3.
|
|
92
|
-
"@tiptap/extension-text-align": "^3.
|
|
93
|
-
"@tiptap/extension-typography": "^3.
|
|
94
|
-
"@tiptap/extension-underline": "^3.
|
|
95
|
-
"@tiptap/extension-youtube": "^3.
|
|
96
|
-
"@tiptap/extensions": "^3.
|
|
97
|
-
"@tiptap/pm": "^3.
|
|
98
|
-
"@tiptap/suggestion": "^3.
|
|
99
|
-
"@tiptap/vue-3": "^3.
|
|
62
|
+
"@tiptap/core": "^3.20.0",
|
|
63
|
+
"@tiptap/extension-blockquote": "^3.20.0",
|
|
64
|
+
"@tiptap/extension-bold": "^3.20.0",
|
|
65
|
+
"@tiptap/extension-bubble-menu": "^3.20.0",
|
|
66
|
+
"@tiptap/extension-code": "^3.20.0",
|
|
67
|
+
"@tiptap/extension-code-block": "^3.20.0",
|
|
68
|
+
"@tiptap/extension-code-block-lowlight": "^3.20.0",
|
|
69
|
+
"@tiptap/extension-color": "^3.20.0",
|
|
70
|
+
"@tiptap/extension-document": "^3.20.0",
|
|
71
|
+
"@tiptap/extension-floating-menu": "^3.20.0",
|
|
72
|
+
"@tiptap/extension-font-family": "^3.20.0",
|
|
73
|
+
"@tiptap/extension-hard-break": "^3.20.0",
|
|
74
|
+
"@tiptap/extension-heading": "^3.20.0",
|
|
75
|
+
"@tiptap/extension-highlight": "^3.20.0",
|
|
76
|
+
"@tiptap/extension-horizontal-rule": "^3.20.0",
|
|
77
|
+
"@tiptap/extension-image": "^3.20.0",
|
|
78
|
+
"@tiptap/extension-invisible-characters": "^3.20.0",
|
|
79
|
+
"@tiptap/extension-italic": "^3.20.0",
|
|
80
|
+
"@tiptap/extension-link": "^3.20.0",
|
|
81
|
+
"@tiptap/extension-list": "^3.20.0",
|
|
82
|
+
"@tiptap/extension-list-item": "^3.20.0",
|
|
83
|
+
"@tiptap/extension-paragraph": "^3.20.0",
|
|
84
|
+
"@tiptap/extension-placeholder": "^3.20.0",
|
|
85
|
+
"@tiptap/extension-strike": "^3.20.0",
|
|
86
|
+
"@tiptap/extension-subscript": "^3.20.0",
|
|
87
|
+
"@tiptap/extension-superscript": "^3.20.0",
|
|
88
|
+
"@tiptap/extension-table": "^3.20.0",
|
|
89
|
+
"@tiptap/extension-task-item": "^3.20.0",
|
|
90
|
+
"@tiptap/extension-task-list": "^3.20.0",
|
|
91
|
+
"@tiptap/extension-text": "^3.20.0",
|
|
92
|
+
"@tiptap/extension-text-align": "^3.20.0",
|
|
93
|
+
"@tiptap/extension-typography": "^3.20.0",
|
|
94
|
+
"@tiptap/extension-underline": "^3.20.0",
|
|
95
|
+
"@tiptap/extension-youtube": "^3.20.0",
|
|
96
|
+
"@tiptap/extensions": "^3.20.0",
|
|
97
|
+
"@tiptap/pm": "^3.20.0",
|
|
98
|
+
"@tiptap/suggestion": "^3.20.0",
|
|
99
|
+
"@tiptap/vue-3": "^3.20.0",
|
|
100
100
|
"emoji.json": "^16.0.0",
|
|
101
101
|
"highlight.js": "^11.11.1",
|
|
102
102
|
"linkifyjs": "^4.3.2",
|
|
103
103
|
"lowlight": "^3.3.0",
|
|
104
|
-
"vue-i18n": "^11.
|
|
104
|
+
"vue-i18n": "^11.2.8"
|
|
105
105
|
},
|
|
106
106
|
"peerDependencies": {
|
|
107
107
|
"@mdi/js": "^7.4.47",
|
|
108
108
|
"vue": "^3.5.14",
|
|
109
|
-
"vuetify": "^3.8.5"
|
|
109
|
+
"vuetify": "^3.8.5 || ^4.0.0"
|
|
110
110
|
},
|
|
111
111
|
"devDependencies": {
|
|
112
112
|
"@intlify/unplugin-vue-i18n": "^6.0.8",
|
|
113
113
|
"@rollup/plugin-alias": "^5.1.1",
|
|
114
|
-
"@types/node": "^22.
|
|
114
|
+
"@types/node": "^22.19.13",
|
|
115
115
|
"@vitejs/plugin-vue": "^5.2.4",
|
|
116
116
|
"@vitejs/plugin-vue-jsx": "^4.2.0",
|
|
117
117
|
"rollup-plugin-tsconfig-paths": "^1.5.2",
|
|
118
|
-
"sass-embedded": "^1.
|
|
119
|
-
"tsx": "^4.
|
|
120
|
-
"typescript": "^5.9.
|
|
118
|
+
"sass-embedded": "^1.97.3",
|
|
119
|
+
"tsx": "^4.21.0",
|
|
120
|
+
"typescript": "^5.9.3",
|
|
121
121
|
"unplugin-vue-components": "^28.8.0",
|
|
122
|
-
"vite": "^6.
|
|
123
|
-
"vite-plugin-vuetify": "^2.1.
|
|
122
|
+
"vite": "^6.4.1",
|
|
123
|
+
"vite-plugin-vuetify": "^2.1.3",
|
|
124
124
|
"vite-svg-loader": "^5.1.0",
|
|
125
125
|
"vite-tsconfig-paths": "^5.1.4",
|
|
126
|
-
"vue-tsc": "^3.
|
|
126
|
+
"vue-tsc": "^3.2.5"
|
|
127
127
|
}
|
|
128
128
|
}
|
|
@@ -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,
|
|
@@ -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": {
|