feat(editor): add bubble menu
continuous-integration/drone/pr Build is failing Details

This commit is contained in:
kolaente 2023-10-21 14:02:53 +02:00
parent 17c23d9463
commit beefc1d5ef
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
4 changed files with 133 additions and 4 deletions

View File

@ -69,6 +69,7 @@
"@tiptap/extension-task-item": "2.0.3",
"@tiptap/extension-task-list": "2.0.3",
"@tiptap/extension-typography": "2.0.3",
"@tiptap/extension-underline": "^2.1.12",
"@tiptap/starter-kit": "2.0.3",
"@tiptap/suggestion": "^2.1.12",
"@tiptap/vue-3": "2.0.3",

View File

@ -82,6 +82,9 @@ dependencies:
'@tiptap/extension-typography':
specifier: 2.0.3
version: 2.0.3(@tiptap/core@2.1.12)
'@tiptap/extension-underline':
specifier: ^2.1.12
version: 2.1.12(@tiptap/core@2.1.12)
'@tiptap/starter-kit':
specifier: 2.0.3
version: 2.0.3(@tiptap/pm@2.1.12)
@ -4498,6 +4501,14 @@ packages:
'@tiptap/core': 2.1.12(@tiptap/pm@2.1.12)
dev: false
/@tiptap/extension-underline@2.1.12(@tiptap/core@2.1.12):
resolution: {integrity: sha512-NwwdhFT8gDD0VUNLQx85yFBhP9a8qg8GPuxlGzAP/lPTV8Ubh3vSeQ5N9k2ZF/vHlEvnugzeVCbmYn7wf8vn1g==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.1.12(@tiptap/pm@2.1.12)
dev: false
/@tiptap/pm@2.1.12:
resolution: {integrity: sha512-Q3MXXQABG4CZBesSp82yV84uhJh/W0Gag6KPm2HRWPimSFELM09Z9/5WK9RItAYE0aLhe4Krnyiczn9AAa1tQQ==}
dependencies:

View File

@ -5,6 +5,60 @@
:editor="editor"
:upload-callback="uploadCallback"
/>
<BubbleMenu
v-if="editor"
:editor="editor"
class="editor-bubble__wrapper"
>
<BaseButton
class="editor-bubble__button"
@click="editor.chain().focus().toggleBold().run()"
:class="{ 'is-active': editor.isActive('bold') }"
title="bold"
>
<icon :icon="['fa', 'fa-bold']"/>
</BaseButton>
<BaseButton
class="editor-bubble__button"
@click="editor.chain().focus().toggleItalic().run()"
:class="{ 'is-active': editor.isActive('italic') }"
title="italic"
>
<icon :icon="['fa', 'fa-italic']"/>
</BaseButton>
<BaseButton
class="editor-bubble__button"
@click="editor.chain().focus().toggleUnderline().run()"
:class="{ 'is-active': editor.isActive('underline') }"
title="italic"
>
<icon :icon="['fa', 'fa-underline']"/>
</BaseButton>
<BaseButton
class="editor-bubble__button"
@click="editor.chain().focus().toggleStrike().run()"
:class="{ 'is-active': editor.isActive('strike') }"
title="strike"
>
<icon :icon="['fa', 'fa-strikethrough']"/>
</BaseButton>
<BaseButton
class="editor-bubble__button"
@click="editor.chain().focus().toggleCode().run()"
:class="{ 'is-active': editor.isActive('code') }"
title="code"
>
<icon :icon="['fa', 'fa-code']"/>
</BaseButton>
<BaseButton
class="editor-bubble__button"
@click="setLink"
:class="{ 'is-active': editor.isActive('link') }"
title="set link"
>
<icon :icon="['fa', 'fa-link']"/>
</BaseButton>
</BubbleMenu>
<editor-content
class="tiptap__editor"
:editor="editor"
@ -42,6 +96,7 @@ import Highlight from '@tiptap/extension-highlight'
import Typography from '@tiptap/extension-typography'
import Document from '@tiptap/extension-document'
import Image from '@tiptap/extension-image'
import Underline from '@tiptap/extension-underline'
// import Text from '@tiptap/extension-text'
import TaskItem from '@tiptap/extension-task-item'
@ -50,7 +105,7 @@ import TaskList from '@tiptap/extension-task-list'
import CharacterCount from '@tiptap/extension-character-count'
import StarterKit from '@tiptap/starter-kit'
import {EditorContent, useEditor, VueNodeViewRenderer} from '@tiptap/vue-3'
import {BubbleMenu, EditorContent, useEditor} from '@tiptap/vue-3'
import Commands from './commands'
import suggestionSetup from './suggestion'
@ -65,6 +120,7 @@ import type {IAttachment} from '@/modelTypes/IAttachment'
import AttachmentModel from '@/models/attachment'
import AttachmentService from '@/services/attachment'
import {useI18n} from 'vue-i18n'
import BaseButton from '@/components/base/BaseButton.vue'
const {t} = useI18n()
@ -181,6 +237,7 @@ const editor = useEditor({
StarterKit,
Highlight,
Typography,
Underline,
Link.configure({
openOnClick: false,
validate: (href: string) => /^https?:\/\//.test(href),
@ -215,6 +272,7 @@ const editor = useEditor({
Commands.configure({
suggestion: suggestionSetup(t),
}),
BubbleMenu,
],
onUpdate: () => {
// HTML
@ -256,7 +314,7 @@ function addImage() {
uploadCallback(files).then(urls => {
urls.forEach(url => {
editor.value
.chain()
?.chain()
.focus()
.setImage({src: url})
.run()
@ -270,10 +328,40 @@ function addImage() {
const url = window.prompt('URL')
if (url) {
editor.value.chain().focus().setImage({src: url}).run()
editor.value?.chain().focus().setImage({src: url}).run()
onImageAdded()
}
}
function setLink() {
const previousUrl = editor.value?.getAttributes('link').href
const url = window.prompt('URL', previousUrl)
// cancelled
if (url === null) {
return
}
// empty
if (url === '') {
editor.value
?.chain()
.focus()
.extendMarkRange('link')
.unsetLink()
.run()
return
}
// update link
editor.value
?.chain()
.focus()
.extendMarkRange('link')
.setLink({href: url, target: '_blank'})
.run()
}
</script>
<style lang="scss">
@ -551,4 +639,32 @@ ul[data-type='taskList'] {
color: #868e96;
}
}
.editor-bubble__wrapper {
background: var(--white);
border-radius: $radius;
border: 1px solid var(--grey-200);
box-shadow: var(--shadow-md);
display: flex;
overflow: hidden;
}
.editor-bubble__button {
color: var(--grey-700);
transition: all $transition;
background: transparent;
svg {
box-sizing: border-box;
display: block;
width: 1rem;
height: 1rem;
padding: .5rem;
margin: 0;
}
&:hover {
background: var(--grey-200);
}
}
</style>

View File

@ -73,7 +73,7 @@ import {
faUnlink,
faParagraph,
faTable,
faX, faArrowTurnDown, faListCheck, faXmark, faXmarksLines, faFont, faRulerHorizontal,
faX, faArrowTurnDown, faListCheck, faXmark, faXmarksLines, faFont, faRulerHorizontal, faUnderline,
} from '@fortawesome/free-solid-svg-icons'
import {
faBellSlash,
@ -185,6 +185,7 @@ library.add(faXmark)
library.add(faXmarksLines)
library.add(faFont)
library.add(faRulerHorizontal)
library.add(faUnderline)
// overwriting the wrong types
export default FontAwesomeIcon as unknown as FontAwesomeIconFixedTypes