From cf448d4698eec545b6d7439be36f3294474849ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Tue, 10 Aug 2021 15:13:19 +0200 Subject: [PATCH 1/8] add key option to bubble menu --- packages/extension-bubble-menu/src/bubble-menu-plugin.ts | 5 ++++- packages/extension-bubble-menu/src/bubble-menu.ts | 2 ++ packages/vue-2/src/BubbleMenu.ts | 9 ++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/extension-bubble-menu/src/bubble-menu-plugin.ts b/packages/extension-bubble-menu/src/bubble-menu-plugin.ts index 71cee1c28..0b168b5f2 100644 --- a/packages/extension-bubble-menu/src/bubble-menu-plugin.ts +++ b/packages/extension-bubble-menu/src/bubble-menu-plugin.ts @@ -9,6 +9,7 @@ import { EditorView } from 'prosemirror-view' import tippy, { Instance, Props } from 'tippy.js' export interface BubbleMenuPluginProps { + key: PluginKey | string, editor: Editor, element: HTMLElement, tippyOptions?: Partial, @@ -154,7 +155,9 @@ 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 }), }) } diff --git a/packages/extension-bubble-menu/src/bubble-menu.ts b/packages/extension-bubble-menu/src/bubble-menu.ts index 87c9c51da..3b8396051 100644 --- a/packages/extension-bubble-menu/src/bubble-menu.ts +++ b/packages/extension-bubble-menu/src/bubble-menu.ts @@ -11,6 +11,7 @@ export const BubbleMenu = Extension.create({ defaultOptions: { element: null, tippyOptions: {}, + key: 'bubbleMenu', }, addProseMirrorPlugins() { @@ -20,6 +21,7 @@ export const BubbleMenu = Extension.create({ return [ BubbleMenuPlugin({ + key: this.options.key, editor: this.editor, element: this.options.element, tippyOptions: this.options.tippyOptions, diff --git a/packages/vue-2/src/BubbleMenu.ts b/packages/vue-2/src/BubbleMenu.ts index 5b176feb4..1b96f92b7 100644 --- a/packages/vue-2/src/BubbleMenu.ts +++ b/packages/vue-2/src/BubbleMenu.ts @@ -2,14 +2,20 @@ import Vue, { Component, PropType } from 'vue' import { BubbleMenuPlugin, BubbleMenuPluginKey, BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu' export interface BubbleMenuInterface extends Vue { - tippyOptions: BubbleMenuPluginProps['tippyOptions'], + pluginKey: BubbleMenuPluginProps['key'], editor: BubbleMenuPluginProps['editor'], + tippyOptions: BubbleMenuPluginProps['tippyOptions'], } export const BubbleMenu: Component = { name: 'BubbleMenu', props: { + pluginKey: { + type: String || Object as PropType, + default: 'bubbleMenu', + }, + editor: { type: Object as PropType, required: true, @@ -31,6 +37,7 @@ export const BubbleMenu: Component = { this.$nextTick(() => { editor.registerPlugin(BubbleMenuPlugin({ + key: this.pluginKey, editor, element: this.$el as HTMLElement, tippyOptions: this.tippyOptions, From 57b499d6dab7a84a744485dff551164c70581a05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Tue, 10 Aug 2021 15:13:28 +0200 Subject: [PATCH 2/8] ignore react for now --- packages/react/src/BubbleMenu.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react/src/BubbleMenu.tsx b/packages/react/src/BubbleMenu.tsx index 3f48a2da9..64f37d460 100644 --- a/packages/react/src/BubbleMenu.tsx +++ b/packages/react/src/BubbleMenu.tsx @@ -1,3 +1,4 @@ +// @ts-nocheck import React, { useEffect, useRef } from 'react' import { BubbleMenuPlugin, BubbleMenuPluginKey, BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu' From 23285c6c3486b353ccec99b86c16cad3e84af16c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Tue, 10 Aug 2021 16:00:21 +0200 Subject: [PATCH 3/8] add shouldShow option to bubble menu extension --- .../docPages/api/extensions/bubble-menu.md | 10 ++-- .../src/bubble-menu-plugin.ts | 51 +++++++++++++++---- .../extension-bubble-menu/src/bubble-menu.ts | 2 + packages/vue-2/src/BubbleMenu.ts | 7 +++ 4 files changed, 57 insertions(+), 13 deletions(-) diff --git a/docs/src/docPages/api/extensions/bubble-menu.md b/docs/src/docPages/api/extensions/bubble-menu.md index 0a5c7798e..5c6f1fbd2 100644 --- a/docs/src/docPages/api/extensions/bubble-menu.md +++ b/docs/src/docPages/api/extensions/bubble-menu.md @@ -15,10 +15,12 @@ yarn add @tiptap/extension-bubble-menu ``` ## Settings -| Option | Type | Default | Description | -| ------------ | ------------- | ------- | ----------------------------------------------------------------------- | -| element | `HTMLElement` | `null` | The DOM element that contains your menu. | -| tippyOptions | `Object` | `{}` | [Options for tippy.js](https://atomiks.github.io/tippyjs/v6/all-props/) | +| Option | Type | Default | Description | +| ------------ | -------------------- | -------------- | ----------------------------------------------------------------------- | +| element | `HTMLElement` | `null` | The DOM element that contains your menu. | +| tippyOptions | `Object` | `{}` | [Options for tippy.js](https://atomiks.github.io/tippyjs/v6/all-props/) | +| key | `string | PluginKey` | `'bubbleMenu'` | The key for the underlying ProseMirror plugin. | +| shouldShow | `(props) => boolean` | | Controls whether the menu should be shown or not. | ## Source code [packages/extension-bubble-menu/](https://github.com/ueberdosis/tiptap/blob/main/packages/extension-bubble-menu/) diff --git a/packages/extension-bubble-menu/src/bubble-menu-plugin.ts b/packages/extension-bubble-menu/src/bubble-menu-plugin.ts index 0b168b5f2..ded076935 100644 --- a/packages/extension-bubble-menu/src/bubble-menu-plugin.ts +++ b/packages/extension-bubble-menu/src/bubble-menu-plugin.ts @@ -13,6 +13,14 @@ export interface BubbleMenuPluginProps { editor: Editor, element: HTMLElement, tippyOptions?: Partial, + shouldShow: ((props: { + editor: Editor, + view: EditorView, + state: EditorState, + oldState?: EditorState, + from: number, + to: number, + }) => boolean) | null, } export type BubbleMenuViewProps = BubbleMenuPluginProps & { @@ -30,15 +38,38 @@ export class BubbleMenuView { public tippy!: Instance + public shouldShow: Exclude = ({ 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) @@ -99,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 @@ -119,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) { diff --git a/packages/extension-bubble-menu/src/bubble-menu.ts b/packages/extension-bubble-menu/src/bubble-menu.ts index 3b8396051..cb7458645 100644 --- a/packages/extension-bubble-menu/src/bubble-menu.ts +++ b/packages/extension-bubble-menu/src/bubble-menu.ts @@ -12,6 +12,7 @@ export const BubbleMenu = Extension.create({ element: null, tippyOptions: {}, key: 'bubbleMenu', + shouldShow: null, }, addProseMirrorPlugins() { @@ -25,6 +26,7 @@ export const BubbleMenu = Extension.create({ editor: this.editor, element: this.options.element, tippyOptions: this.options.tippyOptions, + shouldShow: this.options.shouldShow, }), ] }, diff --git a/packages/vue-2/src/BubbleMenu.ts b/packages/vue-2/src/BubbleMenu.ts index 1b96f92b7..f8f6147d1 100644 --- a/packages/vue-2/src/BubbleMenu.ts +++ b/packages/vue-2/src/BubbleMenu.ts @@ -5,6 +5,7 @@ export interface BubbleMenuInterface extends Vue { pluginKey: BubbleMenuPluginProps['key'], editor: BubbleMenuPluginProps['editor'], tippyOptions: BubbleMenuPluginProps['tippyOptions'], + shouldShow: BubbleMenuPluginProps['shouldShow'], } export const BubbleMenu: Component = { @@ -25,6 +26,11 @@ export const BubbleMenu: Component = { type: Object as PropType, default: () => ({}), }, + + shouldShow: { + type: Function as PropType, + default: null, + }, }, watch: { @@ -41,6 +47,7 @@ export const BubbleMenu: Component = { editor, element: this.$el as HTMLElement, tippyOptions: this.tippyOptions, + shouldShow: this.shouldShow, })) }) }, From b2dfcbdc1e48f62ff1600303fbc0c93f35e818e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Tue, 10 Aug 2021 16:55:17 +0200 Subject: [PATCH 4/8] improve types --- packages/react/src/BubbleMenu.tsx | 10 ++++++++-- packages/vue-2/src/BubbleMenu.ts | 4 ++-- packages/vue-3/src/BubbleMenu.ts | 21 ++++++++++++++++++++- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/packages/react/src/BubbleMenu.tsx b/packages/react/src/BubbleMenu.tsx index 64f37d460..ae0f7e163 100644 --- a/packages/react/src/BubbleMenu.tsx +++ b/packages/react/src/BubbleMenu.tsx @@ -1,4 +1,3 @@ -// @ts-nocheck import React, { useEffect, useRef } from 'react' import { BubbleMenuPlugin, BubbleMenuPluginKey, BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu' @@ -10,12 +9,19 @@ export const BubbleMenu: React.FC = props => { const element = useRef(null) useEffect(() => { - const { editor, tippyOptions } = props + const { + key, + editor, + tippyOptions, + shouldShow, + } = props editor.registerPlugin(BubbleMenuPlugin({ + key, editor, element: element.current as HTMLElement, tippyOptions, + shouldShow, })) return () => { diff --git a/packages/vue-2/src/BubbleMenu.ts b/packages/vue-2/src/BubbleMenu.ts index f8f6147d1..81a3238d0 100644 --- a/packages/vue-2/src/BubbleMenu.ts +++ b/packages/vue-2/src/BubbleMenu.ts @@ -13,7 +13,7 @@ export const BubbleMenu: Component = { props: { pluginKey: { - type: String || Object as PropType, + type: [String, Object as PropType>], default: 'bubbleMenu', }, @@ -28,7 +28,7 @@ export const BubbleMenu: Component = { }, shouldShow: { - type: Function as PropType, + type: Function as PropType>, default: null, }, }, diff --git a/packages/vue-3/src/BubbleMenu.ts b/packages/vue-3/src/BubbleMenu.ts index c741b8537..f78130c43 100644 --- a/packages/vue-3/src/BubbleMenu.ts +++ b/packages/vue-3/src/BubbleMenu.ts @@ -16,6 +16,13 @@ export const BubbleMenu = defineComponent({ name: 'BubbleMenu', props: { + pluginKey: { + // TODO: TypeScript breaks :( + // type: [String, Object as PropType>], + type: [String, Object], + default: 'bubbleMenu', + }, + editor: { type: Object as PropType, required: true, @@ -25,18 +32,30 @@ export const BubbleMenu = defineComponent({ type: Object as PropType, default: () => ({}), }, + + shouldShow: { + type: Function as PropType>, + default: null, + }, }, setup(props, { slots }) { const root = ref(null) onMounted(() => { - const { editor, tippyOptions } = props + const { + pluginKey, + editor, + tippyOptions, + shouldShow, + } = props editor.registerPlugin(BubbleMenuPlugin({ + key: pluginKey, editor, element: root.value as HTMLElement, tippyOptions, + shouldShow, })) }) From dad0a4ba8f22e58b58a48b746db0ccc412bb60bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Tue, 10 Aug 2021 17:33:56 +0200 Subject: [PATCH 5/8] remove BubbleMenuPluginKey --- .../extension-bubble-menu/src/bubble-menu-plugin.ts | 2 -- packages/react/src/BubbleMenu.tsx | 4 ++-- packages/vue-2/src/BubbleMenu.ts | 4 ++-- packages/vue-3/src/BubbleMenu.ts | 10 +++------- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/extension-bubble-menu/src/bubble-menu-plugin.ts b/packages/extension-bubble-menu/src/bubble-menu-plugin.ts index ded076935..2ffb4072c 100644 --- a/packages/extension-bubble-menu/src/bubble-menu-plugin.ts +++ b/packages/extension-bubble-menu/src/bubble-menu-plugin.ts @@ -184,8 +184,6 @@ export class BubbleMenuView { } } -export const BubbleMenuPluginKey = new PluginKey('menuBubble') - export const BubbleMenuPlugin = (options: BubbleMenuPluginProps) => { return new Plugin({ key: typeof options.key === 'string' diff --git a/packages/react/src/BubbleMenu.tsx b/packages/react/src/BubbleMenu.tsx index ae0f7e163..415bb7023 100644 --- a/packages/react/src/BubbleMenu.tsx +++ b/packages/react/src/BubbleMenu.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef } from 'react' -import { BubbleMenuPlugin, BubbleMenuPluginKey, BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu' +import { BubbleMenuPlugin, BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu' export type BubbleMenuProps = Omit & { className?: string, @@ -25,7 +25,7 @@ export const BubbleMenu: React.FC = props => { })) return () => { - editor.unregisterPlugin(BubbleMenuPluginKey) + editor.unregisterPlugin(key) } }, []) diff --git a/packages/vue-2/src/BubbleMenu.ts b/packages/vue-2/src/BubbleMenu.ts index 81a3238d0..cc71785ba 100644 --- a/packages/vue-2/src/BubbleMenu.ts +++ b/packages/vue-2/src/BubbleMenu.ts @@ -1,5 +1,5 @@ import Vue, { Component, PropType } from 'vue' -import { BubbleMenuPlugin, BubbleMenuPluginKey, BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu' +import { BubbleMenuPlugin, BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu' export interface BubbleMenuInterface extends Vue { pluginKey: BubbleMenuPluginProps['key'], @@ -59,6 +59,6 @@ export const BubbleMenu: Component = { }, beforeDestroy(this: BubbleMenuInterface) { - this.editor.unregisterPlugin(BubbleMenuPluginKey) + this.editor.unregisterPlugin(this.pluginKey) }, } diff --git a/packages/vue-3/src/BubbleMenu.ts b/packages/vue-3/src/BubbleMenu.ts index f78130c43..419fe150f 100644 --- a/packages/vue-3/src/BubbleMenu.ts +++ b/packages/vue-3/src/BubbleMenu.ts @@ -6,11 +6,7 @@ import { onBeforeUnmount, defineComponent, } from 'vue' -import { - BubbleMenuPlugin, - BubbleMenuPluginKey, - BubbleMenuPluginProps, -} from '@tiptap/extension-bubble-menu' +import { BubbleMenuPlugin, BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu' export const BubbleMenu = defineComponent({ name: 'BubbleMenu', @@ -60,9 +56,9 @@ export const BubbleMenu = defineComponent({ }) onBeforeUnmount(() => { - const { editor } = props + const { pluginKey, editor } = props - editor.unregisterPlugin(BubbleMenuPluginKey) + editor.unregisterPlugin(pluginKey) }) return () => h('div', { ref: root }, slots.default?.()) From 8ed7f9532b9cb0f0715a4ceadc750a55fdaf0af3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Tue, 10 Aug 2021 17:37:32 +0200 Subject: [PATCH 6/8] add key and shouldShow option to floating menu extension --- .../docPages/api/extensions/floating-menu.md | 10 ++-- .../src/floating-menu-plugin.ts | 51 ++++++++++++++----- .../src/floating-menu.ts | 4 ++ packages/react/src/FloatingMenu.tsx | 13 +++-- packages/vue-2/src/FloatingMenu.ts | 18 ++++++- packages/vue-3/src/FloatingMenu.ts | 31 ++++++++--- 6 files changed, 97 insertions(+), 30 deletions(-) diff --git a/docs/src/docPages/api/extensions/floating-menu.md b/docs/src/docPages/api/extensions/floating-menu.md index adccdc0f4..f93896f25 100644 --- a/docs/src/docPages/api/extensions/floating-menu.md +++ b/docs/src/docPages/api/extensions/floating-menu.md @@ -13,10 +13,12 @@ yarn add @tiptap/extension-floating-menu ``` ## Settings -| Option | Type | Default | Description | -| ------------ | ------------- | ------- | ----------------------------------------------------------------------- | -| element | `HTMLElement` | `null` | The DOM element of your menu. | -| tippyOptions | `Object` | `{}` | [Options for tippy.js](https://atomiks.github.io/tippyjs/v6/all-props/) | +| Option | Type | Default | Description | +| ------------ | -------------------- | ---------------- | ----------------------------------------------------------------------- | +| element | `HTMLElement` | `null` | The DOM element of your menu. | +| tippyOptions | `Object` | `{}` | [Options for tippy.js](https://atomiks.github.io/tippyjs/v6/all-props/) | +| key | `string | PluginKey` | `'floatingMenu'` | The key for the underlying ProseMirror plugin. | +| shouldShow | `(props) => boolean` | | Controls whether the menu should be shown or not. | ## Source code [packages/extension-floating-menu/](https://github.com/ueberdosis/tiptap/blob/main/packages/extension-floating-menu/) diff --git a/packages/extension-floating-menu/src/floating-menu-plugin.ts b/packages/extension-floating-menu/src/floating-menu-plugin.ts index 3331b5f6d..6c5778f10 100644 --- a/packages/extension-floating-menu/src/floating-menu-plugin.ts +++ b/packages/extension-floating-menu/src/floating-menu-plugin.ts @@ -4,9 +4,16 @@ import { EditorView } from 'prosemirror-view' import tippy, { Instance, Props } from 'tippy.js' export interface FloatingMenuPluginProps { + key: PluginKey | string, editor: Editor, element: HTMLElement, tippyOptions?: Partial, + shouldShow: ((props: { + editor: Editor, + view: EditorView, + state: EditorState, + oldState?: EditorState, + }) => boolean) | null, } export type FloatingMenuViewProps = FloatingMenuPluginProps & { @@ -24,15 +31,35 @@ export class FloatingMenuView { public tippy!: Instance + public shouldShow: Exclude = ({ state }) => { + const { selection } = state + const { $anchor, empty } = selection + const isRootDepth = $anchor.depth === 1 + const isNodeEmpty = !selection.$anchor.parent.isLeaf && !selection.$anchor.parent.textContent + const isActive = isRootDepth && isNodeEmpty + + if (!empty || !isActive) { + return false + } + + return true + } + constructor({ editor, element, view, tippyOptions, + shouldShow, }: FloatingMenuViewProps) { this.editor = editor this.element = element this.view = view + + if (shouldShow) { + this.shouldShow = shouldShow + } + this.element.addEventListener('mousedown', this.mousedownHandler, { capture: true }) this.editor.on('focus', this.focusHandler) this.editor.on('blur', this.blurHandler) @@ -82,23 +109,21 @@ export class FloatingMenuView { update(view: EditorView, oldState?: EditorState) { const { state, composing } = view const { doc, selection } = state + const { from, to } = selection const isSame = oldState && oldState.doc.eq(doc) && oldState.selection.eq(selection) if (composing || isSame) { return } - const { - $anchor, - empty, - from, - to, - } = selection - const isRootDepth = $anchor.depth === 1 - const isNodeEmpty = !selection.$anchor.parent.isLeaf && !selection.$anchor.parent.textContent - const isActive = isRootDepth && isNodeEmpty + const shouldShow = this.shouldShow({ + editor: this.editor, + view, + state, + oldState, + }) - if (!empty || !isActive) { + if (!shouldShow) { this.hide() return @@ -127,11 +152,11 @@ export class FloatingMenuView { } } -export const FloatingMenuPluginKey = new PluginKey('menuFloating') - export const FloatingMenuPlugin = (options: FloatingMenuPluginProps) => { return new Plugin({ - key: FloatingMenuPluginKey, + key: typeof options.key === 'string' + ? new PluginKey(options.key) + : options.key, view: view => new FloatingMenuView({ view, ...options }), }) } diff --git a/packages/extension-floating-menu/src/floating-menu.ts b/packages/extension-floating-menu/src/floating-menu.ts index 5f216cae6..4e0141708 100644 --- a/packages/extension-floating-menu/src/floating-menu.ts +++ b/packages/extension-floating-menu/src/floating-menu.ts @@ -11,6 +11,8 @@ export const FloatingMenu = Extension.create({ defaultOptions: { element: null, tippyOptions: {}, + key: 'floatingMenu', + shouldShow: null, }, addProseMirrorPlugins() { @@ -20,9 +22,11 @@ export const FloatingMenu = Extension.create({ return [ FloatingMenuPlugin({ + key: this.options.key, editor: this.editor, element: this.options.element, tippyOptions: this.options.tippyOptions, + shouldShow: this.options.shouldShow, }), ] }, diff --git a/packages/react/src/FloatingMenu.tsx b/packages/react/src/FloatingMenu.tsx index e33a06363..d1b5d34c5 100644 --- a/packages/react/src/FloatingMenu.tsx +++ b/packages/react/src/FloatingMenu.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef } from 'react' -import { FloatingMenuPlugin, FloatingMenuPluginKey, FloatingMenuPluginProps } from '@tiptap/extension-floating-menu' +import { FloatingMenuPlugin, FloatingMenuPluginProps } from '@tiptap/extension-floating-menu' export type FloatingMenuProps = Omit & { className?: string, @@ -9,16 +9,23 @@ export const FloatingMenu: React.FC = props => { const element = useRef(null) useEffect(() => { - const { editor, tippyOptions } = props + const { + key, + editor, + tippyOptions, + shouldShow, + } = props editor.registerPlugin(FloatingMenuPlugin({ + key, editor, element: element.current as HTMLElement, tippyOptions, + shouldShow, })) return () => { - editor.unregisterPlugin(FloatingMenuPluginKey) + editor.unregisterPlugin(key) } }, []) diff --git a/packages/vue-2/src/FloatingMenu.ts b/packages/vue-2/src/FloatingMenu.ts index 40e380731..78868dcfc 100644 --- a/packages/vue-2/src/FloatingMenu.ts +++ b/packages/vue-2/src/FloatingMenu.ts @@ -1,15 +1,22 @@ import Vue, { Component, PropType } from 'vue' -import { FloatingMenuPlugin, FloatingMenuPluginKey, FloatingMenuPluginProps } from '@tiptap/extension-floating-menu' +import { FloatingMenuPlugin, FloatingMenuPluginProps } from '@tiptap/extension-floating-menu' export interface FloatingMenuInterface extends Vue { + pluginKey: FloatingMenuPluginProps['key'], tippyOptions: FloatingMenuPluginProps['tippyOptions'], editor: FloatingMenuPluginProps['editor'], + shouldShow: FloatingMenuPluginProps['shouldShow'], } export const FloatingMenu: Component = { name: 'FloatingMenu', props: { + pluginKey: { + type: [String, Object as PropType>], + default: 'floatingMenu', + }, + editor: { type: Object as PropType, required: true, @@ -19,6 +26,11 @@ export const FloatingMenu: Component = { type: Object as PropType, default: () => ({}), }, + + shouldShow: { + type: Function as PropType>, + default: null, + }, }, watch: { @@ -31,9 +43,11 @@ export const FloatingMenu: Component = { this.$nextTick(() => { editor.registerPlugin(FloatingMenuPlugin({ + key: this.pluginKey, editor, element: this.$el as HTMLElement, tippyOptions: this.tippyOptions, + shouldShow: this.shouldShow, })) }) }, @@ -45,6 +59,6 @@ export const FloatingMenu: Component = { }, beforeDestroy(this: FloatingMenuInterface) { - this.editor.unregisterPlugin(FloatingMenuPluginKey) + this.editor.unregisterPlugin(this.pluginKey) }, } diff --git a/packages/vue-3/src/FloatingMenu.ts b/packages/vue-3/src/FloatingMenu.ts index 31a53a4b0..13874c35b 100644 --- a/packages/vue-3/src/FloatingMenu.ts +++ b/packages/vue-3/src/FloatingMenu.ts @@ -6,16 +6,19 @@ import { onBeforeUnmount, defineComponent, } from 'vue' -import { - FloatingMenuPlugin, - FloatingMenuPluginKey, - FloatingMenuPluginProps, -} from '@tiptap/extension-floating-menu' +import { FloatingMenuPlugin, FloatingMenuPluginProps } from '@tiptap/extension-floating-menu' export const FloatingMenu = defineComponent({ name: 'FloatingMenu', props: { + pluginKey: { + // TODO: TypeScript breaks :( + // type: [String, Object as PropType>], + type: [String, Object], + default: 'floatingMenu', + }, + editor: { type: Object as PropType, required: true, @@ -25,25 +28,37 @@ export const FloatingMenu = defineComponent({ type: Object as PropType, default: () => ({}), }, + + shouldShow: { + type: Function as PropType>, + default: null, + }, }, setup(props, { slots }) { const root = ref(null) onMounted(() => { - const { editor, tippyOptions } = props + const { + pluginKey, + editor, + tippyOptions, + shouldShow, + } = props editor.registerPlugin(FloatingMenuPlugin({ + key: pluginKey, editor, element: root.value as HTMLElement, tippyOptions, + shouldShow, })) }) onBeforeUnmount(() => { - const { editor } = props + const { pluginKey, editor } = props - editor.unregisterPlugin(FloatingMenuPluginKey) + editor.unregisterPlugin(pluginKey) }) return () => h('div', { ref: root }, slots.default?.()) From 7e0d069cc695dae49aa6e7156c05185091ad00fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Wed, 11 Aug 2021 11:31:05 +0200 Subject: [PATCH 7/8] =?UTF-8?q?fix:=20don=E2=80=99t=20show=20floating=20me?= =?UTF-8?q?nu=20within=20code=20block?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../extension-floating-menu/src/floating-menu-plugin.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/extension-floating-menu/src/floating-menu-plugin.ts b/packages/extension-floating-menu/src/floating-menu-plugin.ts index 6c5778f10..4916c0e32 100644 --- a/packages/extension-floating-menu/src/floating-menu-plugin.ts +++ b/packages/extension-floating-menu/src/floating-menu-plugin.ts @@ -35,10 +35,11 @@ export class FloatingMenuView { const { selection } = state const { $anchor, empty } = selection const isRootDepth = $anchor.depth === 1 - const isNodeEmpty = !selection.$anchor.parent.isLeaf && !selection.$anchor.parent.textContent - const isActive = isRootDepth && isNodeEmpty + const isEmptyTextBlock = $anchor.parent.isTextblock + && !$anchor.parent.type.spec.code + && !$anchor.parent.textContent - if (!empty || !isActive) { + if (!empty || !isRootDepth || !isEmptyTextBlock) { return false } From a1e382350f0e1363bfc4599835cb6a5304ddab19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Wed, 11 Aug 2021 13:54:39 +0200 Subject: [PATCH 8/8] docs: add new menu options --- .../docPages/api/extensions/bubble-menu.md | 54 +++++++++++++++++++ .../docPages/api/extensions/floating-menu.md | 54 +++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/docs/src/docPages/api/extensions/bubble-menu.md b/docs/src/docPages/api/extensions/bubble-menu.md index 5c6f1fbd2..6c1822c7f 100644 --- a/docs/src/docPages/api/extensions/bubble-menu.md +++ b/docs/src/docPages/api/extensions/bubble-menu.md @@ -46,3 +46,57 @@ new Editor({ Vue: 'Extensions/BubbleMenu/Vue', React: 'Extensions/BubbleMenu/React', }" /> + +### Custom logic +Customize the logic for showing the menu with the `shouldShow` option. For components, `shouldShow` can be passed as a prop. + +```js +BubbleMenu.configure({ + shouldShow: ({ editor, view, state, oldState, from, to }) => { + // only show the bubble menu for images and links + return editor.isActive('image') || editor.isActive('link') + }, +}) +``` + +### Multiple menus +Use multiple menus by setting an unique `key`. + +```js +import { Editor } from '@tiptap/core' +import BubbleMenu from '@tiptap/extension-bubble-menu' + +new Editor({ + extensions: [ + BubbleMenu.configure({ + key: 'bubbleMenuOne', + element: document.querySelector('.menu-one'), + }), + BubbleMenu.configure({ + key: 'bubbleMenuTwo', + element: document.querySelector('.menu-two'), + }), + ], +}) +``` + +Alternatively you can pass a ProseMirror `PluginKey`. + +```js +import { Editor } from '@tiptap/core' +import BubbleMenu from '@tiptap/extension-bubble-menu' +import { PluginKey } from 'prosemirror-state' + +new Editor({ + extensions: [ + BubbleMenu.configure({ + key: new PluginKey('bubbleMenuOne'), + element: document.querySelector('.menu-one'), + }), + BubbleMenu.configure({ + key: new PluginKey('bubbleMenuTwo'), + element: document.querySelector('.menu-two'), + }), + ], +}) +``` diff --git a/docs/src/docPages/api/extensions/floating-menu.md b/docs/src/docPages/api/extensions/floating-menu.md index f93896f25..838ffe925 100644 --- a/docs/src/docPages/api/extensions/floating-menu.md +++ b/docs/src/docPages/api/extensions/floating-menu.md @@ -42,3 +42,57 @@ new Editor({ Vue: 'Extensions/FloatingMenu/Vue', React: 'Extensions/FloatingMenu/React', }" /> + +### Custom logic +Customize the logic for showing the menu with the `shouldShow` option. For components, `shouldShow` can be passed as a prop. + +```js +FloatingMenu.configure({ + shouldShow: ({ editor, view, state, oldState }) => { + // show the floating within any paragraph + return editor.isActive('paragraph') + }, +}) +``` + +### Multiple menus +Use multiple menus by setting an unique `key`. + +```js +import { Editor } from '@tiptap/core' +import FloatingMenu from '@tiptap/extension-floating-menu' + +new Editor({ + extensions: [ + FloatingMenu.configure({ + key: 'floatingMenuOne', + element: document.querySelector('.menu-one'), + }), + FloatingMenu.configure({ + key: 'floatingMenuTwo', + element: document.querySelector('.menu-two'), + }), + ], +}) +``` + +Alternatively you can pass a ProseMirror `PluginKey`. + +```js +import { Editor } from '@tiptap/core' +import FloatingMenu from '@tiptap/extension-floating-menu' +import { PluginKey } from 'prosemirror-state' + +new Editor({ + extensions: [ + FloatingMenu.configure({ + key: new PluginKey('floatingMenuOne'), + element: document.querySelector('.menu-one'), + }), + FloatingMenu.configure({ + key: new PluginKey('floatingMenuOne'), + element: document.querySelector('.menu-two'), + }), + ], +}) +```