tiptapify 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiptapify",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Tiptap3 editor with Vuetify3 menu implementation",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,5 @@
1
1
  <script setup lang="ts">
2
2
  import { Editor } from "@tiptap/vue-3";
3
- import LinkDialog from "@tiptapify/extensions/components/LinkDialog.vue";
4
3
  import { computed, defineProps, inject, Ref, ref } from "vue";
5
4
  import { BubbleMenu } from '@tiptap/vue-3/menus'
6
5
  import * as mdi from '@mdi/js'
@@ -12,8 +11,6 @@ defineProps({
12
11
 
13
12
  const editor = inject('tiptapifyEditor') as Ref<Editor>
14
13
 
15
- const bubbleMenuLinkButton = ref(null)
16
-
17
14
  const items = ref([
18
15
  {
19
16
  name: 'bold',
@@ -76,7 +73,7 @@ const items = ref([
76
73
  color: computed(() => editor.value.isActive('link') ? 'primary' : ''),
77
74
  disabled: computed(() => editor.value.isActive('code') || editor.value.isActive('codeBlock')),
78
75
  },
79
- click: () => linkAction(),
76
+ click: () => editor.value.commands.showLink()
80
77
  },
81
78
  {
82
79
  name: 'format clear',
@@ -84,12 +81,6 @@ const items = ref([
84
81
  click: () => editor.value.chain().focus().unsetAllMarks().clearNodes().run(),
85
82
  }
86
83
  ])
87
-
88
- function linkAction() {
89
- return editor.value.isActive('link')
90
- ? editor.value.chain().focus().unsetLink().run()
91
- : bubbleMenuLinkButton.value?.open()
92
- }
93
84
  </script>
94
85
 
95
86
  <template>
@@ -98,6 +89,10 @@ function linkAction() {
98
89
  :editor="editor"
99
90
  :options="{ placement: 'bottom' }"
100
91
  :shouldShow="({ editor, view, state, from, to }) => {
92
+ if (editor.isActive('image') || editor.isActive('code') || editor.isActive('codeBlock')) {
93
+ return false
94
+ }
95
+
101
96
  const docSize = editor.state.doc.content.size
102
97
  const isAllSelected = from === 0 && to === docSize
103
98
 
@@ -120,8 +115,6 @@ function linkAction() {
120
115
  </VCard>
121
116
  </div>
122
117
  </BubbleMenu>
123
-
124
- <LinkDialog ref="bubbleMenuLinkButton" />
125
118
  </template>
126
119
 
127
120
  <style scoped lang="scss">
@@ -13,7 +13,8 @@ import { useTheme } from "vuetify/framework";
13
13
 
14
14
  const props = defineProps({
15
15
  content: String|Object,
16
- variant: { type: String, default () { return 'elevated' } },
16
+ variantBtn: { type: String, default () { return 'elevated' } },
17
+ variantField: { type: String, default () { return 'solo' } },
17
18
  toolbar: { type: Boolean, default () { return true } },
18
19
  items: { type: Array<string>, default() { return [] }},
19
20
  itemsExclude: { type: Boolean, default() { return false } },
@@ -62,7 +63,8 @@ onBeforeUnmount(() => {
62
63
  <template v-if="toolbar">
63
64
  <Toolbar
64
65
  v-if="editor"
65
- :variant="variant"
66
+ :variant-btn="variantBtn"
67
+ :variant-field="variantField"
66
68
  :font-measure="fontMeasure"
67
69
  :items="items"
68
70
  :items-exclude="itemsExclude"
@@ -72,9 +74,9 @@ onBeforeUnmount(() => {
72
74
 
73
75
  <div :class="`border border-t-0 rounded-b-${rounded}`">
74
76
  <div class="pa-2 tiptapify-container">
75
- <MenuFloating v-if="floatingMenu" :variant="variant" :theme="theme" />
77
+ <MenuFloating v-if="floatingMenu" :variant="variantBtn" :theme="theme" />
76
78
 
77
- <MenuBubble v-if="bubbleMenu" :variant="variant" :theme="theme" />
79
+ <MenuBubble v-if="bubbleMenu" :variant="variantBtn" :theme="theme" />
78
80
 
79
81
  <EditorContent :editor="editor" class="tiptapify-editor" />
80
82
  </div>
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { Editor } from "@tiptap/vue-3";
3
+ import ImageDialog from "@tiptapify/extensions/components/ImageDialog.vue";
3
4
  import LinkDialog from "@tiptapify/extensions/components/LinkDialog.vue";
4
5
  import ShowSourceDialog from "@tiptapify/extensions/components/ShowSourceDialog.vue";
5
6
  import PreviewDialog from "@tiptapify/extensions/components/PreviewDialog.vue";
@@ -11,7 +12,8 @@ import { useI18n } from "vue-i18n";
11
12
  import { toolbarItems, ToolbarItemSections } from "@tiptapify/components/Toolbar/items";
12
13
 
13
14
  const props = defineProps({
14
- variant: { type: String, default () { return 'flat' }},
15
+ variantBtn: { type: String, default () { return 'elevated' }},
16
+ variantField: { type: String, default () { return 'solo' }},
15
17
  items: { type: Array<string>, default() { return [] }},
16
18
  itemsExclude: { type: Boolean, default() { return false } },
17
19
  headingLevels: { type: Array<number>, default() { return [] }},
@@ -26,14 +28,11 @@ const { t } = useI18n();
26
28
 
27
29
  const editor = inject('tiptapifyEditor') as Ref<Editor>
28
30
 
29
- const toolbarLinkButton = ref(null)
30
-
31
31
  const items = toolbarItems(
32
32
  editor,
33
33
  computed(() => props.fontMeasure).value,
34
34
  { list: computed(() => props.items).value, exclude: computed(() => props.itemsExclude).value },
35
- computed(() => props.headingLevels).value,
36
- toolbarLinkButton
35
+ computed(() => props.headingLevels).value
37
36
  )
38
37
  const toolbarItemsRef: Ref<ToolbarItemSections> = ref(items)
39
38
 
@@ -44,15 +43,15 @@ const toolbarItemsRef: Ref<ToolbarItemSections> = ref(items)
44
43
  <VToolbar elevation="1" :theme="theme" height="auto" :class="`ps-1 rounded-t-${rounded}`">
45
44
  <VToolbarItems class="py-2">
46
45
  <template v-for="(toolbarSection, sectionKey) in toolbarItemsRef" :key="sectionKey">
47
- <Group v-if="toolbarSection.group" :variant="variant" :toolbar-section="toolbarSection" />
46
+ <Group v-if="toolbarSection.group" :variant="variantBtn" :toolbar-section="toolbarSection" />
48
47
 
49
- <Toggle v-else-if="toolbarSection.toggle" :variant="variant" :toolbar-section="toolbarSection" />
48
+ <Toggle v-else-if="toolbarSection.toggle" :variant="variantBtn" :toolbar-section="toolbarSection" />
50
49
 
51
50
  <VBtn
52
51
  v-else
53
52
  v-for="(toolbarItem, itemKey) in toolbarSection.items"
54
53
  :key="itemKey"
55
- :variant="variant"
54
+ :variant="variantBtn"
56
55
  v-bind="toolbarItem.props"
57
56
  v-on="toolbarItem.attrs"
58
57
  class="menu-button"
@@ -73,9 +72,10 @@ const toolbarItemsRef: Ref<ToolbarItemSections> = ref(items)
73
72
  </VToolbarItems>
74
73
  </VToolbar>
75
74
 
76
- <LinkDialog ref="toolbarLinkButton" />
75
+ <ImageDialog :variant-field="variantField" />
76
+ <LinkDialog :variant-field="variantField" />
77
77
  <PreviewDialog />
78
- <ShowSourceDialog />
78
+ <ShowSourceDialog :variant-field="variantField" />
79
79
  </div>
80
80
  </template>
81
81
 
@@ -3,11 +3,11 @@ import { Editor } from "@tiptap/vue-3";
3
3
  import TableBuilder from "@tiptapify/extensions/components/TableBuilder.vue";
4
4
  import { computed, markRaw, Ref } from "vue";
5
5
 
6
- export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
6
+ export function getMediaItems(editor: Editor) {
7
7
  return {
8
8
  link: {
9
- name: 'format.link',
10
- tooltip: 'format.link',
9
+ name: 'media.link',
10
+ tooltip: 'media.link',
11
11
  icon: computed(() => editor.isActive('link') ? mdi.mdiLinkOff : mdi.mdiLink),
12
12
  enabled: true,
13
13
  props: {
@@ -15,16 +15,25 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
15
15
  disabled: computed(() => editor.isActive('code') || editor.isActive('codeBlock')),
16
16
  },
17
17
  attrs: {
18
- click: computed(() => {
19
- return editor.isActive('link')
20
- ? editor.chain().focus().unsetLink().run
21
- : toolbarLinkButton?.value?.open
22
- })
18
+ click: () => editor.commands.showLink()
19
+ }
20
+ },
21
+ image: {
22
+ name: 'media.image',
23
+ tooltip: 'media.image',
24
+ icon: mdi.mdiImage,
25
+ enabled: true,
26
+ props: {
27
+ color: computed(() => editor.isActive('image') ? 'primary' : ''),
28
+ disabled: computed(() => editor.isActive('code') || editor.isActive('codeBlock')),
29
+ },
30
+ attrs: {
31
+ click: () => editor.commands.showTiptapifyImage()
23
32
  }
24
33
  },
25
34
  table: {
26
35
  name: 'tables',
27
- tooltip: 'format.tables.table',
36
+ tooltip: 'media.tables.table',
28
37
  icon: mdi.mdiTable,
29
38
  modelValue: false,
30
39
  enabled: true,
@@ -35,7 +44,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
35
44
  children: [
36
45
  {
37
46
  name: 'insert table',
38
- tooltip: 'format.tables.insertTable',
47
+ tooltip: 'media.tables.insertTable',
39
48
  icon: mdi.mdiTablePlus,
40
49
  enabled: true,
41
50
  props: {
@@ -49,8 +58,8 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
49
58
  component: markRaw(TableBuilder),
50
59
  },
51
60
  {
52
- name: 'insert table',
53
- tooltip: 'format.tables.deleteTable',
61
+ name: 'delete table',
62
+ tooltip: 'media.tables.deleteTable',
54
63
  icon: mdi.mdiTableMinus,
55
64
  enabled: true,
56
65
  props: {
@@ -62,7 +71,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
62
71
  },
63
72
  {
64
73
  name: 'table row',
65
- tooltip: 'format.tables.row',
74
+ tooltip: 'media.tables.row',
66
75
  icon: mdi.mdiTableRow,
67
76
  enabled: true,
68
77
  props: {
@@ -80,7 +89,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
80
89
  children: [
81
90
  {
82
91
  name: 'insert row before',
83
- tooltip: 'format.tables.insertRowBefore',
92
+ tooltip: 'media.tables.insertRowBefore',
84
93
  icon: mdi.mdiTableRowPlusBefore,
85
94
  enabled: true,
86
95
  props: {
@@ -92,7 +101,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
92
101
  },
93
102
  {
94
103
  name: 'insert row after',
95
- tooltip: 'format.tables.insertRowAfter',
104
+ tooltip: 'media.tables.insertRowAfter',
96
105
  icon: mdi.mdiTableRowPlusAfter,
97
106
  enabled: true,
98
107
  props: {
@@ -104,7 +113,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
104
113
  },
105
114
  {
106
115
  name: 'delete row',
107
- tooltip: 'format.tables.deleteRow',
116
+ tooltip: 'media.tables.deleteRow',
108
117
  icon: mdi.mdiTableRowRemove,
109
118
  enabled: true,
110
119
  props: {
@@ -118,7 +127,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
118
127
  },
119
128
  {
120
129
  name: 'column',
121
- tooltip: 'format.tables.col',
130
+ tooltip: 'media.tables.col',
122
131
  icon: mdi.mdiTableColumn,
123
132
  enabled: true,
124
133
  props: {
@@ -136,7 +145,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
136
145
  children: [
137
146
  {
138
147
  name: 'insert col before',
139
- tooltip: 'format.tables.insertColBefore',
148
+ tooltip: 'media.tables.insertColBefore',
140
149
  icon: mdi.mdiTableColumnPlusBefore,
141
150
  enabled: true,
142
151
  props: {
@@ -148,7 +157,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
148
157
  },
149
158
  {
150
159
  name: 'insert column after',
151
- tooltip: 'format.tables.insertColAfter',
160
+ tooltip: 'media.tables.insertColAfter',
152
161
  icon: mdi.mdiTableColumnPlusAfter,
153
162
  enabled: true,
154
163
  props: {
@@ -160,7 +169,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
160
169
  },
161
170
  {
162
171
  name: 'delete column',
163
- tooltip: 'format.tables.deleteCol',
172
+ tooltip: 'media.tables.deleteCol',
164
173
  icon: mdi.mdiTableColumnRemove,
165
174
  enabled: true,
166
175
  props: {
@@ -174,7 +183,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
174
183
  },
175
184
  {
176
185
  name: 'merge cells',
177
- tooltip: 'format.tables.mergeCells',
186
+ tooltip: 'media.tables.mergeCells',
178
187
  icon: mdi.mdiTableMergeCells,
179
188
  enabled: true,
180
189
  props: {
@@ -186,7 +195,7 @@ export function getMediaItems(editor: Editor, toolbarLinkButton: Ref) {
186
195
  },
187
196
  {
188
197
  name: 'split cell',
189
- tooltip: 'format.tables.splitCell',
198
+ tooltip: 'media.tables.splitCell',
190
199
  icon: mdi.mdiTableSplitCell,
191
200
  enabled: true,
192
201
  props: {
@@ -49,8 +49,7 @@ export function toolbarItems(
49
49
  editor: any,
50
50
  fontMeasure: string,
51
51
  items: { list: Array<string>, exclude: boolean },
52
- customHeadingLevels: Array<number>,
53
- toolbarLinkButton: Ref,
52
+ customHeadingLevels: Array<number>
54
53
  ): ToolbarItemSections {
55
54
  const styleItems = ref(getStyleItems(editor.value, fontMeasure, customHeadingLevels))
56
55
  const formatItems = ref(getFormatItems(editor.value))
@@ -59,7 +58,7 @@ export function toolbarItems(
59
58
  const listItems = ref(getListItems(editor.value))
60
59
  const actionsItems = ref(getActionsItems(editor.value))
61
60
  const miscItems = ref(getMiscItems(editor.value))
62
- const mediaItems = ref(getMediaItems(editor.value, toolbarLinkButton))
61
+ const mediaItems = ref(getMediaItems(editor.value))
63
62
 
64
63
  const allMenuItems: ToolbarItemSections = {
65
64
  /**
@@ -23,7 +23,8 @@ import { Underline } from '@tiptap/extension-underline'
23
23
  import { TableKit } from '@tiptap/extension-table'
24
24
  import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight'
25
25
 
26
- import { Link } from '@tiptap/extension-link'
26
+ import { TiptapifyLink } from '@tiptapify/extensions/link'
27
+ import { TiptapifyImage } from '@tiptapify/extensions/image'
27
28
  import CodeBlockComponent from '@tiptapify/components/CodeBlockComponent.vue'
28
29
  import { ViewSource } from '@tiptapify/extensions/view-source'
29
30
  import { Preview } from '@tiptapify/extensions/preview'
@@ -69,11 +70,12 @@ export function editorExtensions (placeholder: string, slashCommands: boolean) {
69
70
  Typography,
70
71
  Underline,
71
72
  Highlight,
72
- Link.configure({
73
+ TiptapifyLink.configure({
73
74
  openOnClick: false,
74
- defaultProtocol: 'https',
75
+ defaultProtocol: 'https'
75
76
  }),
76
77
  Image,
78
+ TiptapifyImage,
77
79
  Superscript,
78
80
  Subscript,
79
81
  TableKit,
@@ -0,0 +1,142 @@
1
+ <script setup lang="ts">
2
+
3
+ import * as mdi from '@mdi/js'
4
+ import { Editor } from "@tiptap/vue-3";
5
+
6
+ import { useI18n } from 'vue-i18n'
7
+ import { computed, inject, onMounted, onUnmounted, Ref, ref } from 'vue'
8
+
9
+ import helpers from '@tiptapify/utils/helpers'
10
+
11
+ defineProps({
12
+ variantBtn: { type: String, default() { return 'elevated' }},
13
+ variantField: { type: String, default() { return 'solo' }}
14
+ })
15
+
16
+ const { ucFirst } = helpers
17
+
18
+ const editor = inject('tiptapifyEditor') as Ref<Editor>
19
+ const { t } = useI18n()
20
+
21
+ const generateImageAttrs = () => ({
22
+ src: '',
23
+ alt: '',
24
+ height: null,
25
+ width: null
26
+ })
27
+
28
+ const attrs = ref(generateImageAttrs())
29
+
30
+ const dialog = ref<boolean>(false)
31
+
32
+ const isDisabled = computed(() => {
33
+ const { src } = attrs.value
34
+ return !src
35
+ })
36
+
37
+ function apply() {
38
+ let { src, alt, width, height } = attrs.value
39
+
40
+ const imageOptions: { src: string, alt: string, width?: number, height?: number} = {
41
+ src,
42
+ alt
43
+ }
44
+
45
+ if (width) {
46
+ imageOptions.width = width
47
+ }
48
+
49
+ if (height) {
50
+ imageOptions.height = height
51
+ }
52
+
53
+ if (src) {
54
+ editor.value.chain().focus().setImage(imageOptions).run()
55
+ }
56
+
57
+ close()
58
+ }
59
+
60
+ function clear() {
61
+ editor.value.commands.deleteSelection()
62
+
63
+ close()
64
+ }
65
+
66
+ function close() {
67
+ dialog.value = false
68
+
69
+ attrs.value = generateImageAttrs()
70
+ }
71
+
72
+ const showTiptapifyImage = (event: CustomEvent) => {
73
+ attrs.value.src = event.detail.image?.src
74
+ attrs.value.alt = event.detail.image?.alt
75
+ attrs.value.width = event.detail.image?.width
76
+ attrs.value.height = event.detail.image?.height
77
+
78
+ dialog.value = true;
79
+ }
80
+
81
+ onMounted(() => {
82
+ window.addEventListener('tiptapify-show-tiptapifyImage', showTiptapifyImage as EventListener)
83
+ })
84
+
85
+ onUnmounted(() => {
86
+ window.removeEventListener('tiptapify-show-tiptapifyImage', showTiptapifyImage as EventListener)
87
+ })
88
+ </script>
89
+
90
+ <template>
91
+ <VDialog v-model="dialog" max-width="800" absolute @click:outside="close">
92
+ <VCard>
93
+ <VToolbar class="px-6" density="compact">
94
+ <span class="headline">{{ ucFirst(t('dialog.image.title')) }}</span>
95
+
96
+ <VSpacer />
97
+
98
+ <VBtn class="mx-0" icon @click="close">
99
+ <VIcon :icon="mdi.mdiClose" />
100
+ </VBtn>
101
+ </VToolbar>
102
+
103
+ <VCardText>
104
+ <VRow>
105
+ <VCol cols="12">
106
+ <VTextField v-model="attrs.src" :variant="variantField" :label="ucFirst(t('dialog.image.src'))" />
107
+ </VCol>
108
+
109
+ <VCol cols="12" md="6">
110
+ <VTextField v-model="attrs.alt" :variant="variantField" :label="ucFirst(t('dialog.image.alt'))" />
111
+ </VCol>
112
+
113
+ <VCol cols="12" md="3">
114
+ <VTextField v-model="attrs.width" type="number" :variant="variantField" :precision="0" :min="1" :label="ucFirst(t('dialog.image.width'))" />
115
+ </VCol>
116
+
117
+ <VCol cols="12" md="3">
118
+ <VTextField v-model="attrs.height" type="number" :variant="variantField" :precision="0" :min="1" :label="ucFirst(t('dialog.image.height'))" />
119
+ </VCol>
120
+ </VRow>
121
+ </VCardText>
122
+
123
+ <VCardActions>
124
+ <VRow>
125
+ <VCol class="d-flex justify-start">
126
+ <VBtn color="warning" v-if="editor.isActive('image')" :variant="variantBtn" :disabled="isDisabled" @click="clear">
127
+ {{ ucFirst(t('dialog.clear')) }}
128
+ </VBtn>
129
+ </VCol>
130
+ <VCol class="d-flex justify-end">
131
+ <VBtn :variant="variantBtn" @click="close" class="mr-2">
132
+ {{ ucFirst(t('dialog.close')) }}
133
+ </VBtn>
134
+ <VBtn color="primary" :variant="variantBtn" :disabled="isDisabled" @click="apply">
135
+ {{ ucFirst(t('dialog.apply')) }}
136
+ </VBtn>
137
+ </VCol>
138
+ </VRow>
139
+ </VCardActions>
140
+ </VCard>
141
+ </VDialog>
142
+ </template>
@@ -4,78 +4,82 @@ import * as mdi from '@mdi/js'
4
4
  import { Editor } from "@tiptap/vue-3";
5
5
 
6
6
  import { useI18n } from 'vue-i18n'
7
- import { computed, inject, Ref, ref, watch } from 'vue'
7
+ import { computed, inject, onMounted, onUnmounted, Ref, ref } from 'vue'
8
8
 
9
9
  import helpers from '@tiptapify/utils/helpers'
10
10
 
11
- defineExpose({ open })
11
+ defineProps({
12
+ variantBtn: { type: String, default() { return 'elevated' }},
13
+ variantField: { type: String, default() { return 'solo' }}
14
+ })
12
15
 
13
16
  const { ucFirst } = helpers
14
17
 
15
- interface Props {
16
- value?: string
17
- target?: '_self' | '_blank'
18
- destroy?: () => void
19
- }
20
-
21
- const props = withDefaults(defineProps<Props>(), {
22
- value: undefined,
23
- target: '_blank',
24
- destroy: undefined
25
- })
26
-
27
18
  const editor = inject('tiptapifyEditor') as Ref<Editor>
28
-
29
19
  const { t } = useI18n()
30
20
 
31
21
  const generateLinkAttrs = () => ({
32
22
  href: '',
33
- target: '_blank'
23
+ target: '_blank',
24
+ cssClass: '',
25
+ rel: ''
34
26
  })
35
27
 
28
+ const relAttrs = ['alternate', 'author', 'bookmark', 'external', 'help', 'license', 'next', 'nofollow', 'noreferrer', 'noopener', 'prev', 'search', 'tag']
29
+
36
30
  const attrs = ref(generateLinkAttrs())
37
31
 
38
32
  const dialog = ref<boolean>(false)
39
33
 
40
34
  const isDisabled = computed(() => {
41
- const { href, target } = attrs.value
42
- if (!href) return true
43
-
44
- return props.value === href && props.target === target
35
+ const { href } = attrs.value
36
+ return !href
45
37
  })
46
38
 
47
39
  function apply() {
48
- const { href, target } = attrs.value
40
+ let { href, target, rel, cssClass } = attrs.value
41
+ target = target ? '_blank' : '_self'
42
+ rel = rel.join(' ')
49
43
 
50
44
  if (href) {
51
- editor.value.chain().focus().extendMarkRange('link').setLink({ href, target }).run()
45
+ editor.value.chain().focus().extendMarkRange('link').setLink({ href, target, rel, class: cssClass }).run()
52
46
  }
47
+
53
48
  close()
54
49
  }
55
50
 
56
- function open() {
57
- dialog.value = true
51
+ function clear() {
52
+ editor.value.chain().focus().extendMarkRange('link').unsetLink().run()
53
+
54
+ close()
58
55
  }
59
56
 
60
57
  function close() {
61
58
  dialog.value = false
59
+
62
60
  attrs.value = generateLinkAttrs()
61
+ }
63
62
 
64
- setTimeout(() => props.destroy?.(), 300)
63
+ const showLink = (event: CustomEvent) => {
64
+ attrs.value.href = event.detail.link?.href
65
+ attrs.value.target = event.detail.link?.target === '_blank'
66
+ attrs.value.rel = event.detail.link?.rel?.split(' ')
67
+ attrs.value.cssClass = event.detail.link?.class
68
+
69
+ dialog.value = true;
65
70
  }
66
71
 
67
- watch(dialog, val => {
68
- if (!val) return
72
+ onMounted(() => {
73
+ window.addEventListener('tiptapify-show-link', showLink as EventListener)
74
+ })
69
75
 
70
- attrs.value = {
71
- href: props.value,
72
- target: props.target
73
- }
76
+ onUnmounted(() => {
77
+ window.removeEventListener('tiptapify-show-link', showLink as EventListener)
74
78
  })
75
79
  </script>
76
80
 
77
81
  <template>
78
- <VDialog v-model="dialog" max-width="400" absolute @click:outside="close">
82
+ <VDialog v-model="dialog" max-width="800" absolute @click:outside="close">
79
83
  <VCard>
80
84
  <VToolbar class="px-6" density="compact">
81
85
  <span class="headline">{{ ucFirst(t('dialog.link.title')) }}</span>
@@ -88,13 +92,50 @@ watch(dialog, val => {
88
92
  </VToolbar>
89
93
 
90
94
  <VCardText>
91
- <VTextField v-model="attrs.href" :label="ucFirst(t('dialog.link.placeholder'))" autofocus />
95
+ <VRow>
96
+ <VCol cols="12" md="9">
97
+ <VTextField v-model="attrs.href" :variant="variantField" :label="ucFirst(t('dialog.link.href'))" autofocus />
98
+ </VCol>
99
+
100
+ <VCol cols="12" md="3">
101
+ <VCheckbox v-model="attrs.target" color="primary" :label="ucFirst(t('dialog.link.target'))" />
102
+ </VCol>
103
+
104
+ <VCol cols="12">
105
+ <VSelect
106
+ v-model="attrs.rel"
107
+ :items="relAttrs"
108
+ :label="ucFirst(t('dialog.link.rel'))"
109
+ :variant="variantField"
110
+ multiple
111
+ chips
112
+ closable-chips
113
+ clearable
114
+ />
115
+ </VCol>
116
+
117
+ <VCol cols="12">
118
+ <VTextField v-model="attrs.cssClass" :variant="variantField" :label="ucFirst(t('dialog.link.class'))" />
119
+ </VCol>
120
+ </VRow>
92
121
  </VCardText>
93
122
 
94
123
  <VCardActions>
95
- <VBtn :disabled="isDisabled" @click="apply">
96
- {{ ucFirst(t('dialog.apply')) }}
97
- </VBtn>
124
+ <VRow>
125
+ <VCol class="d-flex justify-start">
126
+ <VBtn color="warning" v-if="editor.isActive('link')" :variant="variantBtn" :disabled="isDisabled" @click="clear">
127
+ {{ ucFirst(t('dialog.clear')) }}
128
+ </VBtn>
129
+ </VCol>
130
+ <VCol class="d-flex justify-end">
131
+ <VBtn :variant="variantBtn" @click="close" class="mr-2">
132
+ {{ ucFirst(t('dialog.close')) }}
133
+ </VBtn>
134
+ <VBtn color="primary" :variant="variantBtn" :disabled="isDisabled" @click="apply">
135
+ {{ ucFirst(t('dialog.apply')) }}
136
+ </VBtn>
137
+ </VCol>
138
+ </VRow>
98
139
  </VCardActions>
99
140
  </VCard>
100
141
  </VDialog>
@@ -10,7 +10,6 @@ const content = ref()
10
10
  const dialog = ref(false)
11
11
 
12
12
  const showDialog = (event: CustomEvent) => {
13
- console.log('preview event', event)
14
13
  content.value = event.detail.html
15
14
  dialog.value = true;
16
15
  }