tiptapify 0.0.9 → 0.0.11
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 +1 -1
- package/dist/tiptapify.css +1 -1
- package/dist/tiptapify.mjs +21133 -19765
- package/dist/tiptapify.umd.js +37 -38
- package/package.json +43 -43
- package/src/components/Tiptapify.vue +2 -2
- package/src/components/Toolbar/GroupBtn.vue +2 -6
- package/src/components/Toolbar/GroupDropdown.vue +4 -8
- package/src/components/Toolbar/Index.vue +5 -4
- package/src/components/Toolbar/Items.vue +2 -2
- package/src/components/Toolbar/Toggle.vue +2 -2
- package/src/components/Toolbar/items.ts +20 -20
- package/src/components/UI/Dialog.vue +141 -0
- package/src/components/editorExtensions.ts +1 -1
- package/src/{components/Toolbar/items/actions.ts → composables/Toolbar/useActionsItems.ts} +6 -3
- package/src/{components/Toolbar/items/alignment.ts → composables/Toolbar/useAlignmentItems.ts} +12 -9
- package/src/{components/Toolbar/items/formatExtra.ts → composables/Toolbar/useFormatExtraItems.ts} +10 -7
- package/src/{components/Toolbar/items/format.ts → composables/Toolbar/useFormatItems.ts} +20 -17
- package/src/{components/Toolbar/items/list.ts → composables/Toolbar/useListItems.ts} +14 -11
- package/src/{components/Toolbar/items/media.ts → composables/Toolbar/useMediaItems.ts} +22 -20
- package/src/{components/Toolbar/items/misc.ts → composables/Toolbar/useMiscItems.ts} +9 -6
- package/src/composables/Toolbar/useStyleItems.ts +231 -0
- package/src/{components/Toolbar/fonts.ts → constants/style.ts} +21 -0
- package/src/extensions/components/FontFamily.vue +82 -0
- package/src/extensions/components/FontSize.vue +83 -0
- package/src/extensions/components/ImageDialog.vue +17 -29
- package/src/extensions/components/LineHeight.vue +82 -0
- package/src/extensions/components/LinkDialog.vue +75 -44
- package/src/extensions/components/PreviewDialog.vue +8 -16
- package/src/extensions/components/ShowSourceDialog.vue +17 -18
- package/src/extensions/components/StyleColor.vue +68 -15
- package/src/extensions/components/TableBuilder.vue +3 -6
- package/src/extensions/link.ts +8 -0
- package/src/extensions/slash-commands.ts +1 -1
- package/src/i18n/index.ts +0 -1
- package/src/i18n/locales/ch.json +119 -0
- package/src/i18n/locales/cz.json +119 -0
- package/src/i18n/locales/de.json +83 -76
- package/src/i18n/locales/en.json +82 -75
- package/src/i18n/locales/es.json +80 -73
- package/src/i18n/locales/fr.json +81 -73
- package/src/i18n/locales/it.json +80 -73
- package/src/i18n/locales/la.json +119 -0
- package/src/i18n/locales/lt.json +119 -0
- package/src/i18n/locales/nl.json +119 -0
- package/src/i18n/locales/pl.json +80 -74
- package/src/i18n/locales/pt.json +119 -0
- package/src/i18n/locales/ru.json +78 -71
- package/src/i18n/locales/se.json +119 -0
- package/src/i18n/locales/ua.json +79 -72
- package/src/components/Toolbar/items/style.ts +0 -187
- /package/src/{components → extensions/components}/CodeBlockComponent.vue +0 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
|
|
3
|
+
import { Editor } from "@tiptap/vue-3";
|
|
4
|
+
|
|
5
|
+
import { computed, inject, Ref, ref } from 'vue'
|
|
6
|
+
import { useI18n } from "vue-i18n";
|
|
7
|
+
|
|
8
|
+
const props = defineProps({
|
|
9
|
+
sizes: { type: Array<number>, default: [] },
|
|
10
|
+
measure: { type: String, default: 'px' },
|
|
11
|
+
fontSize: { type: Number, default () { return null } },
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const { t } = useI18n();
|
|
15
|
+
|
|
16
|
+
const emit = defineEmits(['close'])
|
|
17
|
+
|
|
18
|
+
const editor = inject('tiptapifyEditor') as Ref<Editor>
|
|
19
|
+
|
|
20
|
+
const initialFontSize = ref(computed(() => props.fontSize).value)
|
|
21
|
+
|
|
22
|
+
const fontSizeSelected = ref<boolean>(false)
|
|
23
|
+
|
|
24
|
+
function hoverFontSize(fontSize: number) {
|
|
25
|
+
fontSizeSelected.value = false
|
|
26
|
+
|
|
27
|
+
editor.value.chain().focus().setFontSize(`${fontSize}${props.measure}`).run()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function resetFontSize() {
|
|
31
|
+
if (fontSizeSelected.value) {
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
initialFontSize.value
|
|
36
|
+
? editor.value.chain().focus().setFontSize(`${initialFontSize.value}${props.measure}`).run()
|
|
37
|
+
: editor.value.chain().focus().unsetFontSize().run()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function clearFontSize() {
|
|
41
|
+
editor.value.chain().focus().unsetFontSize().run()
|
|
42
|
+
emit('close')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function setFontSize() {
|
|
46
|
+
fontSizeSelected.value = true
|
|
47
|
+
|
|
48
|
+
emit('close')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isFontSizeActive(fontSize: number): boolean {
|
|
52
|
+
return editor.value.isActive('textStyle', { fontSize: `${fontSize}${props.measure}` }) || fontSize === initialFontSize.value
|
|
53
|
+
}
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<template>
|
|
57
|
+
<VList class="tiptapify-font-size-list">
|
|
58
|
+
<VListItem :disabled="fontSize === null" density="compact" @click="clearFontSize">
|
|
59
|
+
<VListItemTitle class="font-italic text-grey-darken-1">
|
|
60
|
+
{{ t('defaultValue') }}
|
|
61
|
+
</VListItemTitle>
|
|
62
|
+
</VListItem>
|
|
63
|
+
<VListItem
|
|
64
|
+
v-for="size in sizes"
|
|
65
|
+
:active="isFontSizeActive(size)"
|
|
66
|
+
:color="size === initialFontSize ? 'primary' : ''"
|
|
67
|
+
density="compact"
|
|
68
|
+
@click="setFontSize"
|
|
69
|
+
@mouseover="hoverFontSize(size)"
|
|
70
|
+
@mouseleave="resetFontSize"
|
|
71
|
+
>
|
|
72
|
+
<VListItemTitle>
|
|
73
|
+
{{ size }}{{ measure}}
|
|
74
|
+
</VListItemTitle>
|
|
75
|
+
</VListItem>
|
|
76
|
+
</VList>
|
|
77
|
+
</template>
|
|
78
|
+
|
|
79
|
+
<style lang="scss" scoped>
|
|
80
|
+
.tiptapify-font-size-list {
|
|
81
|
+
max-height: 390px;
|
|
82
|
+
}
|
|
83
|
+
</style>
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
|
|
3
|
-
import * as mdi from '@mdi/js'
|
|
4
3
|
import { Editor } from "@tiptap/vue-3";
|
|
4
|
+
import Dialog from "@tiptapify/components/UI/Dialog.vue";
|
|
5
5
|
|
|
6
6
|
import { useI18n } from 'vue-i18n'
|
|
7
7
|
import { computed, inject, onMounted, onUnmounted, Ref, ref } from 'vue'
|
|
8
8
|
|
|
9
|
-
import helpers from '@tiptapify/utils/helpers'
|
|
10
|
-
|
|
11
9
|
defineProps({
|
|
12
10
|
variantBtn: { type: String, default() { return 'elevated' }},
|
|
13
11
|
variantField: { type: String, default() { return 'solo' }}
|
|
14
12
|
})
|
|
15
13
|
|
|
16
|
-
const { ucFirst } = helpers
|
|
17
|
-
|
|
18
14
|
const editor = inject('tiptapifyEditor') as Ref<Editor>
|
|
19
15
|
const { t } = useI18n()
|
|
20
16
|
|
|
@@ -27,7 +23,7 @@ const generateImageAttrs = () => ({
|
|
|
27
23
|
|
|
28
24
|
const attrs = ref(generateImageAttrs())
|
|
29
25
|
|
|
30
|
-
const dialog = ref
|
|
26
|
+
const dialog = ref(null)
|
|
31
27
|
|
|
32
28
|
const isDisabled = computed(() => {
|
|
33
29
|
const { src } = attrs.value
|
|
@@ -64,7 +60,7 @@ function clear() {
|
|
|
64
60
|
}
|
|
65
61
|
|
|
66
62
|
function close() {
|
|
67
|
-
dialog.value
|
|
63
|
+
dialog.value.close()
|
|
68
64
|
|
|
69
65
|
attrs.value = generateImageAttrs()
|
|
70
66
|
}
|
|
@@ -75,7 +71,7 @@ const showTiptapifyImage = (event: CustomEvent) => {
|
|
|
75
71
|
attrs.value.width = event.detail.image?.width
|
|
76
72
|
attrs.value.height = event.detail.image?.height
|
|
77
73
|
|
|
78
|
-
dialog.value
|
|
74
|
+
dialog.value.open()
|
|
79
75
|
}
|
|
80
76
|
|
|
81
77
|
onMounted(() => {
|
|
@@ -88,55 +84,47 @@ onUnmounted(() => {
|
|
|
88
84
|
</script>
|
|
89
85
|
|
|
90
86
|
<template>
|
|
91
|
-
<
|
|
92
|
-
<
|
|
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
|
-
|
|
87
|
+
<Dialog ref="dialog" module="image" :max-width="800">
|
|
88
|
+
<template #content>
|
|
103
89
|
<VCardText>
|
|
104
90
|
<VRow>
|
|
105
91
|
<VCol cols="12">
|
|
106
|
-
<VTextField v-model="attrs.src"
|
|
92
|
+
<VTextField v-model="attrs.src" density="compact" variant="outlined" :label="t('dialog.image.src')" />
|
|
107
93
|
</VCol>
|
|
108
94
|
|
|
109
95
|
<VCol cols="12" md="6">
|
|
110
|
-
<VTextField v-model="attrs.alt"
|
|
96
|
+
<VTextField v-model="attrs.alt" density="compact" variant="outlined" :label="t('dialog.image.alt')" />
|
|
111
97
|
</VCol>
|
|
112
98
|
|
|
113
99
|
<VCol cols="12" md="3">
|
|
114
|
-
<VTextField v-model="attrs.width" type="number"
|
|
100
|
+
<VTextField v-model="attrs.width" type="number" density="compact" variant="outlined" :precision="0" :min="1" :label="t('dialog.image.width')" />
|
|
115
101
|
</VCol>
|
|
116
102
|
|
|
117
103
|
<VCol cols="12" md="3">
|
|
118
|
-
<VTextField v-model="attrs.height" type="number"
|
|
104
|
+
<VTextField v-model="attrs.height" type="number" density="compact" variant="outlined" :precision="0" :min="1" :label="t('dialog.image.height')" />
|
|
119
105
|
</VCol>
|
|
120
106
|
</VRow>
|
|
121
107
|
</VCardText>
|
|
108
|
+
</template>
|
|
122
109
|
|
|
110
|
+
<template #actions>
|
|
123
111
|
<VCardActions>
|
|
124
112
|
<VRow>
|
|
125
113
|
<VCol class="d-flex justify-start">
|
|
126
114
|
<VBtn color="warning" v-if="editor.isActive('image')" :variant="variantBtn" :disabled="isDisabled" @click="clear">
|
|
127
|
-
{{
|
|
115
|
+
{{ t('dialog.clear') }}
|
|
128
116
|
</VBtn>
|
|
129
117
|
</VCol>
|
|
130
118
|
<VCol class="d-flex justify-end">
|
|
131
119
|
<VBtn :variant="variantBtn" @click="close" class="mr-2">
|
|
132
|
-
{{
|
|
120
|
+
{{ t('dialog.close') }}
|
|
133
121
|
</VBtn>
|
|
134
122
|
<VBtn color="primary" :variant="variantBtn" :disabled="isDisabled" @click="apply">
|
|
135
|
-
{{
|
|
123
|
+
{{ t('dialog.apply') }}
|
|
136
124
|
</VBtn>
|
|
137
125
|
</VCol>
|
|
138
126
|
</VRow>
|
|
139
127
|
</VCardActions>
|
|
140
|
-
</
|
|
141
|
-
</
|
|
128
|
+
</template>
|
|
129
|
+
</Dialog>
|
|
142
130
|
</template>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
|
|
3
|
+
import { Editor } from "@tiptap/vue-3";
|
|
4
|
+
|
|
5
|
+
import { computed, inject, Ref, ref } from 'vue'
|
|
6
|
+
import { useI18n } from "vue-i18n";
|
|
7
|
+
|
|
8
|
+
const props = defineProps({
|
|
9
|
+
lineHeights: { type: Array<number>, default () { return [] } },
|
|
10
|
+
lineHeight: { type: Number, default () { return null } },
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const { t } = useI18n();
|
|
14
|
+
|
|
15
|
+
const emit = defineEmits(['close'])
|
|
16
|
+
|
|
17
|
+
const editor = inject('tiptapifyEditor') as Ref<Editor>
|
|
18
|
+
|
|
19
|
+
const initialLineHeight = ref(computed(() => props.lineHeight).value)
|
|
20
|
+
|
|
21
|
+
const lineHeightSelected = ref<boolean>(false)
|
|
22
|
+
|
|
23
|
+
function hoverLineHeight(lineHeight: number) {
|
|
24
|
+
lineHeightSelected.value = false
|
|
25
|
+
|
|
26
|
+
editor.value.chain().focus().setLineHeight(lineHeight.toString()).run()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function resetLineHeight() {
|
|
30
|
+
if (lineHeightSelected.value) {
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
initialLineHeight.value
|
|
35
|
+
? editor.value.chain().focus().setLineHeight(initialLineHeight.value.toString()).run()
|
|
36
|
+
: editor.value.chain().focus().unsetLineHeight().run()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function clearLineHeight() {
|
|
40
|
+
editor.value.chain().focus().unsetLineHeight().run()
|
|
41
|
+
emit('close')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function setLineHeight() {
|
|
45
|
+
lineHeightSelected.value = true
|
|
46
|
+
|
|
47
|
+
emit('close')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isLineHeightActive(lineHeight: number): boolean {
|
|
51
|
+
return editor.value.isActive('textStyle', { lineHeight }) || lineHeight === initialLineHeight.value
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<template>
|
|
56
|
+
<VList class="tiptapify-line-height-list">
|
|
57
|
+
<VListItem :disabled="lineHeight === null" density="compact" @click="clearLineHeight">
|
|
58
|
+
<VListItemTitle class="font-italic text-grey-darken-1">
|
|
59
|
+
{{ t('defaultValue') }}
|
|
60
|
+
</VListItemTitle>
|
|
61
|
+
</VListItem>
|
|
62
|
+
<VListItem
|
|
63
|
+
v-for="lineHeight in lineHeights"
|
|
64
|
+
:active="isLineHeightActive(lineHeight)"
|
|
65
|
+
:color="lineHeight === initialLineHeight ? 'primary' : ''"
|
|
66
|
+
density="compact"
|
|
67
|
+
@click="setLineHeight"
|
|
68
|
+
@mouseover="hoverLineHeight(lineHeight)"
|
|
69
|
+
@mouseleave="resetLineHeight"
|
|
70
|
+
>
|
|
71
|
+
<VListItemTitle>
|
|
72
|
+
{{ lineHeight }}
|
|
73
|
+
</VListItemTitle>
|
|
74
|
+
</VListItem>
|
|
75
|
+
</VList>
|
|
76
|
+
</template>
|
|
77
|
+
|
|
78
|
+
<style lang="scss" scoped>
|
|
79
|
+
.tiptapify-line-height-list {
|
|
80
|
+
max-height: 390px;
|
|
81
|
+
}
|
|
82
|
+
</style>
|
|
@@ -1,35 +1,38 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
|
|
3
|
-
import * as mdi from '@mdi/js'
|
|
4
3
|
import { Editor } from "@tiptap/vue-3";
|
|
5
4
|
|
|
6
5
|
import { useI18n } from 'vue-i18n'
|
|
7
|
-
import { computed, inject, onMounted, onUnmounted, Ref, ref } from 'vue'
|
|
6
|
+
import { computed, inject, onMounted, onUnmounted, Ref, ref, watch } from 'vue'
|
|
8
7
|
|
|
9
|
-
import
|
|
8
|
+
import Dialog from "@tiptapify/components/UI/Dialog.vue"
|
|
10
9
|
|
|
11
10
|
defineProps({
|
|
12
11
|
variantBtn: { type: String, default() { return 'elevated' }},
|
|
13
|
-
variantField: { type: String, default() { return '
|
|
12
|
+
variantField: { type: String, default() { return 'outlined' }}
|
|
14
13
|
})
|
|
15
14
|
|
|
16
|
-
const { ucFirst } = helpers
|
|
17
|
-
|
|
18
15
|
const editor = inject('tiptapifyEditor') as Ref<Editor>
|
|
19
16
|
const { t } = useI18n()
|
|
20
17
|
|
|
21
18
|
const generateLinkAttrs = () => ({
|
|
22
19
|
href: '',
|
|
23
|
-
target:
|
|
20
|
+
target: targetAttrs.value[0],
|
|
24
21
|
cssClass: '',
|
|
25
22
|
rel: ''
|
|
26
23
|
})
|
|
27
24
|
|
|
28
|
-
const relAttrs = ['alternate', 'author', 'bookmark', 'external', 'help', 'license', 'next', 'nofollow', 'noreferrer', '
|
|
25
|
+
const relAttrs = ['alternate', 'author', 'bookmark', 'external', 'help', 'license', 'me', 'next', 'nofollow', 'noopener', 'noreferrer', 'opener', 'prev', 'privacy-policy', 'search', 'tag', 'terms-of-service']
|
|
26
|
+
|
|
27
|
+
const targetAttrs = computed(() => [
|
|
28
|
+
{ value: '_blank', title: t('dialog.link.target_blank') },
|
|
29
|
+
{ value: '_self', title: t('dialog.link.target_self') }
|
|
30
|
+
])
|
|
29
31
|
|
|
30
32
|
const attrs = ref(generateLinkAttrs())
|
|
33
|
+
const hrefInvalid = ref(false)
|
|
31
34
|
|
|
32
|
-
const dialog = ref
|
|
35
|
+
const dialog = ref(null)
|
|
33
36
|
|
|
34
37
|
const isDisabled = computed(() => {
|
|
35
38
|
const { href } = attrs.value
|
|
@@ -38,11 +41,10 @@ const isDisabled = computed(() => {
|
|
|
38
41
|
|
|
39
42
|
function apply() {
|
|
40
43
|
let { href, target, rel, cssClass } = attrs.value
|
|
41
|
-
|
|
42
|
-
rel = rel.join(' ')
|
|
44
|
+
rel = rel?.length ? rel.join(' ') : null
|
|
43
45
|
|
|
44
46
|
if (href) {
|
|
45
|
-
editor.value.chain().focus().extendMarkRange('link').setLink({ href, target, rel, class: cssClass }).run()
|
|
47
|
+
editor.value.chain().focus().extendMarkRange('link').setLink({ href, target: target.value, rel, class: cssClass }).run()
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
close()
|
|
@@ -55,18 +57,18 @@ function clear() {
|
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
function close() {
|
|
58
|
-
dialog.value = false
|
|
59
|
-
|
|
60
60
|
attrs.value = generateLinkAttrs()
|
|
61
|
+
|
|
62
|
+
dialog.value.close()
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
const showLink = (event: CustomEvent) => {
|
|
64
|
-
attrs.value.href = event.detail.link?.href
|
|
65
|
-
attrs.value.target = event.detail.link?.target
|
|
66
|
+
attrs.value.href = event.detail.link?.href ?? ''
|
|
67
|
+
attrs.value.target = targetAttrs.value.find(item => item.value === event.detail.link?.target) ?? targetAttrs[0]
|
|
66
68
|
attrs.value.rel = event.detail.link?.rel?.split(' ')
|
|
67
69
|
attrs.value.cssClass = event.detail.link?.class
|
|
68
70
|
|
|
69
|
-
dialog.value
|
|
71
|
+
dialog.value.open()
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
onMounted(() => {
|
|
@@ -76,67 +78,96 @@ onMounted(() => {
|
|
|
76
78
|
onUnmounted(() => {
|
|
77
79
|
window.removeEventListener('tiptapify-show-link', showLink as EventListener)
|
|
78
80
|
})
|
|
79
|
-
</script>
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<VCard>
|
|
84
|
-
<VToolbar class="px-6" density="compact">
|
|
85
|
-
<span class="headline">{{ ucFirst(t('dialog.link.title')) }}</span>
|
|
82
|
+
watch(() => attrs.value.href, () => {
|
|
83
|
+
const azAZ09 = 'a-zA-Z0-9'
|
|
86
84
|
|
|
87
|
-
|
|
85
|
+
const regexHrefProto = 'https?:\\/\\/'
|
|
86
|
+
const regexHrefAuth = `([${azAZ09}]+(:[${azAZ09}]+)?@)?`
|
|
87
|
+
const regexHrefDomain = `[${azAZ09}]+(\\.[${azAZ09}]+(-?[${azAZ09}]+)?)+`
|
|
88
|
+
const regexHrefPath = `(\\/[${azAZ09}\\-]+)*`
|
|
89
|
+
const regexHrefFragment = '(#[^\\s]*)?'
|
|
90
|
+
const regexHrefQueryParam = `(\\?[${azAZ09}\\-_]+((\\[[${azAZ09}]+\\])?=[${azAZ09}\\-_%]+)?)?`
|
|
91
|
+
const regexHrefQueryParamExtra = `(&[${azAZ09}\\-_]+((\\[[${azAZ09}]+\\])?=[${azAZ09}\\-_%]+)?)*`
|
|
92
|
+
const regexHref = `${regexHrefProto}${regexHrefAuth}${regexHrefDomain}${regexHrefPath}${regexHrefFragment}${regexHrefQueryParam}${regexHrefQueryParamExtra}`
|
|
88
93
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
const regexMailto = `mailto:\\w+@[${azAZ09}]+(\\.[${azAZ09}]+)*`
|
|
95
|
+
const regexTel = 'tel:\\+?[0-9]+'
|
|
96
|
+
|
|
97
|
+
const regexAll = [regexHref, regexMailto, regexTel].join('|')
|
|
98
|
+
|
|
99
|
+
const regex = new RegExp(`^(${regexAll})$`, 'i')
|
|
100
|
+
|
|
101
|
+
hrefInvalid.value = attrs.value.href !== '' && !regex.test(attrs.value.href)
|
|
102
|
+
})
|
|
103
|
+
</script>
|
|
93
104
|
|
|
105
|
+
<template>
|
|
106
|
+
<Dialog ref="dialog" module="link">
|
|
107
|
+
<template #content>
|
|
94
108
|
<VCardText>
|
|
95
109
|
<VRow>
|
|
96
|
-
<VCol cols="12"
|
|
97
|
-
<VTextField
|
|
110
|
+
<VCol cols="12">
|
|
111
|
+
<VTextField
|
|
112
|
+
v-model="attrs.href"
|
|
113
|
+
density="compact"
|
|
114
|
+
variant="outlined"
|
|
115
|
+
:label="t('dialog.link.href')"
|
|
116
|
+
:error-messages="hrefInvalid ? t('dialog.link.href_error') : ''"
|
|
117
|
+
autofocus
|
|
118
|
+
/>
|
|
119
|
+
</VCol>
|
|
120
|
+
|
|
121
|
+
<VCol cols="12" md="4">
|
|
122
|
+
<VSelect
|
|
123
|
+
v-model="attrs.target"
|
|
124
|
+
:items="targetAttrs"
|
|
125
|
+
:label="t('dialog.link.target')"
|
|
126
|
+
variant="outlined"
|
|
127
|
+
return-object
|
|
128
|
+
density="compact"
|
|
129
|
+
/>
|
|
98
130
|
</VCol>
|
|
99
131
|
|
|
100
|
-
<VCol cols="12" md="
|
|
101
|
-
<
|
|
132
|
+
<VCol cols="12" md="8">
|
|
133
|
+
<VTextField v-model="attrs.cssClass" density="compact" variant="outlined" :label="t('dialog.link.class')" />
|
|
102
134
|
</VCol>
|
|
103
135
|
|
|
104
136
|
<VCol cols="12">
|
|
105
137
|
<VSelect
|
|
106
138
|
v-model="attrs.rel"
|
|
107
139
|
:items="relAttrs"
|
|
108
|
-
:label="
|
|
109
|
-
|
|
140
|
+
:label="t('dialog.link.rel')"
|
|
141
|
+
variant="outlined"
|
|
110
142
|
multiple
|
|
111
143
|
chips
|
|
112
144
|
closable-chips
|
|
113
145
|
clearable
|
|
146
|
+
density="compact"
|
|
114
147
|
/>
|
|
115
148
|
</VCol>
|
|
116
|
-
|
|
117
|
-
<VCol cols="12">
|
|
118
|
-
<VTextField v-model="attrs.cssClass" :variant="variantField" :label="ucFirst(t('dialog.link.class'))" />
|
|
119
|
-
</VCol>
|
|
120
149
|
</VRow>
|
|
121
150
|
</VCardText>
|
|
151
|
+
</template>
|
|
122
152
|
|
|
153
|
+
<template #actions>
|
|
123
154
|
<VCardActions>
|
|
124
155
|
<VRow>
|
|
125
156
|
<VCol class="d-flex justify-start">
|
|
126
157
|
<VBtn color="warning" v-if="editor.isActive('link')" :variant="variantBtn" :disabled="isDisabled" @click="clear">
|
|
127
|
-
{{
|
|
158
|
+
{{ t('dialog.clear') }}
|
|
128
159
|
</VBtn>
|
|
129
160
|
</VCol>
|
|
130
161
|
<VCol class="d-flex justify-end">
|
|
131
162
|
<VBtn :variant="variantBtn" @click="close" class="mr-2">
|
|
132
|
-
{{
|
|
163
|
+
{{ t('dialog.close') }}
|
|
133
164
|
</VBtn>
|
|
134
|
-
<VBtn color="primary" :variant="variantBtn" :disabled="isDisabled" @click="apply">
|
|
135
|
-
{{
|
|
165
|
+
<VBtn color="primary" :variant="variantBtn" :disabled="isDisabled || hrefInvalid" @click="apply">
|
|
166
|
+
{{ t('dialog.apply') }}
|
|
136
167
|
</VBtn>
|
|
137
168
|
</VCol>
|
|
138
169
|
</VRow>
|
|
139
170
|
</VCardActions>
|
|
140
|
-
</
|
|
141
|
-
</
|
|
171
|
+
</template>
|
|
172
|
+
</Dialog>
|
|
142
173
|
</template>
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import Dialog from "@tiptapify/components/UI/Dialog.vue";
|
|
2
3
|
import { ref, onMounted, onUnmounted } from 'vue'
|
|
3
|
-
import { useI18n } from "vue-i18n";
|
|
4
|
-
import * as mdi from '@mdi/js'
|
|
5
|
-
|
|
6
|
-
const { t } = useI18n();
|
|
7
4
|
|
|
8
5
|
const content = ref()
|
|
9
6
|
|
|
10
|
-
const dialog = ref(
|
|
7
|
+
const dialog = ref(null)
|
|
11
8
|
|
|
12
9
|
const showDialog = (event: CustomEvent) => {
|
|
13
10
|
content.value = event.detail.html
|
|
14
|
-
|
|
11
|
+
|
|
12
|
+
dialog.value.open()
|
|
15
13
|
}
|
|
16
14
|
|
|
17
15
|
onMounted(() => {
|
|
@@ -24,19 +22,13 @@ onUnmounted(() => {
|
|
|
24
22
|
</script>
|
|
25
23
|
|
|
26
24
|
<template>
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
<VToolbar>
|
|
30
|
-
<VBtn :icon="mdi.mdiClose" @click="dialog = false" />
|
|
31
|
-
|
|
32
|
-
<VToolbarTitle>Preview</VToolbarTitle>
|
|
33
|
-
</VToolbar>
|
|
34
|
-
|
|
25
|
+
<Dialog ref="dialog" module="preview" fullscreen>
|
|
26
|
+
<template #content>
|
|
35
27
|
<VCardItem>
|
|
36
28
|
<div class="tiptap" v-html="content"></div>
|
|
37
29
|
</VCardItem>
|
|
38
|
-
</
|
|
39
|
-
</
|
|
30
|
+
</template>
|
|
31
|
+
</Dialog>
|
|
40
32
|
</template>
|
|
41
33
|
|
|
42
34
|
<style lang="scss">
|
|
@@ -1,30 +1,27 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { Editor } from "@tiptap/vue-3";
|
|
3
|
+
import Dialog from "@tiptapify/components/UI/Dialog.vue";
|
|
3
4
|
import { ref, onMounted, onUnmounted, watch, inject, Ref } from 'vue'
|
|
4
5
|
import { useI18n } from "vue-i18n";
|
|
5
6
|
|
|
6
|
-
import helpers from "@tiptapify/utils/helpers";
|
|
7
|
-
|
|
8
7
|
const props = defineProps({
|
|
9
8
|
indent: { type: Number, default: 2 },
|
|
10
9
|
variantBtn: { type: String, default: 'elevated' },
|
|
11
10
|
variantField: { type: String, default: 'solo' }
|
|
12
11
|
})
|
|
13
12
|
|
|
14
|
-
const { ucFirst } = helpers;
|
|
15
|
-
|
|
16
13
|
const { t } = useI18n();
|
|
17
14
|
|
|
18
15
|
const editor = inject('tiptapifyEditor') as Ref<Editor>
|
|
19
16
|
|
|
20
|
-
const dialog = ref(
|
|
17
|
+
const dialog = ref(null)
|
|
21
18
|
const formatted = ref(false)
|
|
22
19
|
const sourceCode = ref('')
|
|
23
20
|
|
|
24
21
|
const formatHtml = (html: string): string => {
|
|
22
|
+
const singleTags = ['img', 'hr', 'br', 'input']
|
|
25
23
|
let formatted = html.replace(/>/g, '>\n');
|
|
26
24
|
|
|
27
|
-
formatted = formatted.replace(/\n</g, '\n<');
|
|
28
25
|
formatted = formatted.replace(/([^>\n])</g, '$1\n<');
|
|
29
26
|
|
|
30
27
|
const lines = formatted.split('\n');
|
|
@@ -38,7 +35,8 @@ const formatHtml = (html: string): string => {
|
|
|
38
35
|
|
|
39
36
|
const indentedLine = ' '.repeat(indentLevel * props.indent) + line;
|
|
40
37
|
|
|
41
|
-
|
|
38
|
+
const tag = line.match(/<\/?(\S+).*>/) ?? []
|
|
39
|
+
if (!singleTags.includes(tag[1] ?? '') && line.match(/<[^\/][^>]*>/) && !line.match(/<.*\/>/)) {
|
|
42
40
|
indentLevel++;
|
|
43
41
|
}
|
|
44
42
|
|
|
@@ -57,11 +55,12 @@ const unformatHtml = (html: string): string => {
|
|
|
57
55
|
|
|
58
56
|
const showDialog = (event: CustomEvent) => {
|
|
59
57
|
sourceCode.value = event.detail.html
|
|
60
|
-
|
|
58
|
+
|
|
59
|
+
dialog.value.open()
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
const saveChanges = () => {
|
|
64
|
-
dialog.value
|
|
63
|
+
dialog.value.close()
|
|
65
64
|
|
|
66
65
|
editor.value.commands.setContent(sourceCode.value, true)
|
|
67
66
|
}
|
|
@@ -80,16 +79,14 @@ watch(() => formatted.value, () => {
|
|
|
80
79
|
</script>
|
|
81
80
|
|
|
82
81
|
<template>
|
|
83
|
-
<
|
|
84
|
-
<
|
|
85
|
-
<VCardTitle>{{ ucFirst(t('dialog.source.title')) }}</VCardTitle>
|
|
86
|
-
|
|
82
|
+
<Dialog ref="dialog" module="source" :max-width="1500">
|
|
83
|
+
<template #content>
|
|
87
84
|
<VCardText>
|
|
88
85
|
<VContainer fluid class="pt-0 pl-0 pr-0">
|
|
89
86
|
<VRow>
|
|
90
87
|
<VCol>
|
|
91
88
|
<VBtn v-model="formatted" :color="`${formatted ? 'primary' : ''}`" @click="formatted = !formatted">
|
|
92
|
-
{{
|
|
89
|
+
{{ t('dialog.source.prettify') }}
|
|
93
90
|
</VBtn>
|
|
94
91
|
</VCol>
|
|
95
92
|
</VRow>
|
|
@@ -103,18 +100,20 @@ watch(() => formatted.value, () => {
|
|
|
103
100
|
class="source-code-area"
|
|
104
101
|
/>
|
|
105
102
|
</VCardText>
|
|
103
|
+
</template>
|
|
106
104
|
|
|
105
|
+
<template #actions>
|
|
107
106
|
<VCardActions>
|
|
108
107
|
<VSpacer></VSpacer>
|
|
109
108
|
<VBtn :variant="variantBtn" @click="dialog = false">
|
|
110
|
-
{{
|
|
109
|
+
{{ t('dialog.close') }}
|
|
111
110
|
</VBtn>
|
|
112
111
|
<VBtn :variant="variantBtn" color="primary" @click="saveChanges">
|
|
113
|
-
{{
|
|
112
|
+
{{ t('dialog.apply') }}
|
|
114
113
|
</VBtn>
|
|
115
114
|
</VCardActions>
|
|
116
|
-
</
|
|
117
|
-
</
|
|
115
|
+
</template>
|
|
116
|
+
</Dialog>
|
|
118
117
|
</template>
|
|
119
118
|
|
|
120
119
|
<style scoped lang="scss">
|