diff --git a/apps/client/package.json b/apps/client/package.json index 02c80183..ba98842d 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -55,7 +55,6 @@ "react-router-dom": "^7.0.1", "semver": "^7.7.2", "socket.io-client": "^4.8.1", - "tippy.js": "^6.3.7", "tiptap-extension-global-drag-handle": "^0.1.18", "zod": "^3.25.76" }, diff --git a/apps/client/src/features/editor/components/emoji-menu/render-emoji-items.ts b/apps/client/src/features/editor/components/emoji-menu/render-emoji-items.ts index 82fb24a9..0ae5e24a 100644 --- a/apps/client/src/features/editor/components/emoji-menu/render-emoji-items.ts +++ b/apps/client/src/features/editor/components/emoji-menu/render-emoji-items.ts @@ -1,16 +1,41 @@ import { ReactRenderer, useEditor } from "@tiptap/react"; import EmojiList from "./emoji-list"; -import tippy from "tippy.js"; import { init } from "emoji-mart"; +import { + autoUpdate, + computePosition, + flip, + offset, + shift, +} from "@floating-ui/dom"; const renderEmojiItems = () => { let component: ReactRenderer | null = null; - let popup: any | null = null; + let popup: HTMLDivElement | null = null; + let cleanup: (() => void) | null = null; + let getReferenceClientRect: (() => DOMRect) | null = null; + + const destroy = () => { + if (cleanup) { + cleanup(); + cleanup = null; + } + + if (popup) { + popup.remove(); + popup = null; + } + + if (component) { + component.destroy(); + component = null; + } + }; return { onBeforeStart: (props: { editor: ReturnType; - clientRect: DOMRect; + clientRect: () => DOMRect; }) => { init({ data: async () => (await import("@emoji-mart/data")).default, @@ -25,51 +50,61 @@ const renderEmojiItems = () => { return; } - // @ts-ignore - popup = tippy("body", { - getReferenceClientRect: props.clientRect, - appendTo: () => document.body, - content: component.element, - showOnCreate: true, - interactive: true, - trigger: "manual", - placement: "bottom", + getReferenceClientRect = props.clientRect; + popup = document.createElement("div"); + popup.style.zIndex = "9999"; + popup.style.position = "absolute"; + popup.style.top = "0"; + popup.style.left = "0"; + popup.appendChild(component.element); + document.body.appendChild(popup); + + const virtualElement = { + getBoundingClientRect: () => { + return getReferenceClientRect + ? getReferenceClientRect() + : new DOMRect(0, 0, 0, 0); + }, + }; + + cleanup = autoUpdate(virtualElement, popup, () => { + if (!popup) return; + + computePosition(virtualElement, popup, { + placement: "bottom-start", + middleware: [offset(10), flip(), shift()], + }).then(({ x, y }) => { + if (!popup) return; + + Object.assign(popup.style, { + transform: `translate(${x}px, ${y}px)`, + }); + }); }); }, onStart: (props: { editor: ReturnType; - clientRect: DOMRect; + clientRect: () => DOMRect; }) => { - component?.updateProps({...props, isLoading: false}); + component?.updateProps({ ...props, isLoading: false }); - if (!props.clientRect) { - return; + if (props.clientRect) { + getReferenceClientRect = props.clientRect; } - - popup && - popup[0].setProps({ - getReferenceClientRect: props.clientRect, - }); }, onUpdate: (props: { editor: ReturnType; - clientRect: DOMRect; + clientRect: () => DOMRect; }) => { component?.updateProps(props); - if (!props.clientRect) { - return; + if (props.clientRect) { + getReferenceClientRect = props.clientRect; } - - popup && - popup[0].setProps({ - getReferenceClientRect: props.clientRect, - }); }, onKeyDown: (props: { event: KeyboardEvent }) => { if (props.event.key === "Escape") { - popup?.[0].hide(); - component?.destroy() + destroy(); return true; } @@ -78,13 +113,7 @@ const renderEmojiItems = () => { return component?.ref?.onKeyDown(props); }, onExit: () => { - if (popup && !popup[0]?.state.isDestroyed) { - popup[0]?.destroy(); - } - - if (component) { - component?.destroy(); - } + destroy(); }, }; }; diff --git a/apps/client/src/features/editor/components/mention/mention-suggestion.ts b/apps/client/src/features/editor/components/mention/mention-suggestion.ts index fe2adf26..7c7408ad 100644 --- a/apps/client/src/features/editor/components/mention/mention-suggestion.ts +++ b/apps/client/src/features/editor/components/mention/mention-suggestion.ts @@ -1,5 +1,11 @@ import { ReactRenderer, useEditor } from "@tiptap/react"; -import tippy from "tippy.js"; +import { + autoUpdate, + computePosition, + flip, + offset, + shift, +} from "@floating-ui/dom"; import MentionList from "@/features/editor/components/mention/mention-list.tsx"; function getWhitespaceCount(query: string) { @@ -9,16 +15,27 @@ function getWhitespaceCount(query: string) { const mentionRenderItems = () => { let component: ReactRenderer | null = null; - let popup: any | null = null; + let activeClientRect: (() => DOMRect) | null = null; + let updatePositionCleanup: (() => void) | null = null; + + const destroy = () => { + updatePositionCleanup?.(); + updatePositionCleanup = null; + component?.destroy(); + if (component?.element?.parentNode) { + component.element.parentNode.removeChild(component.element); + } + component = null; + }; return { onStart: (props: { editor: ReturnType; - clientRect: DOMRect; + clientRect: () => DOMRect; query: string; }) => { // query must not start with a whitespace - if (props.query.charAt(0) === ' '){ + if (props.query.charAt(0) === " ") { return; } @@ -37,37 +54,63 @@ const mentionRenderItems = () => { return; } - // @ts-ignore - popup = tippy("body", { - getReferenceClientRect: props.clientRect, - appendTo: () => document.body, - content: component.element, - showOnCreate: true, - interactive: true, - trigger: "manual", - placement: "bottom-start", - }); + activeClientRect = props.clientRect; + + const { element } = component; + document.body.appendChild(element); + + updatePositionCleanup = autoUpdate( + { + getBoundingClientRect: () => + activeClientRect ? activeClientRect() : new DOMRect(), + }, + element, + () => { + if (!component?.element) return; + computePosition( + { + getBoundingClientRect: () => { + return activeClientRect ? activeClientRect() : new DOMRect(); + }, + }, + element, + { + placement: "bottom-start", + middleware: [offset(0), flip(), shift()], + } + ).then(({ x, y }) => { + Object.assign(element.style, { + left: `${x}px`, + top: `${y}px`, + position: "absolute", + zIndex: "9999", + }); + }); + } + ); }, onUpdate: (props: { editor: ReturnType; - clientRect: DOMRect; + clientRect: () => DOMRect; query: string; }) => { // query must not start with a whitespace - if (props.query.charAt(0) === ' '){ - component?.destroy(); + if (props.query.charAt(0) === " ") { + destroy(); return; } // only update component if popup is not destroyed - if (!popup?.[0].state.isDestroyed) { - component?.updateProps(props); + if (component) { + component.updateProps(props); } if (!props || !props.clientRect) { return; } + activeClientRect = props.clientRect; + const whitespaceCount = getWhitespaceCount(props.query); // destroy component if space is greater 3 without a match @@ -76,37 +119,23 @@ const mentionRenderItems = () => { //@ts-ignore props.editor.storage.mentionItems.length === 0 ) { - popup?.[0]?.destroy(); - component?.destroy(); + destroy(); return; } - - popup && - !popup?.[0].state.isDestroyed && - popup?.[0].setProps({ - getReferenceClientRect: props.clientRect, - }); }, onKeyDown: (props: { event: KeyboardEvent }) => { if (props.event.key) if ( props.event.key === "Escape" || - (props.event.key === "Enter" && !popup?.[0].state.isShown) + (props.event.key === "Enter" && !component) ) { - popup?.[0].destroy(); - component?.destroy(); + destroy(); return false; } return (component?.ref as any)?.onKeyDown(props); }, onExit: () => { - if (popup && !popup?.[0].state.isDestroyed) { - popup[0].destroy(); - } - - if (component) { - component.destroy(); - } + destroy(); }, }; }; diff --git a/apps/client/src/features/editor/components/slash-menu/render-items.ts b/apps/client/src/features/editor/components/slash-menu/render-items.ts index db6424e8..057e8214 100644 --- a/apps/client/src/features/editor/components/slash-menu/render-items.ts +++ b/apps/client/src/features/editor/components/slash-menu/render-items.ts @@ -1,10 +1,35 @@ import { ReactRenderer, useEditor } from "@tiptap/react"; import CommandList from "@/features/editor/components/slash-menu/command-list"; -import tippy from "tippy.js"; +import { + autoUpdate, + computePosition, + flip, + offset, + shift, +} from "@floating-ui/dom"; const renderItems = () => { let component: ReactRenderer | null = null; - let popup: any | null = null; + let popup: HTMLElement | null = null; + let cleanup: (() => void) | null = null; + let getReferenceClientRect: (() => DOMRect) | null = null; + + const updatePosition = () => { + if (!popup || !getReferenceClientRect) return; + + // @ts-ignore + const rect = getReferenceClientRect(); + + computePosition({ getBoundingClientRect: () => rect }, popup, { + placement: "bottom-start", + middleware: [offset(0), flip(), shift()], + }).then(({ x, y }) => { + if (popup) { + popup.style.left = `${x}px`; + popup.style.top = `${y}px`; + } + }); + }; return { onStart: (props: { @@ -21,15 +46,29 @@ const renderItems = () => { } // @ts-ignore - popup = tippy("body", { - getReferenceClientRect: props.clientRect, - appendTo: () => document.body, - content: component.element, - showOnCreate: true, - interactive: true, - trigger: "manual", - placement: "bottom-start", - }); + getReferenceClientRect = props.clientRect; + + popup = document.createElement("div"); + popup.style.zIndex = "9999"; + popup.style.position = "absolute"; + popup.style.top = "0"; + popup.style.left = "0"; + + document.body.appendChild(popup); + popup.appendChild(component.element); + + cleanup = autoUpdate( + // @ts-ignore + { + getBoundingClientRect: () => { + return getReferenceClientRect + ? getReferenceClientRect() + : new DOMRect(); + }, + }, + popup, + updatePosition + ); }, onUpdate: (props: { editor: ReturnType; @@ -41,14 +80,15 @@ const renderItems = () => { return; } - popup && - popup[0].setProps({ - getReferenceClientRect: props.clientRect, - }); + // @ts-ignore + getReferenceClientRect = props.clientRect; + updatePosition(); }, onKeyDown: (props: { event: KeyboardEvent }) => { if (props.event.key === "Escape") { - popup?.[0].hide(); + if (popup) { + popup.style.display = "none"; + } return true; } @@ -57,12 +97,19 @@ const renderItems = () => { return component?.ref?.onKeyDown(props); }, onExit: () => { - if (popup && !popup[0].state.isDestroyed) { - popup[0].destroy(); + if (cleanup) { + cleanup(); + cleanup = null; + } + + if (popup) { + popup.remove(); + popup = null; } if (component) { component.destroy(); + component = null; } }, }; diff --git a/apps/client/src/features/editor/components/subpages/subpages-menu.tsx b/apps/client/src/features/editor/components/subpages/subpages-menu.tsx index eb9bfd9d..9f0544e6 100644 --- a/apps/client/src/features/editor/components/subpages/subpages-menu.tsx +++ b/apps/client/src/features/editor/components/subpages/subpages-menu.tsx @@ -29,7 +29,7 @@ export const SubpagesMenu = React.memo( return editor.isActive("subpages"); }, - [editor], + [editor] ); const getReferenceClientRect = useCallback(() => { @@ -60,16 +60,6 @@ export const SubpagesMenu = React.memo( editor={editor} pluginKey={`subpages-menu`} updateDelay={0} - /* tippyOptions={{ - getReferenceClientRect, - offset: [0, 8], - zIndex: 99, - popperOptions: { - modifiers: [{ name: "flip", enabled: false }], - }, - plugins: [sticky], - sticky: "popper", - }}*/ shouldShow={shouldShow} > @@ -85,7 +75,7 @@ export const SubpagesMenu = React.memo( ); - }, + } ); export default SubpagesMenu; diff --git a/apps/client/src/features/editor/components/table/table-cell-menu.tsx b/apps/client/src/features/editor/components/table/table-cell-menu.tsx index 4f56c764..8af896b3 100644 --- a/apps/client/src/features/editor/components/table/table-cell-menu.tsx +++ b/apps/client/src/features/editor/components/table/table-cell-menu.tsx @@ -3,7 +3,7 @@ import { EditorMenuProps, ShouldShowProps, } from "@/features/editor/components/table/types/types.ts"; -import { isCellSelection, TiptapTippyBubbleMenu } from '@docmost/editor-ext'; +import { isCellSelection } from "@docmost/editor-ext"; import { ActionIcon, Tooltip } from "@mantine/core"; import { IconBoxMargin, @@ -15,6 +15,7 @@ import { import { useTranslation } from "react-i18next"; import { TableBackgroundColor } from "./table-background-color"; import { TableTextAlignment } from "./table-text-alignment"; +import { BubbleMenu } from "@tiptap/react/menus"; export const TableCellMenu = React.memo( ({ editor, appendTo }: EditorMenuProps): JSX.Element => { @@ -27,7 +28,7 @@ export const TableCellMenu = React.memo( return isCellSelection(state.selection); }, - [editor], + [editor] ); const mergeCells = useCallback(() => { @@ -51,16 +52,20 @@ export const TableCellMenu = React.memo( }, [editor]); return ( - { - return appendTo?.current; + appendTo={() => { + return appendTo?.current; + }} + ref={(element) => { + element.style.zIndex = "99"; + }} + options={{ + offset: { + mainAxis: 15, }, - offset: [0, 15], - zIndex: 99, }} shouldShow={shouldShow} > @@ -123,9 +128,9 @@ export const TableCellMenu = React.memo( - + ); - }, + } ); export default TableCellMenu; diff --git a/apps/client/src/features/editor/components/table/table-menu.tsx b/apps/client/src/features/editor/components/table/table-menu.tsx index 2cfe63c9..e54a06af 100644 --- a/apps/client/src/features/editor/components/table/table-menu.tsx +++ b/apps/client/src/features/editor/components/table/table-menu.tsx @@ -17,7 +17,8 @@ import { IconTableRow, IconTrashX, } from "@tabler/icons-react"; -import { isCellSelection, TiptapTippyBubbleMenu } from "@docmost/editor-ext"; +import { BubbleMenu } from "@tiptap/react/menus"; +import { isCellSelection } from "@docmost/editor-ext"; import { useTranslation } from "react-i18next"; export const TableMenu = React.memo( @@ -31,20 +32,28 @@ export const TableMenu = React.memo( return editor.isActive("table") && !isCellSelection(state.selection); }, - [editor], + [editor] ); - const getReferenceClientRect = useCallback(() => { + const getReferencedVirtualElement = useCallback(() => { const { selection } = editor.state; const predicate = (node: PMNode) => node.type.name === "table"; const parent = findParentNode(predicate)(selection); if (parent) { const dom = editor.view.nodeDOM(parent?.pos) as HTMLElement; - return dom.getBoundingClientRect(); + const rect = dom.getBoundingClientRect(); + return { + getBoundingClientRect: () => rect, + getClientRects: () => [rect], + }; } - return posToDOMRect(editor.view, selection.from, selection.to); + const rect = posToDOMRect(editor.view, selection.from, selection.to); + return { + getBoundingClientRect: () => rect, + getClientRects: () => [rect], + }; }, [editor]); const toggleHeaderColumn = useCallback(() => { @@ -84,35 +93,27 @@ export const TableMenu = React.memo( }, [editor]); return ( - { + element.style.zIndex = "99"; + }} + options={{ + placement: "top", + offset: { + mainAxis: 15, + }, + flip: { + fallbackPlacements: ["top", "bottom"], + padding: { top: 35 + 15, left: 8, right: 8, bottom: -Infinity }, + boundary: editor.options.element as HTMLElement, + }, + shift: { + padding: 8 + 15, + crossAxis: true, }, }} shouldShow={shouldShow} @@ -218,9 +219,9 @@ export const TableMenu = React.memo( - + ); - }, + } ); export default TableMenu; diff --git a/packages/editor-ext/src/index.ts b/packages/editor-ext/src/index.ts index d26592a2..3ff99083 100644 --- a/packages/editor-ext/src/index.ts +++ b/packages/editor-ext/src/index.ts @@ -23,4 +23,3 @@ export * from "./lib/subpages"; export * from "./lib/highlight"; export * from "./lib/heading/heading"; export * from "./lib/unique-id"; -export * from "./lib/tippy-bubble-menu"; diff --git a/packages/editor-ext/src/lib/tippy-bubble-menu/bubble-menu-plugin.ts b/packages/editor-ext/src/lib/tippy-bubble-menu/bubble-menu-plugin.ts deleted file mode 100644 index f9d6d092..00000000 --- a/packages/editor-ext/src/lib/tippy-bubble-menu/bubble-menu-plugin.ts +++ /dev/null @@ -1,349 +0,0 @@ -// Source: https://github.com/ueberdosis/tiptap/blob/v2/packages/extension-bubble-menu/src/bubble-menu-plugin.ts - MIT -import { - Editor, - isNodeSelection, - isTextSelection, - posToDOMRect, -} from "@tiptap/core"; -import { EditorState, Plugin, PluginKey } from "@tiptap/pm/state"; -import { EditorView } from "@tiptap/pm/view"; -import tippy, { Instance, Props } from "tippy.js"; - -export interface BubbleMenuPluginProps { - /** - * The plugin key. - * @type {PluginKey | string} - * @default 'bubbleMenu' - */ - pluginKey: PluginKey | string; - - /** - * The editor instance. - */ - editor: Editor; - - /** - * The DOM element that contains your menu. - * @type {HTMLElement} - * @default null - */ - element: HTMLElement; - - /** - * The options for the tippy.js instance. - * @see https://atomiks.github.io/tippyjs/v6/all-props/ - */ - tippyOptions?: Partial; - - /** - * The delay in milliseconds before the menu should be updated. - * This can be useful to prevent performance issues. - * @type {number} - * @default 250 - */ - updateDelay?: number; - - /** - * A function that determines whether the menu should be shown or not. - * If this function returns `false`, the menu will be hidden, otherwise it will be shown. - */ - shouldShow?: - | ((props: { - editor: Editor; - element: HTMLElement; - view: EditorView; - state: EditorState; - oldState?: EditorState; - from: number; - to: number; - }) => boolean) - | null; -} - -export type BubbleMenuViewProps = BubbleMenuPluginProps & { - view: EditorView; -}; - -export class BubbleMenuView { - public editor: Editor; - - public element: HTMLElement; - - public view: EditorView; - - public preventHide = false; - - public tippy: Instance | undefined; - - public tippyOptions?: Partial; - - public updateDelay: number; - - private updateDebounceTimer: number | undefined; - - public shouldShow: Exclude = ({ - view, - 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); - - // When clicking on a element inside the bubble menu the editor "blur" event - // is called and the bubble menu item is focussed. In this case we should - // consider the menu as part of the editor and keep showing the menu - const isChildOfMenu = this.element.contains(document.activeElement); - - const hasEditorFocus = view.hasFocus() || isChildOfMenu; - - if ( - !hasEditorFocus || - empty || - isEmptyTextBlock || - !this.editor.isEditable - ) { - return false; - } - - return true; - }; - - constructor({ - editor, - element, - view, - tippyOptions = {}, - updateDelay = 250, - shouldShow, - }: BubbleMenuViewProps) { - this.editor = editor; - this.element = element; - this.view = view; - this.updateDelay = updateDelay; - - 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); - this.editor.on("blur", this.blurHandler); - this.tippyOptions = tippyOptions; - // Detaches menu content from its current parent - this.element.remove(); - this.element.style.visibility = "visible"; - } - - mousedownHandler = () => { - this.preventHide = true; - }; - - dragstartHandler = () => { - this.hide(); - }; - - focusHandler = () => { - // we use `setTimeout` to make sure `selection` is already updated - setTimeout(() => this.update(this.editor.view)); - }; - - blurHandler = ({ event }: { event: FocusEvent }) => { - if (this.preventHide) { - this.preventHide = false; - - return; - } - - if ( - event?.relatedTarget && - this.element.parentNode?.contains(event.relatedTarget as Node) - ) { - return; - } - - if (event?.relatedTarget === this.editor.view.dom) { - return; - } - - this.hide(); - }; - - tippyBlurHandler = (event: FocusEvent) => { - this.blurHandler({ event }); - }; - - createTooltip() { - const { element: editorElement } = this.editor.options; - //@ts-ignore - const editorIsAttached = !!editorElement.parentElement; - - this.element.tabIndex = 0; - - if (this.tippy || !editorIsAttached) { - return; - } - - //@ts-ignore - this.tippy = tippy(editorElement, { - duration: 0, - getReferenceClientRect: null, - content: this.element, - interactive: true, - trigger: "manual", - placement: "top", - hideOnClick: "toggle", - ...this.tippyOptions, - }); - - // maybe we have to hide tippy on its own blur event as well - if (this.tippy.popper.firstChild) { - (this.tippy.popper.firstChild as HTMLElement).addEventListener( - "blur", - this.tippyBlurHandler, - ); - } - } - - update(view: EditorView, oldState?: EditorState) { - const { state } = view; - const hasValidSelection = state.selection.from !== state.selection.to; - - if (this.updateDelay > 0 && hasValidSelection) { - this.handleDebouncedUpdate(view, oldState); - return; - } - - const selectionChanged = !oldState?.selection.eq(view.state.selection); - const docChanged = !oldState?.doc.eq(view.state.doc); - - this.updateHandler(view, selectionChanged, docChanged, oldState); - } - - handleDebouncedUpdate = (view: EditorView, oldState?: EditorState) => { - const selectionChanged = !oldState?.selection.eq(view.state.selection); - const docChanged = !oldState?.doc.eq(view.state.doc); - - if (!selectionChanged && !docChanged) { - return; - } - - if (this.updateDebounceTimer) { - clearTimeout(this.updateDebounceTimer); - } - - this.updateDebounceTimer = window.setTimeout(() => { - this.updateHandler(view, selectionChanged, docChanged, oldState); - }, this.updateDelay); - }; - - updateHandler = ( - view: EditorView, - selectionChanged: boolean, - docChanged: boolean, - oldState?: EditorState, - ) => { - const { state, composing } = view; - const { selection } = state; - - const isSame = !selectionChanged && !docChanged; - - if (composing || isSame) { - return; - } - - this.createTooltip(); - - // 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)); - - const shouldShow = this.shouldShow?.({ - editor: this.editor, - element: this.element, - view, - state, - oldState, - from, - to, - }); - - if (!shouldShow) { - this.hide(); - - return; - } - - this.tippy?.setProps({ - getReferenceClientRect: - this.tippyOptions?.getReferenceClientRect || - (() => { - if (isNodeSelection(state.selection)) { - let node = view.nodeDOM(from) as HTMLElement; - - if (node) { - const nodeViewWrapper = node.dataset.nodeViewWrapper - ? node - : node.querySelector("[data-node-view-wrapper]"); - - if (nodeViewWrapper) { - node = nodeViewWrapper.firstChild as HTMLElement; - } - - if (node) { - return node.getBoundingClientRect(); - } - } - } - - return posToDOMRect(view, from, to); - }), - }); - - this.show(); - }; - - show() { - this.tippy?.show(); - } - - hide() { - this.tippy?.hide(); - } - - destroy() { - if (this.tippy?.popper.firstChild) { - (this.tippy.popper.firstChild as HTMLElement).removeEventListener( - "blur", - this.tippyBlurHandler, - ); - } - this.tippy?.destroy(); - this.element.removeEventListener("mousedown", this.mousedownHandler, { - capture: true, - }); - this.view.dom.removeEventListener("dragstart", this.dragstartHandler); - this.editor.off("focus", this.focusHandler); - this.editor.off("blur", this.blurHandler); - } -} - -export const BubbleMenuPlugin = (options: BubbleMenuPluginProps) => { - return new Plugin({ - key: - typeof options.pluginKey === "string" - ? new PluginKey(options.pluginKey) - : options.pluginKey, - view: (view) => new BubbleMenuView({ view, ...options }), - }); -}; diff --git a/packages/editor-ext/src/lib/tippy-bubble-menu/bubble-menu-react.tsx b/packages/editor-ext/src/lib/tippy-bubble-menu/bubble-menu-react.tsx deleted file mode 100644 index d2f7b008..00000000 --- a/packages/editor-ext/src/lib/tippy-bubble-menu/bubble-menu-react.tsx +++ /dev/null @@ -1,72 +0,0 @@ -// Source: https://github.com/ueberdosis/tiptap/blob/v2/packages/react/src/BubbleMenu.tsx - MIT -import { BubbleMenuPlugin, BubbleMenuPluginProps } from "./bubble-menu-plugin"; -import React, { useEffect, useState } from "react"; -import { useCurrentEditor } from "@tiptap/react"; - -type Optional = Pick, K> & Omit; - -export type BubbleMenuProps = Omit< - Optional, - "element" | "editor" -> & { - editor: BubbleMenuPluginProps["editor"] | null; - className?: string; - children: React.ReactNode; - updateDelay?: number; -}; - -export const BubbleMenu = (props: BubbleMenuProps) => { - const [element, setElement] = useState(null); - const { editor: currentEditor } = useCurrentEditor(); - - useEffect(() => { - if (!element) { - return; - } - - if (props.editor?.isDestroyed || currentEditor?.isDestroyed) { - return; - } - - const { - pluginKey = "bubbleMenu", - editor, - tippyOptions = {}, - updateDelay, - shouldShow = null, - } = props; - - const menuEditor = editor || currentEditor; - - if (!menuEditor) { - console.warn( - "BubbleMenu component is not rendered inside of an editor component or does not have editor prop.", - ); - return; - } - - const plugin = BubbleMenuPlugin({ - updateDelay, - editor: menuEditor, - element, - pluginKey, - shouldShow, - tippyOptions, - }); - - menuEditor.registerPlugin(plugin); - return () => { - menuEditor.unregisterPlugin(pluginKey); - }; - }, [props.editor, currentEditor, element]); - - return ( -
- {props.children} -
- ); -}; diff --git a/packages/editor-ext/src/lib/tippy-bubble-menu/index.ts b/packages/editor-ext/src/lib/tippy-bubble-menu/index.ts deleted file mode 100644 index b385802d..00000000 --- a/packages/editor-ext/src/lib/tippy-bubble-menu/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { BubbleMenu as TiptapTippyBubbleMenu } from "./bubble-menu-react"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d08cabf7..e42353c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -337,9 +337,6 @@ importers: socket.io-client: specifier: ^4.8.1 version: 4.8.1 - tippy.js: - specifier: ^6.3.7 - version: 6.3.7 tiptap-extension-global-drag-handle: specifier: ^0.1.18 version: 0.1.18 @@ -3350,9 +3347,6 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@popperjs/core@2.11.8': - resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - '@radix-ui/primitive@1.1.1': resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==} @@ -9423,9 +9417,6 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - tippy.js@6.3.7: - resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} - tiptap-extension-global-drag-handle@0.1.18: resolution: {integrity: sha512-jwFuy1K8DP3a4bFy76Hpc63w1Sil0B7uZ3mvhQomVvUFCU787Lg2FowNhn7NFzeyok761qY2VG+PZ/FDthWUdg==} @@ -13620,8 +13611,6 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@popperjs/core@2.11.8': {} - '@radix-ui/primitive@1.1.1': {} '@radix-ui/react-arrow@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': @@ -20710,10 +20699,6 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 - tippy.js@6.3.7: - dependencies: - '@popperjs/core': 2.11.8 - tiptap-extension-global-drag-handle@0.1.18: {} tldts-core@6.1.72: {}