Compare commits

...

2 Commits

Author SHA1 Message Date
Dominik Pschenitschni 7d3d426663 fix: TipTap editor reactive destructuring 2024-12-27 22:45:34 +01:00
Dominik Pschenitschni fc91cf6013 feat: use TipTap starter-kit 2024-12-27 22:29:30 +01:00
3 changed files with 105 additions and 178 deletions

View File

@ -60,37 +60,22 @@
"@sentry/tracing": "7.114.0", "@sentry/tracing": "7.114.0",
"@sentry/vue": "8.47.0", "@sentry/vue": "8.47.0",
"@tiptap/core": "2.10.4", "@tiptap/core": "2.10.4",
"@tiptap/extension-blockquote": "2.10.4",
"@tiptap/extension-bold": "2.10.4",
"@tiptap/extension-bullet-list": "2.10.4",
"@tiptap/extension-code": "2.10.4",
"@tiptap/extension-code-block": "2.10.4", "@tiptap/extension-code-block": "2.10.4",
"@tiptap/extension-code-block-lowlight": "2.10.4", "@tiptap/extension-code-block-lowlight": "2.10.4",
"@tiptap/extension-document": "2.10.4",
"@tiptap/extension-dropcursor": "2.10.4",
"@tiptap/extension-gapcursor": "2.10.4",
"@tiptap/extension-hard-break": "2.10.4", "@tiptap/extension-hard-break": "2.10.4",
"@tiptap/extension-heading": "2.10.4",
"@tiptap/extension-history": "2.10.4",
"@tiptap/extension-horizontal-rule": "2.10.4",
"@tiptap/extension-image": "2.10.4", "@tiptap/extension-image": "2.10.4",
"@tiptap/extension-italic": "2.10.4",
"@tiptap/extension-link": "2.10.4", "@tiptap/extension-link": "2.10.4",
"@tiptap/extension-list-item": "2.10.4",
"@tiptap/extension-ordered-list": "2.10.4",
"@tiptap/extension-paragraph": "2.10.4",
"@tiptap/extension-placeholder": "2.10.4", "@tiptap/extension-placeholder": "2.10.4",
"@tiptap/extension-strike": "2.10.4",
"@tiptap/extension-table": "2.10.4", "@tiptap/extension-table": "2.10.4",
"@tiptap/extension-table-cell": "2.10.4", "@tiptap/extension-table-cell": "2.10.4",
"@tiptap/extension-table-header": "2.10.4", "@tiptap/extension-table-header": "2.10.4",
"@tiptap/extension-table-row": "2.10.4", "@tiptap/extension-table-row": "2.10.4",
"@tiptap/extension-task-item": "2.10.4", "@tiptap/extension-task-item": "2.10.4",
"@tiptap/extension-task-list": "2.10.4", "@tiptap/extension-task-list": "2.10.4",
"@tiptap/extension-text": "2.10.4",
"@tiptap/extension-typography": "2.10.4", "@tiptap/extension-typography": "2.10.4",
"@tiptap/extension-underline": "2.10.4", "@tiptap/extension-underline": "2.10.4",
"@tiptap/pm": "2.10.4", "@tiptap/pm": "2.10.4",
"@tiptap/starter-kit": "^2.10.4",
"@tiptap/suggestion": "2.10.4", "@tiptap/suggestion": "2.10.4",
"@tiptap/vue-3": "2.10.4", "@tiptap/vue-3": "2.10.4",
"@vueuse/core": "12.2.0", "@vueuse/core": "12.2.0",

View File

@ -49,69 +49,24 @@ importers:
'@tiptap/core': '@tiptap/core':
specifier: 2.10.4 specifier: 2.10.4
version: 2.10.4(@tiptap/pm@2.10.4) version: 2.10.4(@tiptap/pm@2.10.4)
'@tiptap/extension-blockquote':
specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-bold':
specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-bullet-list':
specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-code':
specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-code-block': '@tiptap/extension-code-block':
specifier: 2.10.4 specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4) version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)
'@tiptap/extension-code-block-lowlight': '@tiptap/extension-code-block-lowlight':
specifier: 2.10.4 specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/extension-code-block@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)(highlight.js@11.8.0)(lowlight@2.9.0) version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/extension-code-block@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)(highlight.js@11.8.0)(lowlight@2.9.0)
'@tiptap/extension-document':
specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-dropcursor':
specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)
'@tiptap/extension-gapcursor':
specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)
'@tiptap/extension-hard-break': '@tiptap/extension-hard-break':
specifier: 2.10.4 specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-heading':
specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-history':
specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)
'@tiptap/extension-horizontal-rule':
specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)
'@tiptap/extension-image': '@tiptap/extension-image':
specifier: 2.10.4 specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-italic':
specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-link': '@tiptap/extension-link':
specifier: 2.10.4 specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4) version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)
'@tiptap/extension-list-item':
specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-ordered-list':
specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-paragraph':
specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-placeholder': '@tiptap/extension-placeholder':
specifier: 2.10.4 specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4) version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)
'@tiptap/extension-strike':
specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-table': '@tiptap/extension-table':
specifier: 2.10.4 specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4) version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)
@ -130,9 +85,6 @@ importers:
'@tiptap/extension-task-list': '@tiptap/extension-task-list':
specifier: 2.10.4 specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-text':
specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-typography': '@tiptap/extension-typography':
specifier: 2.10.4 specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
@ -142,6 +94,9 @@ importers:
'@tiptap/pm': '@tiptap/pm':
specifier: 2.10.4 specifier: 2.10.4
version: 2.10.4 version: 2.10.4
'@tiptap/starter-kit':
specifier: ^2.10.4
version: 2.10.4
'@tiptap/suggestion': '@tiptap/suggestion':
specifier: 2.10.4 specifier: 2.10.4
version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4) version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)
@ -2339,6 +2294,11 @@ packages:
peerDependencies: peerDependencies:
'@tiptap/core': ^2.7.0 '@tiptap/core': ^2.7.0
'@tiptap/extension-text-style@2.10.4':
resolution: {integrity: sha512-ibq7avkcwHyUSG53Hf+P31rrwsKVbbiqbWZM4kXC7M2X3iUwFrtvaa+SWzyWQfE1jl2cCrD1+rfSkj/alcOKGg==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/extension-text@2.10.4': '@tiptap/extension-text@2.10.4':
resolution: {integrity: sha512-wPdVxCHrIS9S+8n08lgyyqRZPj9FBbyLlFt74/lV5yBC3LOorq1VKdjrTskmaj4jud7ImXoKDyBddAYTHdJ1xw==} resolution: {integrity: sha512-wPdVxCHrIS9S+8n08lgyyqRZPj9FBbyLlFt74/lV5yBC3LOorq1VKdjrTskmaj4jud7ImXoKDyBddAYTHdJ1xw==}
peerDependencies: peerDependencies:
@ -2357,6 +2317,9 @@ packages:
'@tiptap/pm@2.10.4': '@tiptap/pm@2.10.4':
resolution: {integrity: sha512-pZ4NEkRtYoDLe0spARvXZ1N3hNv/5u6vfPdPtEbmNpoOSjSNqDC1kVM+qJY0iaCYpxbxcv7cxn3kBumcFLQpJQ==} resolution: {integrity: sha512-pZ4NEkRtYoDLe0spARvXZ1N3hNv/5u6vfPdPtEbmNpoOSjSNqDC1kVM+qJY0iaCYpxbxcv7cxn3kBumcFLQpJQ==}
'@tiptap/starter-kit@2.10.4':
resolution: {integrity: sha512-tu/WCs9Mkr5Nt8c3/uC4VvAbQlVX0OY7ygcqdzHGUeG9zP3twdW7o5xM3kyDKR2++sbVzqu5Ll5qNU+1JZvPGQ==}
'@tiptap/suggestion@2.10.4': '@tiptap/suggestion@2.10.4':
resolution: {integrity: sha512-7Bzcn1REA7OmVRxiMF2kVK9EhosXotdLAGaEvSbn4zQtHCJG0tREuYvPy53LGzVuPkBDR6Pf6sp1QbGvSne/8g==} resolution: {integrity: sha512-7Bzcn1REA7OmVRxiMF2kVK9EhosXotdLAGaEvSbn4zQtHCJG0tREuYvPy53LGzVuPkBDR6Pf6sp1QbGvSne/8g==}
peerDependencies: peerDependencies:
@ -8714,6 +8677,10 @@ snapshots:
dependencies: dependencies:
'@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4)
'@tiptap/extension-text-style@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))':
dependencies:
'@tiptap/core': 2.10.4(@tiptap/pm@2.10.4)
'@tiptap/extension-text@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))': '@tiptap/extension-text@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))':
dependencies: dependencies:
'@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4)
@ -8747,6 +8714,30 @@ snapshots:
prosemirror-transform: 1.10.2 prosemirror-transform: 1.10.2
prosemirror-view: 1.37.0 prosemirror-view: 1.37.0
'@tiptap/starter-kit@2.10.4':
dependencies:
'@tiptap/core': 2.10.4(@tiptap/pm@2.10.4)
'@tiptap/extension-blockquote': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-bold': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-bullet-list': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-code': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-code-block': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)
'@tiptap/extension-document': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-dropcursor': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)
'@tiptap/extension-gapcursor': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)
'@tiptap/extension-hard-break': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-heading': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-history': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)
'@tiptap/extension-horizontal-rule': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)
'@tiptap/extension-italic': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-list-item': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-ordered-list': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-paragraph': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-strike': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-text': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/extension-text-style': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/pm': 2.10.4
'@tiptap/suggestion@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)': '@tiptap/suggestion@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)':
dependencies: dependencies:
'@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4)

View File

@ -17,7 +17,7 @@
v-tooltip="$t('input.editor.bold')" v-tooltip="$t('input.editor.bold')"
class="editor-bubble__button" class="editor-bubble__button"
:class="{ 'is-active': editor.isActive('bold') }" :class="{ 'is-active': editor.isActive('bold') }"
@click="editor.chain().focus().toggleBold().run()" @click="() => editor?.chain().focus().toggleBold().run()"
> >
<Icon :icon="['fa', 'fa-bold']" /> <Icon :icon="['fa', 'fa-bold']" />
</BaseButton> </BaseButton>
@ -25,7 +25,7 @@
v-tooltip="$t('input.editor.italic')" v-tooltip="$t('input.editor.italic')"
class="editor-bubble__button" class="editor-bubble__button"
:class="{ 'is-active': editor.isActive('italic') }" :class="{ 'is-active': editor.isActive('italic') }"
@click="editor.chain().focus().toggleItalic().run()" @click="() => editor?.chain().focus().toggleItalic().run()"
> >
<Icon :icon="['fa', 'fa-italic']" /> <Icon :icon="['fa', 'fa-italic']" />
</BaseButton> </BaseButton>
@ -33,7 +33,7 @@
v-tooltip="$t('input.editor.underline')" v-tooltip="$t('input.editor.underline')"
class="editor-bubble__button" class="editor-bubble__button"
:class="{ 'is-active': editor.isActive('underline') }" :class="{ 'is-active': editor.isActive('underline') }"
@click="editor.chain().focus().toggleUnderline().run()" @click="() => editor?.chain().focus().toggleUnderline().run()"
> >
<Icon :icon="['fa', 'fa-underline']" /> <Icon :icon="['fa', 'fa-underline']" />
</BaseButton> </BaseButton>
@ -41,7 +41,7 @@
v-tooltip="$t('input.editor.strikethrough')" v-tooltip="$t('input.editor.strikethrough')"
class="editor-bubble__button" class="editor-bubble__button"
:class="{ 'is-active': editor.isActive('strike') }" :class="{ 'is-active': editor.isActive('strike') }"
@click="editor.chain().focus().toggleStrike().run()" @click="() => editor?.chain().focus().toggleStrike().run()"
> >
<Icon :icon="['fa', 'fa-strikethrough']" /> <Icon :icon="['fa', 'fa-strikethrough']" />
</BaseButton> </BaseButton>
@ -49,7 +49,7 @@
v-tooltip="$t('input.editor.code')" v-tooltip="$t('input.editor.code')"
class="editor-bubble__button" class="editor-bubble__button"
:class="{ 'is-active': editor.isActive('code') }" :class="{ 'is-active': editor.isActive('code') }"
@click="editor.chain().focus().toggleCode().run()" @click="() => editor?.chain().focus().toggleCode().run()"
> >
<Icon :icon="['fa', 'fa-code']" /> <Icon :icon="['fa', 'fa-code']" />
</BaseButton> </BaseButton>
@ -87,7 +87,7 @@
<li> <li>
<BaseButton <BaseButton
class="done-edit" class="done-edit"
@click="setEdit" @click="() => setEdit()"
> >
{{ $t('input.editor.edit') }} {{ $t('input.editor.edit') }}
</BaseButton> </BaseButton>
@ -108,7 +108,7 @@
<li v-if="!isEditing"> <li v-if="!isEditing">
<BaseButton <BaseButton
class="done-edit" class="done-edit"
@click="setEdit" @click="() => setEdit()"
> >
{{ $t('input.editor.edit') }} {{ $t('input.editor.edit') }}
</BaseButton> </BaseButton>
@ -137,12 +137,17 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {computed, nextTick, onBeforeUnmount, onMounted, ref, watch} from 'vue' import {computed, nextTick, onBeforeUnmount, onMounted, ref, watch, watchEffect} from 'vue'
import {useI18n} from 'vue-i18n'
import {eventToHotkeyString} from '@github/hotkey'
import EditorToolbar from './EditorToolbar.vue' import EditorToolbar from './EditorToolbar.vue'
import Link from '@tiptap/extension-link' import StarterKit from '@tiptap/starter-kit'
import {Extension, mergeAttributes} from '@tiptap/core'
import {BubbleMenu, EditorContent, type Extensions, useEditor} from '@tiptap/vue-3'
import Link from '@tiptap/extension-link'
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight' import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
import Table from '@tiptap/extension-table' import Table from '@tiptap/extension-table'
import TableCell from '@tiptap/extension-table-cell' import TableCell from '@tiptap/extension-table-cell'
@ -151,29 +156,11 @@ import TableRow from '@tiptap/extension-table-row'
import Typography from '@tiptap/extension-typography' import Typography from '@tiptap/extension-typography'
import Image from '@tiptap/extension-image' import Image from '@tiptap/extension-image'
import Underline from '@tiptap/extension-underline' import Underline from '@tiptap/extension-underline'
import {Placeholder} from '@tiptap/extension-placeholder'
import TaskItem from '@tiptap/extension-task-item' import TaskItem from '@tiptap/extension-task-item'
import TaskList from '@tiptap/extension-task-list' import TaskList from '@tiptap/extension-task-list'
import HardBreak from '@tiptap/extension-hard-break'
import {Blockquote} from '@tiptap/extension-blockquote'
import {Bold} from '@tiptap/extension-bold'
import {BulletList} from '@tiptap/extension-bullet-list'
import {Code} from '@tiptap/extension-code'
import {Document} from '@tiptap/extension-document'
import {Dropcursor} from '@tiptap/extension-dropcursor'
import {Gapcursor} from '@tiptap/extension-gapcursor'
import {HardBreak} from '@tiptap/extension-hard-break'
import {Heading} from '@tiptap/extension-heading'
import {History} from '@tiptap/extension-history'
import {HorizontalRule} from '@tiptap/extension-horizontal-rule'
import {Italic} from '@tiptap/extension-italic'
import {ListItem} from '@tiptap/extension-list-item'
import {OrderedList} from '@tiptap/extension-ordered-list'
import {Paragraph} from '@tiptap/extension-paragraph'
import {Strike} from '@tiptap/extension-strike'
import {Text} from '@tiptap/extension-text'
import {BubbleMenu, EditorContent, type Extensions, useEditor} from '@tiptap/vue-3'
import {Node} from '@tiptap/pm/model'
import Commands from './commands' import Commands from './commands'
import suggestionSetup from './suggestion' import suggestionSetup from './suggestion'
@ -185,27 +172,14 @@ import type {ITask} from '@/modelTypes/ITask'
import type {IAttachment} from '@/modelTypes/IAttachment' import type {IAttachment} from '@/modelTypes/IAttachment'
import AttachmentModel from '@/models/attachment' import AttachmentModel from '@/models/attachment'
import AttachmentService from '@/services/attachment' import AttachmentService from '@/services/attachment'
import {useI18n} from 'vue-i18n'
import BaseButton from '@/components/base/BaseButton.vue' import BaseButton from '@/components/base/BaseButton.vue'
import XButton from '@/components/input/Button.vue' import XButton from '@/components/input/Button.vue'
import {Placeholder} from '@tiptap/extension-placeholder'
import {eventToHotkeyString} from '@github/hotkey'
import {Extension, mergeAttributes} from '@tiptap/core'
import {isEditorContentEmpty} from '@/helpers/editorContentEmpty' import {isEditorContentEmpty} from '@/helpers/editorContentEmpty'
import inputPrompt from '@/helpers/inputPrompt' import inputPrompt from '@/helpers/inputPrompt'
import {setLinkInEditor} from '@/components/input/editor/setLinkInEditor' import {setLinkInEditor} from '@/components/input/editor/setLinkInEditor'
const { const props = withDefaults(defineProps<{
modelValue,
uploadCallback,
isEditEnabled,
bottomActions,
showSave,
placeholder,
editShortcut,
enableDiscardShortcut,
} = withDefaults(defineProps<{
modelValue: string,
uploadCallback?: UploadCallback, uploadCallback?: UploadCallback,
isEditEnabled?: boolean, isEditEnabled?: boolean,
bottomActions?: BottomAction[], bottomActions?: BottomAction[],
@ -214,6 +188,7 @@ const {
editShortcut?: string, editShortcut?: string,
enableDiscardShortcut?: boolean, enableDiscardShortcut?: boolean,
}>(), { }>(), {
uploadCallback: undefined,
isEditEnabled: true, isEditEnabled: true,
bottomActions: () => [], bottomActions: () => [],
showSave: false, showSave: false,
@ -222,7 +197,9 @@ const {
enableDiscardShortcut: false, enableDiscardShortcut: false,
}) })
const emit = defineEmits(['update:modelValue', 'save']) const emit = defineEmits(['save'])
const modelValue = defineModel<string>({ default: '' })
const tiptapInstanceRef = ref<HTMLInputElement | null>(null) const tiptapInstanceRef = ref<HTMLInputElement | null>(null)
@ -287,7 +264,7 @@ const CustomImage = Image.extend({
nextTick(async () => { nextTick(async () => {
const img = document.getElementById(id) const img = document.getElementById(id) as HTMLImageElement | null
if (!img) return if (!img) return
@ -318,13 +295,13 @@ const CustomImage = Image.extend({
type Mode = 'edit' | 'preview' type Mode = 'edit' | 'preview'
const internalMode = ref<Mode>('preview') const internalMode = ref<Mode>('preview')
const isEditing = computed(() => internalMode.value === 'edit' && isEditEnabled) const isEditing = computed(() => internalMode.value === 'edit' && props.isEditEnabled)
const contentHasChanged = ref<boolean>(false) const contentHasChanged = ref<boolean>(false)
let lastSavedState = '' let lastSavedState = ''
watch( watch(
() => modelValue, modelValue,
(newValue) => { (newValue) => {
if (!contentHasChanged.value) { if (!contentHasChanged.value) {
lastSavedState = newValue lastSavedState = newValue
@ -334,7 +311,7 @@ watch(
) )
watch( watch(
() => internalMode.value, internalMode,
mode => { mode => {
if (mode === 'preview') { if (mode === 'preview') {
contentHasChanged.value = false contentHasChanged.value = false
@ -351,16 +328,14 @@ const additionalLinkProtocols = [
const extensions : Extensions = [ const extensions : Extensions = [
// Starterkit: // Starterkit:
Blockquote, StarterKit.configure({
Bold, codeBlock: false,
BulletList, hardBreak: false,
Code, }),
CodeBlockLowlight.configure({ CodeBlockLowlight.configure({
lowlight, lowlight,
}), }),
Document,
Dropcursor,
Gapcursor,
HardBreak.extend({ HardBreak.extend({
addKeyboardShortcuts() { addKeyboardShortcuts() {
return { return {
@ -374,36 +349,21 @@ const extensions : Extensions = [
} }
}, },
}), }),
Heading,
History,
HorizontalRule,
Italic,
ListItem,
OrderedList,
Paragraph,
Strike,
Text,
Placeholder.configure({ Placeholder.configure({
placeholder: ({editor}) => { placeholder({editor}) {
if (!isEditing.value) { if (!isEditing.value || editor.getText() !== '' && !editor.isFocused) {
return '' return ''
} }
if (editor.getText() !== '' && !editor.isFocused) { return props.placeholder || t('input.editor.placeholder')
return ''
}
return placeholder !== ''
? placeholder
: t('input.editor.placeholder')
}, },
}), }),
Typography, Typography,
Underline, Underline,
Link.configure({ Link.configure({
openOnClick: false, openOnClick: false,
validate: (href: string) => (new RegExp( validate: (href) => (new RegExp(
`^(https?|${additionalLinkProtocols.join('|')}):\\/\\/`, `^(https?|${additionalLinkProtocols.join('|')}):\\/\\/`,
'i', 'i',
)).test(href), )).test(href),
@ -422,8 +382,8 @@ const extensions : Extensions = [
TaskList, TaskList,
TaskItem.configure({ TaskItem.configure({
nested: true, nested: true,
onReadOnlyChecked: (node: Node, checked: boolean): boolean => { onReadOnlyChecked(node, checked) {
if (!isEditEnabled) { if (!props.isEditEnabled) {
return false return false
} }
@ -451,11 +411,10 @@ const extensions : Extensions = [
Commands.configure({ Commands.configure({
suggestion: suggestionSetup(t), suggestion: suggestionSetup(t),
}), }),
BubbleMenu,
] ]
// Add a custom extension for the Escape key // Add a custom extension for the Escape key
if (enableDiscardShortcut) { if (props.enableDiscardShortcut) {
extensions.push(Extension.create({ extensions.push(Extension.create({
name: 'escapeKey', name: 'escapeKey',
@ -474,21 +433,13 @@ const editor = useEditor({
// eslint-disable-next-line vue/no-ref-object-reactivity-loss // eslint-disable-next-line vue/no-ref-object-reactivity-loss
editable: isEditing.value, editable: isEditing.value,
extensions: extensions, extensions: extensions,
onUpdate: () => { onUpdate: bubbleNow,
bubbleNow()
},
}) })
watch( watchEffect(() => editor.value?.setEditable(isEditing.value))
() => isEditing.value,
() => {
editor.value?.setEditable(isEditing.value)
},
{immediate: true},
)
watch( watch(
() => modelValue, modelValue,
value => { value => {
if (!editor?.value) return if (!editor?.value) return
@ -502,13 +453,14 @@ watch(
) )
function bubbleNow() { function bubbleNow() {
if (editor.value?.getHTML() === modelValue || const editorVal = editor.value!.getHTML()
(editor.value?.getHTML() === '<p></p>') && modelValue === '') { if (editorVal === modelValue.value ||
(editorVal === '<p></p>') && modelValue.value === '') {
return return
} }
contentHasChanged.value = true contentHasChanged.value = true
emit('update:modelValue', editor.value?.getHTML()) modelValue.value = editorVal
} }
function bubbleSave() { function bubbleSave() {
@ -528,7 +480,7 @@ function exitEditMode() {
} }
function setEditIfApplicable() { function setEditIfApplicable() {
if (!isEditEnabled) return if (!props.isEditEnabled) return
if (isEditing.value) return if (isEditing.value) return
setEdit() setEdit()
@ -545,21 +497,20 @@ onBeforeUnmount(() => editor.value?.destroy())
const uploadInputRef = ref<HTMLInputElement | null>(null) const uploadInputRef = ref<HTMLInputElement | null>(null)
function uploadAndInsertFiles(files: File[] | FileList) { async function uploadAndInsertFiles(files: File[] | FileList) {
uploadCallback(files).then(urls => { const urls = await props.uploadCallback?.(files)
urls?.forEach(url => { urls?.forEach(url => {
editor.value editor.value
?.chain() ?.chain()
.focus() .focus()
.setImage({src: url}) .setImage({src: url})
.run() .run()
})
bubbleSave()
}) })
bubbleSave()
} }
function triggerImageInput(event) { function triggerImageInput(event) {
if (typeof uploadCallback !== 'undefined') { if (typeof props.uploadCallback !== 'undefined') {
uploadInputRef.value?.click() uploadInputRef.value?.click()
return return
} }
@ -569,7 +520,7 @@ function triggerImageInput(event) {
async function addImage(event) { async function addImage(event) {
if (typeof uploadCallback !== 'undefined') { if (typeof props.uploadCallback !== 'undefined') {
const files = uploadInputRef.value?.files const files = uploadInputRef.value?.files
if (!files || files.length === 0) { if (!files || files.length === 0) {
@ -594,28 +545,28 @@ function setLink(event) {
} }
onMounted(async () => { onMounted(async () => {
if (editShortcut !== '') { if (props.editShortcut !== '') {
document.addEventListener('keydown', setFocusToEditor) document.addEventListener('keydown', setFocusToEditor)
} }
await nextTick() await nextTick()
if (typeof uploadCallback !== 'undefined') { if (typeof props.uploadCallback !== 'undefined') {
const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0] const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0]
input?.addEventListener('paste', handleImagePaste) input?.addEventListener('paste', handleImagePaste)
} }
setModeAndValue(modelValue) setModeAndValue(modelValue.value)
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
nextTick(() => { nextTick(() => {
if (typeof uploadCallback !== 'undefined') { if (typeof props.uploadCallback !== 'undefined') {
const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0] const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0]
input?.removeEventListener('paste', handleImagePaste) input?.removeEventListener('paste', handleImagePaste)
} }
}) })
if (editShortcut !== '') { if (props.editShortcut !== '') {
document.removeEventListener('keydown', setFocusToEditor) document.removeEventListener('keydown', setFocusToEditor)
} }
}) })
@ -646,7 +597,7 @@ function setFocusToEditor(event) {
const hotkeyString = eventToHotkeyString(event) const hotkeyString = eventToHotkeyString(event)
if (!hotkeyString) return if (!hotkeyString) return
if (hotkeyString !== editShortcut || if (hotkeyString !== props.editShortcut ||
event.target.tagName.toLowerCase() === 'input' || event.target.tagName.toLowerCase() === 'input' ||
event.target.tagName.toLowerCase() === 'textarea' || event.target.tagName.toLowerCase() === 'textarea' ||
event.target.contentEditable === 'true') { event.target.contentEditable === 'true') {
@ -655,7 +606,7 @@ function setFocusToEditor(event) {
event.preventDefault() event.preventDefault()
if (!isEditing.value && isEditEnabled) { if (!isEditing.value && props.isEditEnabled) {
internalMode.value = 'edit' internalMode.value = 'edit'
} }
@ -679,7 +630,7 @@ function clickTasklistCheckbox(event) {
} }
watch( watch(
() => isEditing.value, isEditing,
async editing => { async editing => {
await nextTick() await nextTick()