feat(editor): add bubble menu
This commit is contained in:
parent
17c23d9463
commit
beefc1d5ef
@ -69,6 +69,7 @@
|
|||||||
"@tiptap/extension-task-item": "2.0.3",
|
"@tiptap/extension-task-item": "2.0.3",
|
||||||
"@tiptap/extension-task-list": "2.0.3",
|
"@tiptap/extension-task-list": "2.0.3",
|
||||||
"@tiptap/extension-typography": "2.0.3",
|
"@tiptap/extension-typography": "2.0.3",
|
||||||
|
"@tiptap/extension-underline": "^2.1.12",
|
||||||
"@tiptap/starter-kit": "2.0.3",
|
"@tiptap/starter-kit": "2.0.3",
|
||||||
"@tiptap/suggestion": "^2.1.12",
|
"@tiptap/suggestion": "^2.1.12",
|
||||||
"@tiptap/vue-3": "2.0.3",
|
"@tiptap/vue-3": "2.0.3",
|
||||||
|
11
pnpm-lock.yaml
generated
11
pnpm-lock.yaml
generated
@ -82,6 +82,9 @@ dependencies:
|
|||||||
'@tiptap/extension-typography':
|
'@tiptap/extension-typography':
|
||||||
specifier: 2.0.3
|
specifier: 2.0.3
|
||||||
version: 2.0.3(@tiptap/core@2.1.12)
|
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':
|
'@tiptap/starter-kit':
|
||||||
specifier: 2.0.3
|
specifier: 2.0.3
|
||||||
version: 2.0.3(@tiptap/pm@2.1.12)
|
version: 2.0.3(@tiptap/pm@2.1.12)
|
||||||
@ -4498,6 +4501,14 @@ packages:
|
|||||||
'@tiptap/core': 2.1.12(@tiptap/pm@2.1.12)
|
'@tiptap/core': 2.1.12(@tiptap/pm@2.1.12)
|
||||||
dev: false
|
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:
|
/@tiptap/pm@2.1.12:
|
||||||
resolution: {integrity: sha512-Q3MXXQABG4CZBesSp82yV84uhJh/W0Gag6KPm2HRWPimSFELM09Z9/5WK9RItAYE0aLhe4Krnyiczn9AAa1tQQ==}
|
resolution: {integrity: sha512-Q3MXXQABG4CZBesSp82yV84uhJh/W0Gag6KPm2HRWPimSFELM09Z9/5WK9RItAYE0aLhe4Krnyiczn9AAa1tQQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -5,6 +5,60 @@
|
|||||||
:editor="editor"
|
:editor="editor"
|
||||||
:upload-callback="uploadCallback"
|
: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
|
<editor-content
|
||||||
class="tiptap__editor"
|
class="tiptap__editor"
|
||||||
:editor="editor"
|
:editor="editor"
|
||||||
@ -42,6 +96,7 @@ import Highlight from '@tiptap/extension-highlight'
|
|||||||
import Typography from '@tiptap/extension-typography'
|
import Typography from '@tiptap/extension-typography'
|
||||||
import Document from '@tiptap/extension-document'
|
import Document from '@tiptap/extension-document'
|
||||||
import Image from '@tiptap/extension-image'
|
import Image from '@tiptap/extension-image'
|
||||||
|
import Underline from '@tiptap/extension-underline'
|
||||||
// import Text from '@tiptap/extension-text'
|
// import Text from '@tiptap/extension-text'
|
||||||
|
|
||||||
import TaskItem from '@tiptap/extension-task-item'
|
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 CharacterCount from '@tiptap/extension-character-count'
|
||||||
|
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
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 Commands from './commands'
|
||||||
import suggestionSetup from './suggestion'
|
import suggestionSetup from './suggestion'
|
||||||
@ -65,6 +120,7 @@ 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 {useI18n} from 'vue-i18n'
|
||||||
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
|
|
||||||
const {t} = useI18n()
|
const {t} = useI18n()
|
||||||
|
|
||||||
@ -181,6 +237,7 @@ const editor = useEditor({
|
|||||||
StarterKit,
|
StarterKit,
|
||||||
Highlight,
|
Highlight,
|
||||||
Typography,
|
Typography,
|
||||||
|
Underline,
|
||||||
Link.configure({
|
Link.configure({
|
||||||
openOnClick: false,
|
openOnClick: false,
|
||||||
validate: (href: string) => /^https?:\/\//.test(href),
|
validate: (href: string) => /^https?:\/\//.test(href),
|
||||||
@ -215,6 +272,7 @@ const editor = useEditor({
|
|||||||
Commands.configure({
|
Commands.configure({
|
||||||
suggestion: suggestionSetup(t),
|
suggestion: suggestionSetup(t),
|
||||||
}),
|
}),
|
||||||
|
BubbleMenu,
|
||||||
],
|
],
|
||||||
onUpdate: () => {
|
onUpdate: () => {
|
||||||
// HTML
|
// HTML
|
||||||
@ -256,7 +314,7 @@ function addImage() {
|
|||||||
uploadCallback(files).then(urls => {
|
uploadCallback(files).then(urls => {
|
||||||
urls.forEach(url => {
|
urls.forEach(url => {
|
||||||
editor.value
|
editor.value
|
||||||
.chain()
|
?.chain()
|
||||||
.focus()
|
.focus()
|
||||||
.setImage({src: url})
|
.setImage({src: url})
|
||||||
.run()
|
.run()
|
||||||
@ -270,10 +328,40 @@ function addImage() {
|
|||||||
const url = window.prompt('URL')
|
const url = window.prompt('URL')
|
||||||
|
|
||||||
if (url) {
|
if (url) {
|
||||||
editor.value.chain().focus().setImage({src: url}).run()
|
editor.value?.chain().focus().setImage({src: url}).run()
|
||||||
onImageAdded()
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@ -551,4 +639,32 @@ ul[data-type='taskList'] {
|
|||||||
color: #868e96;
|
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>
|
</style>
|
||||||
|
@ -73,7 +73,7 @@ import {
|
|||||||
faUnlink,
|
faUnlink,
|
||||||
faParagraph,
|
faParagraph,
|
||||||
faTable,
|
faTable,
|
||||||
faX, faArrowTurnDown, faListCheck, faXmark, faXmarksLines, faFont, faRulerHorizontal,
|
faX, faArrowTurnDown, faListCheck, faXmark, faXmarksLines, faFont, faRulerHorizontal, faUnderline,
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import {
|
import {
|
||||||
faBellSlash,
|
faBellSlash,
|
||||||
@ -185,6 +185,7 @@ library.add(faXmark)
|
|||||||
library.add(faXmarksLines)
|
library.add(faXmarksLines)
|
||||||
library.add(faFont)
|
library.add(faFont)
|
||||||
library.add(faRulerHorizontal)
|
library.add(faRulerHorizontal)
|
||||||
|
library.add(faUnderline)
|
||||||
|
|
||||||
// overwriting the wrong types
|
// overwriting the wrong types
|
||||||
export default FontAwesomeIcon as unknown as FontAwesomeIconFixedTypes
|
export default FontAwesomeIcon as unknown as FontAwesomeIconFixedTypes
|
Loading…
x
Reference in New Issue
Block a user