tiptapify 0.0.8 → 0.0.10
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 +25 -2
- package/dist/tiptapify.css +1 -1
- package/dist/tiptapify.mjs +26215 -22992
- package/dist/tiptapify.umd.js +39 -39
- package/index.d.ts +49 -0
- package/package.json +10 -8
- package/src/components/Footer.vue +48 -6
- package/src/components/Tiptapify.vue +37 -29
- package/src/components/Toolbar/Group.vue +13 -14
- package/src/components/Toolbar/GroupBtn.vue +30 -0
- package/src/components/Toolbar/GroupDropdown.vue +4 -8
- package/src/components/Toolbar/Index.vue +26 -77
- package/src/components/Toolbar/Items.vue +77 -0
- package/src/components/Toolbar/defaultExtensionComponents.ts +32 -0
- package/src/components/Toolbar/items/format.ts +7 -20
- package/src/components/Toolbar/items/formatExtra.ts +1 -1
- package/src/components/Toolbar/items/media.ts +1 -1
- package/src/components/Toolbar/items/misc.ts +1 -1
- package/src/components/Toolbar/items/style.ts +60 -2
- package/src/components/Toolbar/items.ts +6 -4
- package/src/components/UI/Dialog.vue +141 -0
- package/src/components/editorExtensions.ts +3 -7
- package/src/extensions/components/ImageDialog.vue +17 -29
- package/src/extensions/components/LinkDialog.vue +58 -43
- package/src/extensions/components/PreviewDialog.vue +8 -16
- package/src/extensions/components/ShowSourceDialog.vue +14 -16
- package/src/extensions/components/StyleColor.vue +230 -0
- package/src/extensions/components/TableBuilder.vue +4 -8
- package/src/i18n/locales/ch.json +118 -0
- package/src/i18n/locales/cz.json +118 -0
- package/src/i18n/locales/de.json +90 -72
- package/src/i18n/locales/en.json +89 -71
- package/src/i18n/locales/es.json +90 -72
- package/src/i18n/locales/fr.json +91 -72
- package/src/i18n/locales/it.json +90 -72
- package/src/i18n/locales/la.json +118 -0
- package/src/i18n/locales/lt.json +118 -0
- package/src/i18n/locales/nl.json +118 -0
- package/src/i18n/locales/pl.json +90 -72
- package/src/i18n/locales/pt.json +118 -0
- package/src/i18n/locales/ru.json +85 -67
- package/src/i18n/locales/se.json +118 -0
- package/src/i18n/locales/ua.json +86 -68
- package/src/types/overridable-extensions.ts +6 -0
|
@@ -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
66
|
attrs.value.href = event.detail.link?.href
|
|
65
|
-
attrs.value.target = event.detail.link?.target
|
|
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,80 @@ 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>
|
|
86
|
-
|
|
87
|
-
<VSpacer />
|
|
82
|
+
watch(() => attrs.value.href, () => {
|
|
83
|
+
const regex = new RegExp(/^((https?|ftps?|sftp):\/\/[a-z0-9]+(\.[a-z0-9]+)+|mailto:\w+@[a-z]+(\.[a-z]+)*|tel:\+?\d+)$/, 'igu')
|
|
88
84
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
</VToolbar>
|
|
85
|
+
hrefInvalid.value = !regex.test(attrs.value.href)
|
|
86
|
+
})
|
|
87
|
+
</script>
|
|
93
88
|
|
|
89
|
+
<template>
|
|
90
|
+
<Dialog ref="dialog" module="link">
|
|
91
|
+
<template #content>
|
|
94
92
|
<VCardText>
|
|
95
93
|
<VRow>
|
|
96
|
-
<VCol cols="12"
|
|
97
|
-
<VTextField
|
|
94
|
+
<VCol cols="12">
|
|
95
|
+
<VTextField
|
|
96
|
+
v-model="attrs.href"
|
|
97
|
+
density="compact"
|
|
98
|
+
variant="outlined"
|
|
99
|
+
:label="t('dialog.link.href')"
|
|
100
|
+
:error-messages="hrefInvalid ? t('dialog.link.href_error') : ''"
|
|
101
|
+
autofocus
|
|
102
|
+
/>
|
|
98
103
|
</VCol>
|
|
99
104
|
|
|
100
|
-
<VCol cols="12" md="
|
|
101
|
-
<
|
|
105
|
+
<VCol cols="12" md="4">
|
|
106
|
+
<VSelect
|
|
107
|
+
v-model="attrs.target"
|
|
108
|
+
:items="targetAttrs"
|
|
109
|
+
:label="t('dialog.link.target')"
|
|
110
|
+
variant="outlined"
|
|
111
|
+
return-object
|
|
112
|
+
density="compact"
|
|
113
|
+
/>
|
|
114
|
+
</VCol>
|
|
115
|
+
|
|
116
|
+
<VCol cols="12" md="8">
|
|
117
|
+
<VTextField v-model="attrs.cssClass" density="compact" variant="outlined" :label="t('dialog.link.class')" />
|
|
102
118
|
</VCol>
|
|
103
119
|
|
|
104
120
|
<VCol cols="12">
|
|
105
121
|
<VSelect
|
|
106
122
|
v-model="attrs.rel"
|
|
107
123
|
:items="relAttrs"
|
|
108
|
-
:label="
|
|
109
|
-
|
|
124
|
+
:label="t('dialog.link.rel')"
|
|
125
|
+
variant="outlined"
|
|
110
126
|
multiple
|
|
111
127
|
chips
|
|
112
128
|
closable-chips
|
|
113
129
|
clearable
|
|
130
|
+
density="compact"
|
|
114
131
|
/>
|
|
115
132
|
</VCol>
|
|
116
|
-
|
|
117
|
-
<VCol cols="12">
|
|
118
|
-
<VTextField v-model="attrs.cssClass" :variant="variantField" :label="ucFirst(t('dialog.link.class'))" />
|
|
119
|
-
</VCol>
|
|
120
133
|
</VRow>
|
|
121
134
|
</VCardText>
|
|
135
|
+
</template>
|
|
122
136
|
|
|
137
|
+
<template #actions>
|
|
123
138
|
<VCardActions>
|
|
124
139
|
<VRow>
|
|
125
140
|
<VCol class="d-flex justify-start">
|
|
126
141
|
<VBtn color="warning" v-if="editor.isActive('link')" :variant="variantBtn" :disabled="isDisabled" @click="clear">
|
|
127
|
-
{{
|
|
142
|
+
{{ t('dialog.clear') }}
|
|
128
143
|
</VBtn>
|
|
129
144
|
</VCol>
|
|
130
145
|
<VCol class="d-flex justify-end">
|
|
131
146
|
<VBtn :variant="variantBtn" @click="close" class="mr-2">
|
|
132
|
-
{{
|
|
147
|
+
{{ t('dialog.close') }}
|
|
133
148
|
</VBtn>
|
|
134
149
|
<VBtn color="primary" :variant="variantBtn" :disabled="isDisabled" @click="apply">
|
|
135
|
-
{{
|
|
150
|
+
{{ t('dialog.apply') }}
|
|
136
151
|
</VBtn>
|
|
137
152
|
</VCol>
|
|
138
153
|
</VRow>
|
|
139
154
|
</VCardActions>
|
|
140
|
-
</
|
|
141
|
-
</
|
|
155
|
+
</template>
|
|
156
|
+
</Dialog>
|
|
142
157
|
</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,23 +1,20 @@
|
|
|
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
|
|
|
@@ -57,11 +54,12 @@ const unformatHtml = (html: string): string => {
|
|
|
57
54
|
|
|
58
55
|
const showDialog = (event: CustomEvent) => {
|
|
59
56
|
sourceCode.value = event.detail.html
|
|
60
|
-
|
|
57
|
+
|
|
58
|
+
dialog.value.open()
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
const saveChanges = () => {
|
|
64
|
-
dialog.value
|
|
62
|
+
dialog.value.close()
|
|
65
63
|
|
|
66
64
|
editor.value.commands.setContent(sourceCode.value, true)
|
|
67
65
|
}
|
|
@@ -80,16 +78,14 @@ watch(() => formatted.value, () => {
|
|
|
80
78
|
</script>
|
|
81
79
|
|
|
82
80
|
<template>
|
|
83
|
-
<
|
|
84
|
-
<
|
|
85
|
-
<VCardTitle>{{ ucFirst(t('dialog.source.title')) }}</VCardTitle>
|
|
86
|
-
|
|
81
|
+
<Dialog ref="dialog" module="source" :max-width="1500">
|
|
82
|
+
<template #content>
|
|
87
83
|
<VCardText>
|
|
88
84
|
<VContainer fluid class="pt-0 pl-0 pr-0">
|
|
89
85
|
<VRow>
|
|
90
86
|
<VCol>
|
|
91
87
|
<VBtn v-model="formatted" :color="`${formatted ? 'primary' : ''}`" @click="formatted = !formatted">
|
|
92
|
-
{{
|
|
88
|
+
{{ t('dialog.source.prettify') }}
|
|
93
89
|
</VBtn>
|
|
94
90
|
</VCol>
|
|
95
91
|
</VRow>
|
|
@@ -103,18 +99,20 @@ watch(() => formatted.value, () => {
|
|
|
103
99
|
class="source-code-area"
|
|
104
100
|
/>
|
|
105
101
|
</VCardText>
|
|
102
|
+
</template>
|
|
106
103
|
|
|
104
|
+
<template #actions>
|
|
107
105
|
<VCardActions>
|
|
108
106
|
<VSpacer></VSpacer>
|
|
109
107
|
<VBtn :variant="variantBtn" @click="dialog = false">
|
|
110
|
-
{{
|
|
108
|
+
{{ t('dialog.close') }}
|
|
111
109
|
</VBtn>
|
|
112
110
|
<VBtn :variant="variantBtn" color="primary" @click="saveChanges">
|
|
113
|
-
{{
|
|
111
|
+
{{ t('dialog.apply') }}
|
|
114
112
|
</VBtn>
|
|
115
113
|
</VCardActions>
|
|
116
|
-
</
|
|
117
|
-
</
|
|
114
|
+
</template>
|
|
115
|
+
</Dialog>
|
|
118
116
|
</template>
|
|
119
117
|
|
|
120
118
|
<style scoped lang="scss">
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
|
|
3
|
+
import { Editor } from "@tiptap/vue-3";
|
|
4
|
+
import { useI18n } from 'vue-i18n'
|
|
5
|
+
|
|
6
|
+
import { computed, inject, Ref, ref } from 'vue'
|
|
7
|
+
|
|
8
|
+
const { t } = useI18n()
|
|
9
|
+
|
|
10
|
+
defineExpose({ open, close })
|
|
11
|
+
|
|
12
|
+
const props = defineProps({
|
|
13
|
+
show: { type: Boolean, default: true },
|
|
14
|
+
fontColor: { type: Boolean, default: false },
|
|
15
|
+
backgroundColor: { type: Boolean, default: false },
|
|
16
|
+
color: { type: String, default: '' },
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const emit = defineEmits(['close'])
|
|
20
|
+
|
|
21
|
+
const editor = inject('tiptapifyEditor') as Ref<Editor>
|
|
22
|
+
|
|
23
|
+
const colorPicker = ref(false)
|
|
24
|
+
const initialColor = ref(computed(() => props.color).value)
|
|
25
|
+
const customColor = ref(computed(() => props.color).value)
|
|
26
|
+
|
|
27
|
+
const colorSelected = ref<boolean>(false)
|
|
28
|
+
|
|
29
|
+
const greyShades = {
|
|
30
|
+
black: '#000',
|
|
31
|
+
'#222': '#222',
|
|
32
|
+
darkgray: '#444',
|
|
33
|
+
gray: '#888',
|
|
34
|
+
lightgray: '#ccc',
|
|
35
|
+
white: '#fff',
|
|
36
|
+
}
|
|
37
|
+
const blueShades = {
|
|
38
|
+
'#000044': '#000044',
|
|
39
|
+
'#00006e': '#00006e',
|
|
40
|
+
'#0000bb': '#0000bb',
|
|
41
|
+
blue: '#0000ff',
|
|
42
|
+
lightblue: '#0088ff',
|
|
43
|
+
cyan: '#00FFFF',
|
|
44
|
+
}
|
|
45
|
+
const greenShades = {
|
|
46
|
+
'#003100': '#003100',
|
|
47
|
+
'#005200': '#005200',
|
|
48
|
+
'#007000': '#007000',
|
|
49
|
+
'#00b700': '#00b700',
|
|
50
|
+
green: '#00ff00',
|
|
51
|
+
'#70ff70': '#70ff70',
|
|
52
|
+
}
|
|
53
|
+
const redShades = {
|
|
54
|
+
'#520000': '#520000',
|
|
55
|
+
'#810000': '#810000',
|
|
56
|
+
'#b20000': '#b20000',
|
|
57
|
+
red: '#ff0000',
|
|
58
|
+
'#ff2323': '#ff2323',
|
|
59
|
+
'#ff5c5c': '#ff5c5c',
|
|
60
|
+
}
|
|
61
|
+
const otherShades = {
|
|
62
|
+
brown: '#964B00',
|
|
63
|
+
orange: '#ff9900',
|
|
64
|
+
yellow: '#ffff00',
|
|
65
|
+
pink: '#ff00ff',
|
|
66
|
+
purple: '#800080',
|
|
67
|
+
indigo: '#4b0082',
|
|
68
|
+
}
|
|
69
|
+
const colors = { ...greyShades, ...blueShades, ...greenShades, ...redShades, ...otherShades }
|
|
70
|
+
|
|
71
|
+
type Color = { r: number, g: number, b: number, a?: number }
|
|
72
|
+
|
|
73
|
+
function calculateShadowColor(color: string): string {
|
|
74
|
+
const rgb: Color = hexToRgb(color)
|
|
75
|
+
|
|
76
|
+
for (const channel of Object.keys(rgb)) {
|
|
77
|
+
rgb[channel] = Math.round(rgb[channel] * .35)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return Object.values(rgb).join(', ')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function hexToRgb(hex: string): Color {
|
|
84
|
+
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
|
85
|
+
hex = hex.replace(shorthandRegex, function(m, r, g, b) {
|
|
86
|
+
return r + r + g + g + b + b;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
90
|
+
return {
|
|
91
|
+
r: result ? parseInt(result[1], 16) : 0,
|
|
92
|
+
g: result ? parseInt(result[2], 16) : 0,
|
|
93
|
+
b: result ? parseInt(result[3], 16) : 0
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function hoverColor(color: string) {
|
|
98
|
+
colorSelected.value = false
|
|
99
|
+
|
|
100
|
+
if (props.fontColor) {
|
|
101
|
+
editor.value.chain().focus().setColor(color).run()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (props.backgroundColor) {
|
|
105
|
+
editor.value.chain().focus().setHighlight({ color: color }).run()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function close() {
|
|
110
|
+
resetColor()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function resetColor() {
|
|
114
|
+
if (colorSelected.value) {
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (props.fontColor) {
|
|
119
|
+
editor.value.chain().focus().setColor(initialColor.value).run()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (props.backgroundColor) {
|
|
123
|
+
editor.value.chain().focus().setHighlight({ color: initialColor.value }).run()
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function setColor() {
|
|
128
|
+
colorSelected.value = true
|
|
129
|
+
|
|
130
|
+
emit('close')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function unsetColor() {
|
|
134
|
+
if (props.fontColor) {
|
|
135
|
+
editor.value.chain().focus().unsetColor().run()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (props.backgroundColor) {
|
|
139
|
+
editor.value.chain().focus().unsetHighlight().run()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
emit('close')
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isColorActive(color: string): boolean {
|
|
146
|
+
return props.color === color
|
|
147
|
+
}
|
|
148
|
+
</script>
|
|
149
|
+
|
|
150
|
+
<template>
|
|
151
|
+
<VSheet class="pa-2">
|
|
152
|
+
<div class="tiptapify-style-color-container">
|
|
153
|
+
<template v-for="colorCode in colors">
|
|
154
|
+
<div
|
|
155
|
+
class="tiptapify-style-color-item"
|
|
156
|
+
:class="isColorActive(colorCode) ? 'tiptapify-style-color-item-active' : ''"
|
|
157
|
+
@mouseenter="hoverColor(colorCode)"
|
|
158
|
+
@mouseleave="resetColor()"
|
|
159
|
+
@click="setColor"
|
|
160
|
+
>
|
|
161
|
+
<div
|
|
162
|
+
class="tiptapify-style-color-picker"
|
|
163
|
+
:style="`background-color: ${colorCode}; box-shadow: 1px 1px 4px rgb(${calculateShadowColor(colorCode)});`"
|
|
164
|
+
></div>
|
|
165
|
+
</div>
|
|
166
|
+
</template>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<VDivider class="mt-2 mb-2" />
|
|
170
|
+
|
|
171
|
+
<VMenu v-model="colorPicker" :close-on-content-click="false">
|
|
172
|
+
<template #activator="{ props: menuProps }">
|
|
173
|
+
<VBtn v-bind="menuProps" variant="flat" block>
|
|
174
|
+
<template #prepend>
|
|
175
|
+
<div
|
|
176
|
+
class="tiptapify-style-color-picker"
|
|
177
|
+
:style="`background-color: ${customColor}; box-shadow: 1px 1px 4px rgb(0, 0, 0, .35);`"
|
|
178
|
+
>
|
|
179
|
+
</div>
|
|
180
|
+
</template>
|
|
181
|
+
<template #default>
|
|
182
|
+
{{ t('style.color.custom') }}
|
|
183
|
+
</template>
|
|
184
|
+
</VBtn>
|
|
185
|
+
</template>
|
|
186
|
+
|
|
187
|
+
<VCard>
|
|
188
|
+
<VCardItem>
|
|
189
|
+
<VColorPicker v-model="customColor" elevated elevation="24" hide-inputs @update:model-value="hoverColor($event)" />
|
|
190
|
+
</VCardItem>
|
|
191
|
+
|
|
192
|
+
<VCardActions>
|
|
193
|
+
<VBtn variant="flat" color="primary" @click="setColor">OK</VBtn>
|
|
194
|
+
<VBtn variant="flat" color="grey-400" @click="colorPicker = !colorPicker; customColor = initialColor; resetColor()">Cancel</VBtn>
|
|
195
|
+
</VCardActions>
|
|
196
|
+
</VCard>
|
|
197
|
+
</VMenu>
|
|
198
|
+
|
|
199
|
+
<VDivider class="mt-2 mb-2" />
|
|
200
|
+
|
|
201
|
+
<VBtn @click="unsetColor" block variant="flat">
|
|
202
|
+
{{ t('style.color.unset') }}
|
|
203
|
+
</VBtn>
|
|
204
|
+
</VSheet>
|
|
205
|
+
</template>
|
|
206
|
+
|
|
207
|
+
<style lang="scss" scoped>
|
|
208
|
+
.tiptapify-style-color-container {
|
|
209
|
+
display: grid;
|
|
210
|
+
grid-template-columns: repeat(6, 1fr);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.tiptapify-style-color-item {
|
|
214
|
+
cursor: pointer;
|
|
215
|
+
border-radius: 8px;
|
|
216
|
+
padding: 5px;
|
|
217
|
+
transition: background-color .2s;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.tiptapify-style-color-item:hover, .tiptapify-style-color-item-active {
|
|
221
|
+
background-color: #dedede;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.tiptapify-style-color-picker {
|
|
225
|
+
width: 24px;
|
|
226
|
+
height: 24px;
|
|
227
|
+
margin: 2px;
|
|
228
|
+
border-radius: 4px;
|
|
229
|
+
}
|
|
230
|
+
</style>
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
|
|
3
3
|
import { Editor } from "@tiptap/vue-3";
|
|
4
|
-
import helpers from '@tiptapify/utils/helpers'
|
|
5
4
|
import { useI18n } from 'vue-i18n'
|
|
6
5
|
|
|
7
6
|
import { inject, Ref, ref } from 'vue'
|
|
8
7
|
|
|
9
8
|
const { t } = useI18n()
|
|
10
9
|
|
|
11
|
-
const { ucFirst } = helpers
|
|
12
|
-
|
|
13
10
|
defineExpose({ open })
|
|
14
11
|
|
|
15
12
|
defineProps({
|
|
16
|
-
show: { type: Boolean, default: false },
|
|
17
13
|
maxCols: { type: Number, default: 10 },
|
|
18
14
|
maxRows: { type: Number, default: 10 },
|
|
19
15
|
})
|
|
@@ -56,7 +52,7 @@ function printSelection() {
|
|
|
56
52
|
v-model="withHeaderRow"
|
|
57
53
|
density="compact"
|
|
58
54
|
color="primary"
|
|
59
|
-
:label="
|
|
55
|
+
:label="t('media.tables.insertWithHeaderRow')" hide-details
|
|
60
56
|
/>
|
|
61
57
|
|
|
62
58
|
<div v-for="rowNum in maxRows" :key="`row-${rowNum}`" class="tiptapify-insert-table-row">
|
|
@@ -74,8 +70,8 @@ function printSelection() {
|
|
|
74
70
|
|
|
75
71
|
<div class="tiptapify-table-builder-info">
|
|
76
72
|
<span>
|
|
77
|
-
{{
|
|
78
|
-
{{
|
|
73
|
+
{{ t('media.tables.rows') }}: {{ rowHover }}
|
|
74
|
+
{{ t('media.tables.cols') }}: {{ colHover }}
|
|
79
75
|
</span>
|
|
80
76
|
<span>
|
|
81
77
|
{{ printSelection() }}
|
|
@@ -111,7 +107,7 @@ $mutedColor: var(--v-theme-muted-color, #888888);
|
|
|
111
107
|
margin: 2px;
|
|
112
108
|
width: 100%;
|
|
113
109
|
height: 100%;
|
|
114
|
-
border: 1px solid #
|
|
110
|
+
border: 1px solid #bbb;
|
|
115
111
|
background: #fff;
|
|
116
112
|
border-radius: 4px;
|
|
117
113
|
filter: drop-shadow(2px 2px 4px #88888888);
|