improve bubble menu

This commit is contained in:
Philipp Kühn
2021-03-30 12:45:56 +02:00
parent 0aa9408124
commit 86fa24231c
3 changed files with 65 additions and 77 deletions

View File

@@ -1,7 +1,11 @@
<template> <template>
<div style="position: relative"> <div style="position: relative">
<div ref="menu"> <div ref="menu">
menu <div v-if="editor">
<button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }">
bold
</button>
</div>
</div> </div>
<editor-content :editor="editor" /> <editor-content :editor="editor" />
</div> </div>
@@ -9,9 +13,7 @@
<script> <script>
import { Editor, EditorContent } from '@tiptap/vue-2' import { Editor, EditorContent } from '@tiptap/vue-2'
import Document from '@tiptap/extension-document' import { defaultExtensions } from '@tiptap/starter-kit'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import BubbleMenu from '@tiptap/extension-bubble-menu' import BubbleMenu from '@tiptap/extension-bubble-menu'
export default { export default {
@@ -28,9 +30,7 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document, ...defaultExtensions(),
Paragraph,
Text,
BubbleMenu.configure({ BubbleMenu.configure({
element: this.$refs.menu, element: this.$refs.menu,
}), }),

View File

@@ -3,68 +3,67 @@ import { EditorState, Plugin, PluginKey } from 'prosemirror-state'
import { EditorView } from 'prosemirror-view' import { EditorView } from 'prosemirror-view'
import { coordsAtPos } from './helpers' import { coordsAtPos } from './helpers'
interface BubbleMenuPluginOptions { export interface BubbleMenuPluginOptions {
editor: Editor; editor: Editor,
element: HTMLElement; element: HTMLElement,
keepInBounds: boolean; keepInBounds: boolean,
} }
class BubbleMenuView { export type BubbleMenuViewOptions = BubbleMenuPluginOptions & {
public options: BubbleMenuPluginOptions; view: EditorView,
}
public editorView: EditorView; export class BubbleMenuView {
public editor: Editor
public isActive = false; public element: HTMLElement
public left = 0; public keepInBounds = true
public bottom = 0; public view: EditorView
public top = 0; public isActive = false
public preventHide = false; public left = 0
public bottom = 0
public top = 0
public preventHide = false
constructor({ constructor({
options, editor,
editorView, element,
}: { keepInBounds,
options: BubbleMenuPluginOptions; view,
editorView: EditorView; }: BubbleMenuViewOptions) {
}) { this.editor = editor
this.options = { this.element = element
...{ this.keepInBounds = keepInBounds
element: null, this.view = view
keepInBounds: true, this.element.addEventListener('mousedown', this.mousedownHandler, { capture: true })
}, this.editor.on('focus', this.focusHandler)
...options, this.editor.on('blur', this.blurHandler)
}
this.editorView = editorView
this.render() this.render()
// this.options.element.addEventListener('mousedown', this.mousedownHandler, {
// capture: true,
// })
// this.options.editor.on('focus', this.focusHandler)
// this.options.editor.on('blur', this.blurHandler)
} }
// mousedownHandler = () => { mousedownHandler = () => {
// this.preventHide = true this.preventHide = true
// }; }
// focusHandler = () => { focusHandler = () => {
// this.update(this.options.editor.view) this.update(this.editor.view)
// }; }
// blurHandler = ({ event }: { event: FocusEvent }) => { blurHandler = ({ event }: { event: FocusEvent }) => {
// if (this.preventHide) { if (this.preventHide) {
// this.preventHide = false this.preventHide = false
// return return
// } }
// this.hide(event) this.hide(event)
// }; }
update(view: EditorView, oldState?: EditorState) { update(view: EditorView, oldState?: EditorState) {
const { state, composing } = view const { state, composing } = view
@@ -86,7 +85,7 @@ class BubbleMenuView {
const start = coordsAtPos(view, from) const start = coordsAtPos(view, from)
const end = coordsAtPos(view, to, true) const end = coordsAtPos(view, to, true)
const parent = this.options.element.offsetParent const parent = this.element.offsetParent
if (!parent) { if (!parent) {
this.hide() this.hide()
@@ -95,11 +94,11 @@ class BubbleMenuView {
} }
const parentBox = parent.getBoundingClientRect() const parentBox = parent.getBoundingClientRect()
const box = this.options.element.getBoundingClientRect() const box = this.element.getBoundingClientRect()
const left = (start.left + end.left) / 2 - parentBox.left const left = (start.left + end.left) / 2 - parentBox.left
this.left = Math.round( this.left = Math.round(
this.options.keepInBounds this.keepInBounds
? Math.min(parentBox.width - box.width / 2, Math.max(left, box.width / 2)) ? Math.min(parentBox.width - box.width / 2, Math.max(left, box.width / 2))
: left, : left,
) )
@@ -111,7 +110,7 @@ class BubbleMenuView {
} }
render() { render() {
Object.assign(this.options.element.style, { Object.assign(this.element.style, {
position: 'absolute', position: 'absolute',
zIndex: 1000, zIndex: 1000,
visibility: this.isActive ? 'visible' : 'hidden', visibility: this.isActive ? 'visible' : 'hidden',
@@ -126,8 +125,7 @@ class BubbleMenuView {
hide(event?: FocusEvent) { hide(event?: FocusEvent) {
if ( if (
event?.relatedTarget event?.relatedTarget
&& this.options.element.parentNode && this.element.parentNode?.contains(event.relatedTarget as Node)
&& this.options.element.parentNode.contains(event.relatedTarget as Node)
) { ) {
return return
} }
@@ -137,22 +135,15 @@ class BubbleMenuView {
} }
destroy() { destroy() {
// this.options.element.removeEventListener( this.element.removeEventListener('mousedown', this.mousedownHandler)
// 'mousedown', this.editor.off('focus', this.focusHandler)
// this.mousedownHandler, this.editor.off('blur', this.blurHandler)
// )
// this.options.editor.off('focus', this.focusHandler)
// this.options.editor.off('blur', this.blurHandler)
} }
} }
const BubbleMenuPlugin = (options: BubbleMenuPluginOptions) => { export const BubbleMenuPlugin = (options: BubbleMenuPluginOptions) => {
return new Plugin({ return new Plugin({
key: new PluginKey('menu_bubble'), key: new PluginKey('menuBubble'),
view(editorView) { view: view => new BubbleMenuView({ view, ...options }),
return new BubbleMenuView({ editorView, options })
},
}) })
} }
export { BubbleMenuPlugin }

View File

@@ -1,10 +1,7 @@
import { Extension } from '@tiptap/core' import { Extension } from '@tiptap/core'
import { BubbleMenuPlugin } from './bubble-menu-plugin' import { BubbleMenuPlugin, BubbleMenuPluginOptions } from './bubble-menu-plugin'
export interface BubbleMenuOptions { export type BubbleMenuOptions = Omit<BubbleMenuPluginOptions, 'editor'>
element: HTMLElement,
keepInBounds: boolean,
}
export const BubbleMenu = Extension.create<BubbleMenuOptions>({ export const BubbleMenu = Extension.create<BubbleMenuOptions>({
name: 'bubbleMenu', name: 'bubbleMenu',