feat: add key option and shouldShow option to menus (fix #1480, fix #1043, fix #1268, fix #1503)

* add key option to bubble menu

* ignore react for now

* add shouldShow option to bubble menu extension

* improve types

* remove BubbleMenuPluginKey

* add key and shouldShow option to floating menu extension

* fix: don’t show floating menu within code block

* docs: add new menu options
This commit is contained in:
Philipp Kühn
2021-08-11 14:37:58 +02:00
committed by GitHub
parent bcc1309cd9
commit 9ba61c1582
12 changed files with 312 additions and 60 deletions

View File

@@ -9,9 +9,18 @@ import { EditorView } from 'prosemirror-view'
import tippy, { Instance, Props } from 'tippy.js'
export interface BubbleMenuPluginProps {
key: PluginKey | string,
editor: Editor,
element: HTMLElement,
tippyOptions?: Partial<Props>,
shouldShow: ((props: {
editor: Editor,
view: EditorView,
state: EditorState,
oldState?: EditorState,
from: number,
to: number,
}) => boolean) | null,
}
export type BubbleMenuViewProps = BubbleMenuPluginProps & {
@@ -29,15 +38,38 @@ export class BubbleMenuView {
public tippy!: Instance
public shouldShow: Exclude<BubbleMenuPluginProps['shouldShow'], null> = ({ state, from, to }) => {
const { doc, selection } = state
const { empty } = selection
// Sometime check for `empty` is not enough.
// Doubleclick an empty paragraph returns a node size of 2.
// So we check also for an empty text size.
const isEmptyTextBlock = !doc.textBetween(from, to).length
&& isTextSelection(state.selection)
if (empty || isEmptyTextBlock) {
return false
}
return true
}
constructor({
editor,
element,
view,
tippyOptions,
shouldShow,
}: BubbleMenuViewProps) {
this.editor = editor
this.element = element
this.view = view
if (shouldShow) {
this.shouldShow = shouldShow
}
this.element.addEventListener('mousedown', this.mousedownHandler, { capture: true })
this.view.dom.addEventListener('dragstart', this.dragstartHandler)
this.editor.on('focus', this.focusHandler)
@@ -98,19 +130,21 @@ export class BubbleMenuView {
return
}
const { empty, ranges } = selection
// support for CellSelections
const { ranges } = selection
const from = Math.min(...ranges.map(range => range.$from.pos))
const to = Math.max(...ranges.map(range => range.$to.pos))
// Sometime check for `empty` is not enough.
// Doubleclick an empty paragraph returns a node size of 2.
// So we check also for an empty text size.
const isEmptyTextBlock = !doc.textBetween(from, to).length
&& isTextSelection(view.state.selection)
const shouldShow = this.shouldShow({
editor: this.editor,
view,
state,
oldState,
from,
to,
})
if (empty || isEmptyTextBlock) {
if (!shouldShow) {
this.hide()
return
@@ -118,7 +152,7 @@ export class BubbleMenuView {
this.tippy.setProps({
getReferenceClientRect: () => {
if (isNodeSelection(view.state.selection)) {
if (isNodeSelection(state.selection)) {
const node = view.nodeDOM(from) as HTMLElement
if (node) {
@@ -150,11 +184,11 @@ export class BubbleMenuView {
}
}
export const BubbleMenuPluginKey = new PluginKey('menuBubble')
export const BubbleMenuPlugin = (options: BubbleMenuPluginProps) => {
return new Plugin({
key: BubbleMenuPluginKey,
key: typeof options.key === 'string'
? new PluginKey(options.key)
: options.key,
view: view => new BubbleMenuView({ view, ...options }),
})
}

View File

@@ -11,6 +11,8 @@ export const BubbleMenu = Extension.create<BubbleMenuOptions>({
defaultOptions: {
element: null,
tippyOptions: {},
key: 'bubbleMenu',
shouldShow: null,
},
addProseMirrorPlugins() {
@@ -20,9 +22,11 @@ export const BubbleMenu = Extension.create<BubbleMenuOptions>({
return [
BubbleMenuPlugin({
key: this.options.key,
editor: this.editor,
element: this.options.element,
tippyOptions: this.options.tippyOptions,
shouldShow: this.options.shouldShow,
}),
]
},