diff --git a/apps/client/package.json b/apps/client/package.json index e9197ef9..504f0f5f 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -24,7 +24,6 @@ "@mantine/spotlight": "^8.3.12", "@tabler/icons-react": "^3.36.1", "@tanstack/react-query": "^5.90.17", - "@tiptap/extension-character-count": "^2.27.1", "alfaaz": "^1.1.0", "axios": "^1.13.2", "clsx": "^2.1.1", @@ -54,7 +53,6 @@ "react-router-dom": "^7.12.0", "semver": "^7.7.3", "socket.io-client": "^4.8.3", - "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/bubble-menu/bubble-menu.tsx b/apps/client/src/features/editor/components/bubble-menu/bubble-menu.tsx index e8085ca6..a6d143ff 100644 --- a/apps/client/src/features/editor/components/bubble-menu/bubble-menu.tsx +++ b/apps/client/src/features/editor/components/bubble-menu/bubble-menu.tsx @@ -1,10 +1,6 @@ -import { - BubbleMenu, - BubbleMenuProps, - isNodeSelection, - useEditor, - useEditorState, -} from "@tiptap/react"; +import { BubbleMenu, BubbleMenuProps } from "@tiptap/react/menus"; +import { isNodeSelection, useEditorState } from "@tiptap/react"; +import type { Editor } from "@tiptap/react"; import { FC, useEffect, useRef, useState } from "react"; import { IconBold, @@ -38,7 +34,7 @@ export interface BubbleMenuItem { } type EditorBubbleMenuProps = Omit & { - editor: ReturnType; + editor: Editor | null; }; export const EditorBubbleMenu: FC = (props) => { @@ -133,14 +129,9 @@ export const EditorBubbleMenu: FC = (props) => { } return isTextSelected(editor); }, - tippyOptions: { - moveTransition: "transform 0.15s ease-out", - onCreate: (instance) => { - instance.popper.firstChild?.addEventListener("blur", (event) => { - event.preventDefault(); - event.stopImmediatePropagation(); - }); - }, + options: { + placement: "top", + offset: 8, onHide: () => { setIsNodeSelectorOpen(false); setIsTextAlignmentOpen(false); @@ -156,7 +147,7 @@ export const EditorBubbleMenu: FC = (props) => { const [isColorSelectorOpen, setIsColorSelectorOpen] = useState(false); return ( - +
{ + const getReferencedVirtualElement = useCallback(() => { + if (!editor) return; const { selection } = editor.state; const predicate = (node: PMNode) => node.type.name === "callout"; const parent = findParentNode(predicate)(selection); if (parent) { const dom = editor.view.nodeDOM(parent?.pos) as HTMLElement; - return dom.getBoundingClientRect(); + const domRect = dom.getBoundingClientRect(); + return { + getBoundingClientRect: () => domRect, + getClientRects: () => [domRect], + }; } - return posToDOMRect(editor.view, selection.from, selection.to); + const domRect = posToDOMRect(editor.view, selection.from, selection.to); + return { + getBoundingClientRect: () => domRect, + getClientRects: () => [domRect], + }; }, [editor]); const setCalloutType = useCallback( @@ -112,14 +117,12 @@ export function CalloutMenu({ editor }: EditorMenuProps) { editor={editor} pluginKey={`callout-menu`} updateDelay={0} - tippyOptions={{ - getReferenceClientRect, - offset: [0, 10], + getReferencedVirtualElement={getReferencedVirtualElement} + options={{ placement: "bottom", - zIndex: 99, - popperOptions: { - modifiers: [{ name: "flip", enabled: false }], - }, + // offset: 233, // // offset: [0, 10], + // zIndex: 99, + flip: false, }} shouldShow={shouldShow} > diff --git a/apps/client/src/features/editor/components/code-block/code-block-view.tsx b/apps/client/src/features/editor/components/code-block/code-block-view.tsx index 07ad2ad0..130016a3 100644 --- a/apps/client/src/features/editor/components/code-block/code-block-view.tsx +++ b/apps/client/src/features/editor/components/code-block/code-block-view.tsx @@ -90,6 +90,7 @@ export default function CodeBlockView(props: NodeViewProps) { node.textContent.length > 0 } > + {/* @ts-ignore */} diff --git a/apps/client/src/features/editor/components/drawio/drawio-menu.tsx b/apps/client/src/features/editor/components/drawio/drawio-menu.tsx index 0efc2ec0..937b8e7d 100644 --- a/apps/client/src/features/editor/components/drawio/drawio-menu.tsx +++ b/apps/client/src/features/editor/components/drawio/drawio-menu.tsx @@ -1,11 +1,6 @@ -import { - BubbleMenu as BaseBubbleMenu, - findParentNode, - posToDOMRect, - useEditorState, -} from "@tiptap/react"; +import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus"; +import { findParentNode, posToDOMRect, useEditorState } from "@tiptap/react"; import { useCallback } from "react"; -import { sticky } from "tippy.js"; import { Node as PMNode } from "prosemirror-model"; import { EditorMenuProps, @@ -40,17 +35,26 @@ export function DrawioMenu({ editor }: EditorMenuProps) { }, }); - const getReferenceClientRect = useCallback(() => { + const getReferencedVirtualElement = useCallback(() => { + if (!editor) return; const { selection } = editor.state; const predicate = (node: PMNode) => node.type.name === "drawio"; const parent = findParentNode(predicate)(selection); if (parent) { const dom = editor.view.nodeDOM(parent?.pos) as HTMLElement; - return dom.getBoundingClientRect(); + const domRect = dom.getBoundingClientRect(); + return { + getBoundingClientRect: () => domRect, + getClientRects: () => [domRect], + }; } - return posToDOMRect(editor.view, selection.from, selection.to); + const domRect = posToDOMRect(editor.view, selection.from, selection.to); + return { + getBoundingClientRect: () => domRect, + getClientRects: () => [domRect], + }; }, [editor]); const onWidthChange = useCallback( @@ -65,15 +69,11 @@ export function DrawioMenu({ editor }: EditorMenuProps) { editor={editor} pluginKey={`drawio-menu`} updateDelay={0} - tippyOptions={{ - getReferenceClientRect, - offset: [0, 8], - zIndex: 99, - popperOptions: { - modifiers: [{ name: "flip", enabled: false }], - }, - plugins: [sticky], - sticky: "popper", + getReferencedVirtualElement={getReferencedVirtualElement} + options={{ + placement: "top", + offset: 8, + flip: false, }} shouldShow={shouldShow} > diff --git a/apps/client/src/features/editor/components/drawio/drawio-view.tsx b/apps/client/src/features/editor/components/drawio/drawio-view.tsx index 5a0fbd86..b51e8936 100644 --- a/apps/client/src/features/editor/components/drawio/drawio-view.tsx +++ b/apps/client/src/features/editor/components/drawio/drawio-view.tsx @@ -66,6 +66,7 @@ export default function DrawioView(props: NodeViewProps) { const fileName = "diagram.drawio.svg"; const drawioSVGFile = await svgStringToFile(svgString, fileName); + //@ts-ignore const pageId = editor.storage?.pageId; let attachment: IAttachment = null; 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/excalidraw/excalidraw-menu.tsx b/apps/client/src/features/editor/components/excalidraw/excalidraw-menu.tsx index 42329e5c..06e79515 100644 --- a/apps/client/src/features/editor/components/excalidraw/excalidraw-menu.tsx +++ b/apps/client/src/features/editor/components/excalidraw/excalidraw-menu.tsx @@ -1,11 +1,6 @@ -import { - BubbleMenu as BaseBubbleMenu, - findParentNode, - posToDOMRect, - useEditorState, -} from "@tiptap/react"; +import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus"; +import { findParentNode, posToDOMRect, useEditorState } from "@tiptap/react"; import { useCallback } from "react"; -import { sticky } from "tippy.js"; import { Node as PMNode } from "prosemirror-model"; import { EditorMenuProps, @@ -42,17 +37,26 @@ export function ExcalidrawMenu({ editor }: EditorMenuProps) { }, }); - const getReferenceClientRect = useCallback(() => { + const getReferencedVirtualElement = useCallback(() => { + if (!editor) return; const { selection } = editor.state; const predicate = (node: PMNode) => node.type.name === "excalidraw"; const parent = findParentNode(predicate)(selection); if (parent) { const dom = editor.view.nodeDOM(parent?.pos) as HTMLElement; - return dom.getBoundingClientRect(); + const domRect = dom.getBoundingClientRect(); + return { + getBoundingClientRect: () => domRect, + getClientRects: () => [domRect], + }; } - return posToDOMRect(editor.view, selection.from, selection.to); + const domRect = posToDOMRect(editor.view, selection.from, selection.to); + return { + getBoundingClientRect: () => domRect, + getClientRects: () => [domRect], + }; }, [editor]); const onWidthChange = useCallback( @@ -65,17 +69,13 @@ export function ExcalidrawMenu({ editor }: EditorMenuProps) { return ( diff --git a/apps/client/src/features/editor/components/excalidraw/excalidraw-view.tsx b/apps/client/src/features/editor/components/excalidraw/excalidraw-view.tsx index 779a826d..86c9665e 100644 --- a/apps/client/src/features/editor/components/excalidraw/excalidraw-view.tsx +++ b/apps/client/src/features/editor/components/excalidraw/excalidraw-view.tsx @@ -98,6 +98,7 @@ export default function ExcalidrawView(props: NodeViewProps) { const fileName = "diagram.excalidraw.svg"; const excalidrawSvgFile = await svgStringToFile(svgString, fileName); + // @ts-ignore const pageId = editor.storage?.pageId; let attachment: IAttachment = null; diff --git a/apps/client/src/features/editor/components/image/image-menu.tsx b/apps/client/src/features/editor/components/image/image-menu.tsx index 723ec299..6f2c9b9c 100644 --- a/apps/client/src/features/editor/components/image/image-menu.tsx +++ b/apps/client/src/features/editor/components/image/image-menu.tsx @@ -1,11 +1,6 @@ -import { - BubbleMenu as BaseBubbleMenu, - findParentNode, - posToDOMRect, - useEditorState, -} from "@tiptap/react"; +import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus"; +import { findParentNode, posToDOMRect, useEditorState } from "@tiptap/react"; import React, { useCallback } from "react"; -import { sticky } from "tippy.js"; import { Node as PMNode } from "prosemirror-model"; import { EditorMenuProps, @@ -22,16 +17,6 @@ import { useTranslation } from "react-i18next"; export function ImageMenu({ editor }: EditorMenuProps) { const { t } = useTranslation(); - const shouldShow = useCallback( - ({ state }: ShouldShowProps) => { - if (!state) { - return false; - } - - return editor.isActive("image"); - }, - [editor], - ); const editorState = useEditorState({ editor, @@ -52,17 +37,37 @@ export function ImageMenu({ editor }: EditorMenuProps) { }, }); - const getReferenceClientRect = useCallback(() => { + const shouldShow = useCallback( + ({ state }: ShouldShowProps) => { + if (!state) { + return false; + } + + return editor.isActive("image"); + }, + [editor], + ); + + const getReferencedVirtualElement = useCallback(() => { + if (!editor) return; const { selection } = editor.state; const predicate = (node: PMNode) => node.type.name === "image"; const parent = findParentNode(predicate)(selection); if (parent) { const dom = editor.view.nodeDOM(parent?.pos) as HTMLElement; - return dom.getBoundingClientRect(); + const domRect = dom.getBoundingClientRect(); + return { + getBoundingClientRect: () => domRect, + getClientRects: () => [domRect], + }; } - return posToDOMRect(editor.view, selection.from, selection.to); + const domRect = posToDOMRect(editor.view, selection.from, selection.to); + return { + getBoundingClientRect: () => domRect, + getClientRects: () => [domRect], + }; }, [editor]); const alignImageLeft = useCallback(() => { @@ -105,15 +110,11 @@ export function ImageMenu({ editor }: EditorMenuProps) { editor={editor} pluginKey={`image-menu`} updateDelay={0} - tippyOptions={{ - getReferenceClientRect, - offset: [0, 8], - zIndex: 99, - popperOptions: { - modifiers: [{ name: "flip", enabled: false }], - }, - plugins: [sticky], - sticky: "popper", + getReferencedVirtualElement={getReferencedVirtualElement} + options={{ + placement: "top", + offset: 8, + flip: false, }} shouldShow={shouldShow} > diff --git a/apps/client/src/features/editor/components/link/link-menu.tsx b/apps/client/src/features/editor/components/link/link-menu.tsx index 69f7c449..63fd10bf 100644 --- a/apps/client/src/features/editor/components/link/link-menu.tsx +++ b/apps/client/src/features/editor/components/link/link-menu.tsx @@ -1,9 +1,10 @@ -import { BubbleMenu as BaseBubbleMenu, useEditorState } from "@tiptap/react"; +import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus"; import React, { useCallback, useState } from "react"; import { EditorMenuProps } from "@/features/editor/components/table/types/types.ts"; import { LinkEditorPanel } from "@/features/editor/components/link/link-editor-panel.tsx"; import { LinkPreviewPanel } from "@/features/editor/components/link/link-preview.tsx"; import { Card } from "@mantine/core"; +import { useEditorState } from "@tiptap/react"; export function LinkMenu({ editor, appendTo }: EditorMenuProps) { const [showEdit, setShowEdit] = useState(false); @@ -59,18 +60,15 @@ export function LinkMenu({ editor, appendTo }: EditorMenuProps) { return ( { - return appendTo?.current; - }, - onHidden: () => { + options={{ + onHide: () => { setShowEdit(false); }, placement: "bottom", - offset: [0, 5], - zIndex: 101, + offset: 5, + // zIndex: 101, }} shouldShow={shouldShow} > diff --git a/apps/client/src/features/editor/components/mention/mention-list.tsx b/apps/client/src/features/editor/components/mention/mention-list.tsx index 389c2ce5..0bd95597 100644 --- a/apps/client/src/features/editor/components/mention/mention-list.tsx +++ b/apps/client/src/features/editor/components/mention/mention-list.tsx @@ -106,6 +106,7 @@ const MentionList = forwardRef((props, ref) => { setRenderItems(items); // update editor storage + //@ts-ignore props.editor.storage.mentionItems = items; } }, [suggestion, isLoading]); 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 11710639..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,75 +54,88 @@ 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 if ( whitespaceCount > 3 && + //@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/search-and-replace/search-and-replace-dialog.tsx b/apps/client/src/features/editor/components/search-and-replace/search-and-replace-dialog.tsx index df6f0031..f5c17661 100644 --- a/apps/client/src/features/editor/components/search-and-replace/search-and-replace-dialog.tsx +++ b/apps/client/src/features/editor/components/search-and-replace/search-and-replace-dialog.tsx @@ -73,6 +73,8 @@ function SearchAndReplaceDialog({ editor, editable = true }: PageFindDialogDialo if (!editor) return; const { results, resultIndex } = editor.storage.searchAndReplace; + //TODO: check type error + //@ts-ignore const position: Range = results[resultIndex]; if (!position) return; diff --git a/apps/client/src/features/editor/components/slash-menu/menu-items.ts b/apps/client/src/features/editor/components/slash-menu/menu-items.ts index f56d7f04..362c1686 100644 --- a/apps/client/src/features/editor/components/slash-menu/menu-items.ts +++ b/apps/client/src/features/editor/components/slash-menu/menu-items.ts @@ -161,6 +161,7 @@ const CommandGroups: SlashMenuGroupedItemsType = { command: ({ editor, range }) => { editor.chain().focus().deleteRange(range).run(); + // @ts-ignore const pageId = editor.storage?.pageId; if (!pageId) return; @@ -188,6 +189,7 @@ const CommandGroups: SlashMenuGroupedItemsType = { command: ({ editor, range }) => { editor.chain().focus().deleteRange(range).run(); + // @ts-ignore const pageId = editor.storage?.pageId; if (!pageId) return; @@ -213,6 +215,7 @@ const CommandGroups: SlashMenuGroupedItemsType = { command: ({ editor, range }) => { editor.chain().focus().deleteRange(range).run(); + // @ts-ignore const pageId = editor.storage?.pageId; if (!pageId) return; 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 6cc017e2..9f0544e6 100644 --- a/apps/client/src/features/editor/components/subpages/subpages-menu.tsx +++ b/apps/client/src/features/editor/components/subpages/subpages-menu.tsx @@ -1,15 +1,11 @@ -import { - BubbleMenu as BaseBubbleMenu, - posToDOMRect, - findParentNode, -} from "@tiptap/react"; +import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus"; +import { posToDOMRect, findParentNode } from "@tiptap/react"; import { Node as PMNode } from "@tiptap/pm/model"; import React, { useCallback } from "react"; import { ActionIcon, Tooltip } from "@mantine/core"; import { IconTrash } from "@tabler/icons-react"; import { useTranslation } from "react-i18next"; import { Editor } from "@tiptap/core"; -import { sticky } from "tippy.js"; interface SubpagesMenuProps { editor: Editor; @@ -33,7 +29,7 @@ export const SubpagesMenu = React.memo( return editor.isActive("subpages"); }, - [editor], + [editor] ); const getReferenceClientRect = useCallback(() => { @@ -62,18 +58,8 @@ export const SubpagesMenu = React.memo( return ( @@ -89,7 +75,7 @@ export const SubpagesMenu = React.memo( ); - }, + } ); export default SubpagesMenu; diff --git a/apps/client/src/features/editor/components/subpages/subpages-view.tsx b/apps/client/src/features/editor/components/subpages/subpages-view.tsx index 0da33028..697c1213 100644 --- a/apps/client/src/features/editor/components/subpages/subpages-view.tsx +++ b/apps/client/src/features/editor/components/subpages/subpages-view.tsx @@ -19,6 +19,7 @@ export default function SubpagesView(props: NodeViewProps) { const { spaceSlug, shareId } = useParams(); const { t } = useTranslation(); + //@ts-ignore const currentPageId = editor.storage.pageId; // Get subpages from shared tree if we're in a shared context 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 2ea2e8dd..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 @@ -1,6 +1,4 @@ -import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react"; import React, { useCallback } from "react"; - import { EditorMenuProps, ShouldShowProps, @@ -17,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 => { @@ -29,7 +28,7 @@ export const TableCellMenu = React.memo( return isCellSelection(state.selection); }, - [editor], + [editor] ); const mergeCells = useCallback(() => { @@ -53,23 +52,27 @@ 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} > - + - + ); - }, + } ); 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 1d2985e8..e54a06af 100644 --- a/apps/client/src/features/editor/components/table/table-menu.tsx +++ b/apps/client/src/features/editor/components/table/table-menu.tsx @@ -1,11 +1,6 @@ -import { - BubbleMenu as BaseBubbleMenu, - posToDOMRect, - findParentNode, -} from "@tiptap/react"; +import { posToDOMRect, findParentNode } from "@tiptap/react"; import { Node as PMNode } from "@tiptap/pm/model"; import React, { useCallback } from "react"; - import { EditorMenuProps, ShouldShowProps, @@ -17,9 +12,12 @@ import { IconColumnRemove, IconRowInsertBottom, IconRowInsertTop, - IconRowRemove, IconTableColumn, IconTableRow, + IconRowRemove, + IconTableColumn, + IconTableRow, IconTrashX, -} from '@tabler/icons-react'; +} from "@tabler/icons-react"; +import { BubbleMenu } from "@tiptap/react/menus"; import { isCellSelection } from "@docmost/editor-ext"; import { useTranslation } from "react-i18next"; @@ -34,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(() => { @@ -87,42 +93,33 @@ 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} > - + - + - + - + ); - }, + } ); export default TableMenu; diff --git a/apps/client/src/features/editor/components/video/video-menu.tsx b/apps/client/src/features/editor/components/video/video-menu.tsx index 3252e621..57a012a8 100644 --- a/apps/client/src/features/editor/components/video/video-menu.tsx +++ b/apps/client/src/features/editor/components/video/video-menu.tsx @@ -1,11 +1,6 @@ -import { - BubbleMenu as BaseBubbleMenu, - findParentNode, - posToDOMRect, - useEditorState, -} from "@tiptap/react"; +import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus"; +import { findParentNode, posToDOMRect, useEditorState } from "@tiptap/react"; import React, { useCallback } from "react"; -import { sticky } from "tippy.js"; import { Node as PMNode } from "prosemirror-model"; import { EditorMenuProps, @@ -22,20 +17,10 @@ import { useTranslation } from "react-i18next"; export function VideoMenu({ editor }: EditorMenuProps) { const { t } = useTranslation(); - const shouldShow = useCallback( - ({ state }: ShouldShowProps) => { - if (!state) { - return false; - } - - return editor.isActive("video"); - }, - [editor], - ); const editorState = useEditorState({ editor, - selector: (ctx) => { + selector: ctx => { if (!ctx.editor) { return null; } @@ -52,17 +37,37 @@ export function VideoMenu({ editor }: EditorMenuProps) { }, }); - const getReferenceClientRect = useCallback(() => { + const shouldShow = useCallback( + ({ state }: ShouldShowProps) => { + if (!state) { + return false; + } + + return editor.isActive("video"); + }, + [editor], + ); + + const getReferencedVirtualElement = useCallback(() => { + if (!editor) return; const { selection } = editor.state; const predicate = (node: PMNode) => node.type.name === "video"; const parent = findParentNode(predicate)(selection); if (parent) { const dom = editor.view.nodeDOM(parent?.pos) as HTMLElement; - return dom.getBoundingClientRect(); + const domRect = dom.getBoundingClientRect(); + return { + getBoundingClientRect: () => domRect, + getClientRects: () => [domRect], + }; } - return posToDOMRect(editor.view, selection.from, selection.to); + const domRect = posToDOMRect(editor.view, selection.from, selection.to); + return { + getBoundingClientRect: () => domRect, + getClientRects: () => [domRect], + }; }, [editor]); const alignVideoLeft = useCallback(() => { @@ -105,15 +110,11 @@ export function VideoMenu({ editor }: EditorMenuProps) { editor={editor} pluginKey={`video-menu`} updateDelay={0} - tippyOptions={{ - getReferenceClientRect, - offset: [0, 8], - zIndex: 99, - popperOptions: { - modifiers: [{ name: "flip", enabled: false }], - }, - plugins: [sticky], - sticky: "popper", + getReferencedVirtualElement={getReferencedVirtualElement} + options={{ + placement: "top", + offset: 8, + flip: false, }} shouldShow={shouldShow} > diff --git a/apps/client/src/features/editor/extensions/extensions.ts b/apps/client/src/features/editor/extensions/extensions.ts index ecdea2e7..cb7e1290 100644 --- a/apps/client/src/features/editor/extensions/extensions.ts +++ b/apps/client/src/features/editor/extensions/extensions.ts @@ -1,11 +1,7 @@ import { StarterKit } from "@tiptap/starter-kit"; -import { Placeholder } from "@tiptap/extension-placeholder"; import { TextAlign } from "@tiptap/extension-text-align"; -import { CharacterCount } from "@tiptap/extension-character-count"; -import { TaskList } from "@tiptap/extension-task-list"; -import { ListKeymap } from "@tiptap/extension-list-keymap"; -import { TaskItem } from "@tiptap/extension-task-item"; -import { Underline } from "@tiptap/extension-underline"; +import { TaskList, TaskItem } from "@tiptap/extension-list"; +import { Placeholder, CharacterCount } from "@tiptap/extensions"; import { Superscript } from "@tiptap/extension-superscript"; import SubScript from "@tiptap/extension-subscript"; import { Typography } from "@tiptap/extension-typography"; @@ -15,7 +11,7 @@ import GlobalDragHandle from "tiptap-extension-global-drag-handle"; import { Youtube } from "@tiptap/extension-youtube"; import SlashCommand from "@/features/editor/extensions/slash-command"; import { Collaboration, isChangeOrigin } from "@tiptap/extension-collaboration"; -import { CollaborationCursor } from "@tiptap/extension-collaboration-cursor"; +import { CollaborationCaret } from "@tiptap/extension-collaboration-caret"; import { HocuspocusProvider } from "@hocuspocus/provider"; import { Comment, @@ -41,8 +37,8 @@ import { Embed, SearchAndReplace, Mention, - Subpages, TableDndExtension, + Subpages, Heading, Highlight, UniqueID, @@ -97,7 +93,9 @@ lowlight.register("scala", scala); export const mainExtensions = [ StarterKit.configure({ heading: false, - history: false, + undoRedo: false, + link: false, + trailingNode: false, dropcursor: { width: 3, color: "#70CFF8", @@ -134,8 +132,6 @@ export const mainExtensions = [ TaskItem.configure({ nested: true, }), - ListKeymap, - Underline, LinkExtension.configure({ openOnClick: false, }), @@ -170,6 +166,9 @@ export const mainExtensions = [ }, }).extend({ addNodeView() { + // Force the react node view to render immediately using flush sync (https://github.com/ueberdosis/tiptap/blob/b4db352f839e1d82f9add6ee7fb45561336286d8/packages/react/src/ReactRenderer.tsx#L183-L191) + this.editor.isInitialized = true; + return ReactNodeViewRenderer(MentionView); }, }), @@ -208,6 +207,7 @@ export const mainExtensions = [ }), CustomCodeBlock.configure({ view: CodeBlockView, + //@ts-ignore lowlight, HTMLAttributes: { spellcheck: false, @@ -258,8 +258,9 @@ type CollabExtensions = (provider: HocuspocusProvider, user: IUser) => any[]; export const collabExtensions: CollabExtensions = (provider, user) => [ Collaboration.configure({ document: provider.document, + provider, }), - CollaborationCursor.configure({ + CollaborationCaret.configure({ provider, user: { name: user.name, diff --git a/apps/client/src/features/editor/page-editor.tsx b/apps/client/src/features/editor/page-editor.tsx index b4478920..f5619c91 100644 --- a/apps/client/src/features/editor/page-editor.tsx +++ b/apps/client/src/features/editor/page-editor.tsx @@ -1,11 +1,19 @@ import "@/features/editor/styles/index.css"; -import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import { IndexeddbPersistence } from "y-indexeddb"; import * as Y from "yjs"; import { HocuspocusProvider, - onAuthenticationFailedParameters, + onStatusParameters, WebSocketStatus, + HocuspocusProviderWebsocket, + onSyncedParameters, } from "@hocuspocus/provider"; import { EditorContent, @@ -69,8 +77,6 @@ export default function PageEditor({ editable, content, }: PageEditorProps) { - - const collaborationURL = useCollaborationUrl(); const isComponentMounted = useRef(false); const editorCreated = useRef(false); @@ -78,152 +84,133 @@ export default function PageEditor({ useEffect(() => { isComponentMounted.current = true; }, []); - + const [currentUser] = useAtom(currentUserAtom); const [, setEditor] = useAtom(pageEditorAtom); const [, setAsideState] = useAtom(asideStateAtom); const [, setActiveCommentId] = useAtom(activeCommentIdAtom); const [showCommentPopup, setShowCommentPopup] = useAtom(showCommentPopupAtom); - const ydocRef = useRef(null); - if (!ydocRef.current) { - ydocRef.current = new Y.Doc(); - } - const ydoc = ydocRef.current; - const [isLocalSynced, setLocalSynced] = useState(false); - const [isRemoteSynced, setRemoteSynced] = useState(false); + const [isLocalSynced, setIsLocalSynced] = useState(false); + const [isRemoteSynced, setIsRemoteSynced] = useState(false); const [yjsConnectionStatus, setYjsConnectionStatus] = useAtom( - yjsConnectionStatusAtom, + yjsConnectionStatusAtom ); const menuContainerRef = useRef(null); - const documentName = `page.${pageId}`; const { data: collabQuery, refetch: refetchCollabToken } = useCollabToken(); const { isIdle, resetIdle } = useIdle(FIVE_MINUTES, { initialState: false }); const documentState = useDocumentVisibility(); - const [isCollabReady, setIsCollabReady] = useState(false); const { pageSlug } = useParams(); const slugId = extractPageSlugId(pageSlug); const userPageEditMode = currentUser?.user?.settings?.preferences?.pageEditMode ?? PageEditMode.Edit; - - const canScroll = useCallback(() => isComponentMounted.current && editorCreated.current, [isComponentMounted, editorCreated]); + const canScroll = useCallback( + () => isComponentMounted.current && editorCreated.current, + [isComponentMounted, editorCreated] + ); const { handleScrollTo } = useEditorScroll({ canScroll }); // Providers only created once per pageId const providersRef = useRef<{ local: IndexeddbPersistence; remote: HocuspocusProvider; + socket: HocuspocusProviderWebsocket; } | null>(null); const [providersReady, setProvidersReady] = useState(false); - const localProvider = providersRef.current?.local; - const remoteProvider = providersRef.current?.remote; - - // Track when collaborative provider is ready and synced - const [collabReady, setCollabReady] = useState(false); - useEffect(() => { - if ( - remoteProvider?.status === WebSocketStatus.Connected && - isLocalSynced && - isRemoteSynced - ) { - setCollabReady(true); - } - }, [remoteProvider?.status, isLocalSynced, isRemoteSynced]); - useEffect(() => { if (!providersRef.current) { + const documentName = `page.${pageId}`; + const ydoc = new Y.Doc(); const local = new IndexeddbPersistence(documentName, ydoc); - local.on("synced", () => setLocalSynced(true)); - const remote = new HocuspocusProvider({ - name: documentName, + const socket = new HocuspocusProviderWebsocket({ url: collaborationURL, + }); + const onLocalSyncedHandler = () => { + setIsLocalSynced(true); + }; + const onStatusHandler = (event: onStatusParameters) => { + setYjsConnectionStatus(event.status); + }; + const onSyncedHandler = (event: onSyncedParameters) => { + setIsRemoteSynced(event.state); + }; + const onAuthenticationFailedHandler = () => { + const payload = jwtDecode(collabQuery?.token); + const now = Date.now().valueOf() / 1000; + const isTokenExpired = now >= payload.exp; + if (isTokenExpired) { + refetchCollabToken().then((result) => { + if (result.data?.token) { + socket.disconnect(); + setTimeout(() => { + remote.configuration.token = result.data.token; + socket.connect(); + }, 100); + } + }); + } + }; + const remote = new HocuspocusProvider({ + websocketProvider: socket, + name: documentName, document: ydoc, token: collabQuery?.token, - connect: true, - preserveConnection: false, - onAuthenticationFailed: (auth: onAuthenticationFailedParameters) => { - const payload = jwtDecode(collabQuery?.token); - const now = Date.now().valueOf() / 1000; - const isTokenExpired = now >= payload.exp; - if (isTokenExpired) { - refetchCollabToken().then((result) => { - if (result.data?.token) { - remote.disconnect(); - setTimeout(() => { - remote.configuration.token = result.data.token; - remote.connect(); - }, 100); - } - }); - } - }, - onStatus: (status) => { - if (status.status === "connected") { - setYjsConnectionStatus(status.status); - } - }, + onAuthenticationFailed: onAuthenticationFailedHandler, + onStatus: onStatusHandler, + onSynced: onSyncedHandler, }); - remote.on("synced", () => setRemoteSynced(true)); - remote.on("disconnect", () => { - setYjsConnectionStatus(WebSocketStatus.Disconnected); - }); - providersRef.current = { local, remote }; + + local.on("synced", onLocalSyncedHandler); + providersRef.current = { socket, local, remote }; setProvidersReady(true); } else { setProvidersReady(true); } // Only destroy on final unmount return () => { + providersRef.current?.socket.destroy(); providersRef.current?.remote.destroy(); providersRef.current?.local.destroy(); providersRef.current = null; }; }, [pageId]); - /* - useEffect(() => { - // Handle token updates by reconnecting with new token - if (providersRef.current?.remote && collabQuery?.token) { - const currentToken = providersRef.current.remote.configuration.token; - if (currentToken !== collabQuery.token) { - // Token has changed, need to reconnect with new token - providersRef.current.remote.disconnect(); - providersRef.current.remote.configuration.token = collabQuery.token; - providersRef.current.remote.connect(); - } - } - }, [collabQuery?.token]); - */ - // Only connect/disconnect on tab/idle, not destroy useEffect(() => { if (!providersReady || !providersRef.current) return; - const remoteProvider = providersRef.current.remote; + const socket = providersRef.current.socket; + if ( isIdle && documentState === "hidden" && - remoteProvider.status === WebSocketStatus.Connected + yjsConnectionStatus === WebSocketStatus.Connected ) { - remoteProvider.disconnect(); - setIsCollabReady(false); + socket.disconnect(); return; } if ( documentState === "visible" && - remoteProvider.status === WebSocketStatus.Disconnected + yjsConnectionStatus === WebSocketStatus.Disconnected ) { resetIdle(); - remoteProvider.connect(); - setTimeout(() => setIsCollabReady(true), 500); + socket.connect(); } }, [isIdle, documentState, providersReady, resetIdle]); + // Attach here, to make sure the connection gets properly established + providersRef.current?.remote.attach(); + const extensions = useMemo(() => { - if (!remoteProvider || !currentUser?.user) return mainExtensions; + if (!providersReady || !providersRef.current || !currentUser?.user) { + return mainExtensions; + } + + const remoteProvider = providersRef.current.remote; + return [ ...mainExtensions, ...collabExtensions(remoteProvider, currentUser?.user), ]; - }, [remoteProvider, currentUser?.user]); + }, [providersReady, currentUser?.user]); const editor = useEditor( { @@ -275,6 +262,7 @@ export default function PageEditor({ if (editor) { // @ts-ignore setEditor(editor); + // @ts-ignore editor.storage.pageId = pageId; handleScrollTo(editor); editorCreated.current = true; @@ -287,7 +275,7 @@ export default function PageEditor({ debouncedUpdateContent(editorJson); }, }, - [pageId, editable, remoteProvider], + [pageId, editable, extensions] ); const editorIsEditable = useEditorState({ @@ -332,7 +320,7 @@ export default function PageEditor({ return () => { document.removeEventListener( "ACTIVE_COMMENT_EVENT", - handleActiveCommentEvent, + handleActiveCommentEvent ); }; }, []); @@ -343,30 +331,17 @@ export default function PageEditor({ setAsideState({ tab: "", isAsideOpen: false }); }, [pageId]); - useEffect(() => { - if (remoteProvider?.status === WebSocketStatus.Connecting) { - const timeout = setTimeout(() => { - setYjsConnectionStatus(WebSocketStatus.Disconnected); - }, 5000); - return () => clearTimeout(timeout); - } - }, [remoteProvider?.status]); - const isSynced = isLocalSynced && isRemoteSynced; useEffect(() => { - const collabReadyTimeout = setTimeout(() => { - if ( - !isCollabReady && - isSynced && - remoteProvider?.status === WebSocketStatus.Connected - ) { - setIsCollabReady(true); + const timeout = setTimeout(() => { + if (yjsConnectionStatus === WebSocketStatus.Connecting || !isSynced) { + setYjsConnectionStatus(WebSocketStatus.Disconnected); } - }, 500); - return () => clearTimeout(collabReadyTimeout); - }, [isRemoteSynced, isLocalSynced, remoteProvider?.status]); + }, 7500); + return () => clearTimeout(timeout); + }, [yjsConnectionStatus, isSynced]); useEffect(() => { // Only honor user default page edit mode preference and permissions if (editor) { @@ -388,12 +363,13 @@ export default function PageEditor({ useEffect(() => { if ( !hasConnectedOnceRef.current && - remoteProvider?.status === WebSocketStatus.Connected + yjsConnectionStatus === WebSocketStatus.Connected && + isSynced ) { hasConnectedOnceRef.current = true; setShowStatic(false); } - }, [remoteProvider?.status]); + }, [yjsConnectionStatus, isSynced]); if (showStatic) { return ( diff --git a/apps/client/src/features/editor/readonly-page-editor.tsx b/apps/client/src/features/editor/readonly-page-editor.tsx index c81e4d19..77496fcd 100644 --- a/apps/client/src/features/editor/readonly-page-editor.tsx +++ b/apps/client/src/features/editor/readonly-page-editor.tsx @@ -81,6 +81,7 @@ export default function ReadonlyPageEditor({ onCreate={({ editor }) => { if (editor) { if (pageId) { + // @ts-ignore editor.storage.pageId = pageId; } // @ts-ignore diff --git a/apps/client/src/features/editor/styles/collaboration.css b/apps/client/src/features/editor/styles/collaboration.css index 4a43ac25..a13d2180 100644 --- a/apps/client/src/features/editor/styles/collaboration.css +++ b/apps/client/src/features/editor/styles/collaboration.css @@ -1,5 +1,5 @@ /* Give a remote user a caret */ -.collaboration-cursor__caret { +.collaboration-carets__caret { border-left: 1px solid #0d0d0d; border-right: 1px solid #0d0d0d; margin-left: -1px; @@ -10,7 +10,7 @@ } /* Render the username above the caret */ -.collaboration-cursor__label { +.collaboration-carets__label { border-radius: 3px 3px 3px 0; color: #0d0d0d; font-size: 0.75rem; diff --git a/apps/client/src/features/page-history/components/history-list.tsx b/apps/client/src/features/page-history/components/history-list.tsx index af178eac..7b0d9ea2 100644 --- a/apps/client/src/features/page-history/components/history-list.tsx +++ b/apps/client/src/features/page-history/components/history-list.tsx @@ -67,7 +67,7 @@ function HistoryList({ pageId }: Props) { mainEditorTitle .chain() .clearContent() - .setContent(activeHistoryData.title, true) + .setContent(activeHistoryData.title, { emitUpdate: true }) .run(); mainEditor .chain() diff --git a/apps/server/src/collaboration/collaboration.gateway.ts b/apps/server/src/collaboration/collaboration.gateway.ts index 53afd64a..c9809efe 100644 --- a/apps/server/src/collaboration/collaboration.gateway.ts +++ b/apps/server/src/collaboration/collaboration.gateway.ts @@ -26,7 +26,7 @@ export class CollaborationGateway { ) { this.redisConfig = parseRedisUrl(this.environmentService.getRedisUrl()); - this.hocuspocus = HocuspocusServer.configure({ + this.hocuspocus = new Hocuspocus({ debounce: 10000, maxDebounce: 45000, unloadImmediately: false, @@ -69,6 +69,6 @@ export class CollaborationGateway { } async destroy(): Promise { - await this.hocuspocus.destroy(); + //await this.hocuspocus.destroy(); } } diff --git a/apps/server/src/collaboration/collaboration.util.ts b/apps/server/src/collaboration/collaboration.util.ts index e20dabdb..76f72c3c 100644 --- a/apps/server/src/collaboration/collaboration.util.ts +++ b/apps/server/src/collaboration/collaboration.util.ts @@ -1,14 +1,12 @@ import { StarterKit } from '@tiptap/starter-kit'; import { TextAlign } from '@tiptap/extension-text-align'; -import { TaskList } from '@tiptap/extension-task-list'; -import { TaskItem } from '@tiptap/extension-task-item'; -import { Underline } from '@tiptap/extension-underline'; import { Superscript } from '@tiptap/extension-superscript'; import SubScript from '@tiptap/extension-subscript'; import { Typography } from '@tiptap/extension-typography'; import { TextStyle } from '@tiptap/extension-text-style'; import { Color } from '@tiptap/extension-color'; import { Youtube } from '@tiptap/extension-youtube'; +import { TaskList, TaskItem } from '@tiptap/extension-list'; import { Heading, Callout, @@ -42,6 +40,7 @@ import { generateHTML, generateJSON } from '../common/helpers/prosemirror/html'; // @tiptap/html library works best for generating prosemirror json state but not HTML // see: https://github.com/ueberdosis/tiptap/issues/5352 // see:https://github.com/ueberdosis/tiptap/issues/4089 +//import { generateJSON } from '@tiptap/html'; import { Node } from '@tiptap/pm/model'; import * as Y from 'yjs'; import { turndown } from '../integrations/export/turndown-utils'; @@ -49,6 +48,8 @@ import { turndown } from '../integrations/export/turndown-utils'; export const tiptapExtensions = [ StarterKit.configure({ codeBlock: false, + link: false, + trailingNode: false, heading: false, }), Heading, @@ -61,7 +62,6 @@ export const tiptapExtensions = [ TaskItem.configure({ nested: true, }), - Underline, LinkExtension, Superscript, SubScript, diff --git a/apps/server/src/collaboration/extensions/authentication.extension.ts b/apps/server/src/collaboration/extensions/authentication.extension.ts index 1a42bd97..04a360f7 100644 --- a/apps/server/src/collaboration/extensions/authentication.extension.ts +++ b/apps/server/src/collaboration/extensions/authentication.extension.ts @@ -69,7 +69,7 @@ export class AuthenticationExtension implements Extension { } if (userSpaceRole === SpaceRole.READER) { - data.connection.readOnly = true; + data.connectionConfig.readOnly = true; this.logger.debug(`User granted readonly access to page: ${pageId}`); } diff --git a/package.json b/package.json index cfaada1b..9875871b 100644 --- a/package.json +++ b/package.json @@ -15,60 +15,55 @@ "server:dev": "nx run server:start:dev", "server:start": "nx run server:start:prod", "email:dev": "nx run server:email:dev", - "dev": "pnpm concurrently -n \"frontend,backend\" -c \"cyan,green\" \"pnpm run client:dev\" \"pnpm run server:dev\"" + "dev": "pnpm concurrently -n \"frontend,backend\" -c \"cyan,green\" \"pnpm run client:dev\" \"pnpm run server:dev\"", + "clean": "rm -rf apps/*/dist packages/*/dist" }, "dependencies": { "@braintree/sanitize-url": "^7.1.0", "@casl/ability": "^6.7.5", "@docmost/editor-ext": "workspace:*", "@floating-ui/dom": "^1.7.3", - "@hocuspocus/extension-redis": "^2.15.3", - "@hocuspocus/provider": "^2.15.3", - "@hocuspocus/server": "^2.15.3", - "@hocuspocus/transformer": "^2.15.3", + "@hocuspocus/extension-redis": "3.4.3", + "@hocuspocus/provider": "3.4.3", + "@hocuspocus/server": "3.4.3", + "@hocuspocus/transformer": "3.4.3", "@joplin/turndown": "^4.0.74", "@joplin/turndown-plugin-gfm": "^1.0.56", "@sindresorhus/slugify": "1.1.0", - "@tiptap/core": "2.27.1", - "@tiptap/extension-code-block": "2.27.1", - "@tiptap/extension-code-block-lowlight": "2.27.1", - "@tiptap/extension-collaboration": "2.27.1", - "@tiptap/extension-collaboration-cursor": "2.27.1", - "@tiptap/extension-color": "2.27.1", - "@tiptap/extension-document": "2.27.1", - "@tiptap/extension-heading": "2.27.1", - "@tiptap/extension-highlight": "2.27.1", - "@tiptap/extension-history": "2.27.1", - "@tiptap/extension-image": "2.27.1", - "@tiptap/extension-link": "2.27.1", - "@tiptap/extension-list-item": "2.27.1", - "@tiptap/extension-list-keymap": "2.27.1", - "@tiptap/extension-placeholder": "2.27.1", - "@tiptap/extension-subscript": "2.27.1", - "@tiptap/extension-superscript": "2.27.1", - "@tiptap/extension-table": "2.27.1", - "@tiptap/extension-table-cell": "2.27.1", - "@tiptap/extension-table-header": "2.27.1", - "@tiptap/extension-table-row": "2.27.1", - "@tiptap/extension-task-item": "2.27.1", - "@tiptap/extension-task-list": "2.27.1", - "@tiptap/extension-text": "2.27.1", - "@tiptap/extension-text-align": "2.27.1", - "@tiptap/extension-text-style": "2.27.1", - "@tiptap/extension-typography": "2.27.1", - "@tiptap/extension-underline": "2.27.1", - "@tiptap/extension-youtube": "2.27.1", - "@tiptap/html": "2.27.1", - "@tiptap/pm": "2.27.1", - "@tiptap/react": "2.27.1", - "@tiptap/starter-kit": "2.27.1", - "@tiptap/suggestion": "2.27.1", + "@tiptap/core": "3.15.3", + "@tiptap/extension-code-block": "3.15.3", + "@tiptap/extension-collaboration": "3.15.3", + "@tiptap/extension-collaboration-caret": "3.15.3", + "@tiptap/extension-color": "3.15.3", + "@tiptap/extension-document": "3.15.3", + "@tiptap/extension-heading": "3.15.3", + "@tiptap/extension-highlight": "3.15.3", + "@tiptap/extension-history": "3.15.3", + "@tiptap/extension-image": "3.15.3", + "@tiptap/extension-link": "3.15.3", + "@tiptap/extension-list": "3.15.3", + "@tiptap/extension-placeholder": "3.15.3", + "@tiptap/extension-subscript": "3.15.3", + "@tiptap/extension-superscript": "3.15.3", + "@tiptap/extension-table": "3.15.3", + "@tiptap/extension-text": "3.15.3", + "@tiptap/extension-text-align": "3.15.3", + "@tiptap/extension-text-style": "3.15.3", + "@tiptap/extension-typography": "3.15.3", + "@tiptap/extension-unique-id": "^3.15.3", + "@tiptap/extension-youtube": "3.15.3", + "@tiptap/html": "3.15.3", + "@tiptap/pm": "3.15.3", + "@tiptap/react": "3.15.3", + "@tiptap/starter-kit": "3.15.3", + "@tiptap/suggestion": "3.15.3", "@types/qrcode": "^1.5.5", "bytes": "^3.1.2", "cross-env": "^7.0.3", "date-fns": "^4.1.0", "dompurify": "^3.2.6", "fractional-indexing-jittered": "^1.0.0", + "highlight.js": "^11.11.1", "ioredis": "^5.4.1", "jszip": "^3.10.1", "linkifyjs": "^4.3.2", @@ -78,7 +73,7 @@ "uuid": "^11.1.0", "y-indexeddb": "^9.0.12", "y-prosemirror": "1.3.7", - "yjs": "^13.6.27" + "yjs": "^13.6.29" }, "devDependencies": { "@nx/js": "20.4.5", diff --git a/packages/editor-ext/src/lib/attachment/attachment.ts b/packages/editor-ext/src/lib/attachment/attachment.ts index 5231c897..bd1814f5 100644 --- a/packages/editor-ext/src/lib/attachment/attachment.ts +++ b/packages/editor-ext/src/lib/attachment/attachment.ts @@ -92,7 +92,7 @@ export const Attachment = Node.create({ mergeAttributes( { "data-type": this.name }, this.options.HTMLAttributes, - HTMLAttributes, + HTMLAttributes ), [ "a", @@ -120,6 +120,9 @@ export const Attachment = Node.create({ }, addNodeView() { + // Force the react node view to render immediately using flush sync (https://github.com/ueberdosis/tiptap/blob/b4db352f839e1d82f9add6ee7fb45561336286d8/packages/react/src/ReactRenderer.tsx#L183-L191) + this.editor.isInitialized = true; + return ReactNodeViewRenderer(this.options.view); }, diff --git a/packages/editor-ext/src/lib/callout/callout.ts b/packages/editor-ext/src/lib/callout/callout.ts index 97c5dfcc..1dc4d800 100644 --- a/packages/editor-ext/src/lib/callout/callout.ts +++ b/packages/editor-ext/src/lib/callout/callout.ts @@ -87,7 +87,7 @@ export const Callout = Node.create({ mergeAttributes( { "data-type": this.name }, this.options.HTMLAttributes, - HTMLAttributes, + HTMLAttributes ), 0, ]; @@ -130,6 +130,9 @@ export const Callout = Node.create({ }, addNodeView() { + // Force the react node view to render immediately using flush sync (https://github.com/ueberdosis/tiptap/blob/b4db352f839e1d82f9add6ee7fb45561336286d8/packages/react/src/ReactRenderer.tsx#L183-L191) + this.editor.isInitialized = true; + return ReactNodeViewRenderer(this.options.view); }, @@ -193,7 +196,7 @@ export const Callout = Node.create({ tr.delete(pos, pos + nodeSize); tr.setSelection( - TextSelection.near(tr.doc.resolve(previousPosition - 1)), + TextSelection.near(tr.doc.resolve(previousPosition - 1)) ); tr.insert(previousPosition - 1, content); diff --git a/packages/editor-ext/src/lib/custom-code-block.ts b/packages/editor-ext/src/lib/custom-code-block.ts deleted file mode 100644 index 702e98a9..00000000 --- a/packages/editor-ext/src/lib/custom-code-block.ts +++ /dev/null @@ -1,81 +0,0 @@ -import CodeBlockLowlight, { - CodeBlockLowlightOptions, -} from "@tiptap/extension-code-block-lowlight"; -import { ReactNodeViewRenderer } from "@tiptap/react"; - -export interface CustomCodeBlockOptions extends CodeBlockLowlightOptions { - view: any; -} - -const TAB_CHAR = "\u00A0\u00A0"; - -export const CustomCodeBlock = CodeBlockLowlight.extend( - { - selectable: true, - - addOptions() { - return { - ...this.parent?.(), - view: null, - }; - }, - - addKeyboardShortcuts() { - return { - ...this.parent?.(), - Tab: () => { - if (this.editor.isActive("codeBlock")) { - this.editor - .chain() - .command(({ tr }) => { - tr.insertText(TAB_CHAR); - return true; - }) - .run(); - return true; - } - }, - "Mod-a": () => { - if (this.editor.isActive("codeBlock")) { - const { state } = this.editor; - const { $from } = state.selection; - - let codeBlockNode = null; - let codeBlockPos = null; - let depth = 0; - - for (depth = $from.depth; depth > 0; depth--) { - const node = $from.node(depth); - if (node.type.name === "codeBlock") { - codeBlockNode = node; - codeBlockPos = $from.start(depth) - 1; - break; - } - } - - if (codeBlockNode && codeBlockPos !== null) { - const codeBlockStart = codeBlockPos; - const codeBlockEnd = codeBlockPos + codeBlockNode.nodeSize; - - const contentStart = codeBlockStart + 1; - const contentEnd = codeBlockEnd - 1; - - this.editor.commands.setTextSelection({ - from: contentStart, - to: contentEnd, - }); - - return true; - } - } - - return false; - }, - }; - }, - - addNodeView() { - return ReactNodeViewRenderer(this.options.view); - }, - } -); diff --git a/packages/editor-ext/src/lib/custom-code-block/custom-code-block.ts b/packages/editor-ext/src/lib/custom-code-block/custom-code-block.ts new file mode 100644 index 00000000..ba9fe9c1 --- /dev/null +++ b/packages/editor-ext/src/lib/custom-code-block/custom-code-block.ts @@ -0,0 +1,108 @@ +import type { CodeBlockOptions } from "@tiptap/extension-code-block"; +import CodeBlock from "@tiptap/extension-code-block"; + +import { LowlightPlugin } from "./lowlight-plugin.js"; +import { ReactNodeViewRenderer } from "@tiptap/react"; + +export interface CodeBlockLowlightOptions extends CodeBlockOptions { + /** + * The lowlight instance. + */ + lowlight: any; + view: any; +} + +const TAB_CHAR = "\u00A0\u00A0"; + +/** + * This extension allows you to highlight code blocks with lowlight. + * @see https://tiptap.dev/api/nodes/code-block-lowlight + */ +export const CustomCodeBlock = CodeBlock.extend({ + selectable: true, + + addOptions() { + return { + ...this.parent?.(), + lowlight: {}, + languageClassPrefix: "language-", + exitOnTripleEnter: true, + exitOnArrowDown: true, + defaultLanguage: null, + HTMLAttributes: {}, + view: null, + }; + }, + + addKeyboardShortcuts() { + return { + ...this.parent?.(), + Tab: () => { + if (this.editor.isActive("codeBlock")) { + this.editor + .chain() + .command(({ tr }) => { + tr.insertText(TAB_CHAR); + return true; + }) + .run(); + return true; + } + }, + "Mod-a": () => { + if (this.editor.isActive("codeBlock")) { + const { state } = this.editor; + const { $from } = state.selection; + + let codeBlockNode = null; + let codeBlockPos = null; + let depth = 0; + + for (depth = $from.depth; depth > 0; depth--) { + const node = $from.node(depth); + if (node.type.name === "codeBlock") { + codeBlockNode = node; + codeBlockPos = $from.start(depth) - 1; + break; + } + } + + if (codeBlockNode && codeBlockPos !== null) { + const codeBlockStart = codeBlockPos; + const codeBlockEnd = codeBlockPos + codeBlockNode.nodeSize; + + const contentStart = codeBlockStart + 1; + const contentEnd = codeBlockEnd - 1; + + this.editor.commands.setTextSelection({ + from: contentStart, + to: contentEnd, + }); + + return true; + } + } + + return false; + }, + }; + }, + + addNodeView() { + // Force the react node view to render immediately using flush sync (https://github.com/ueberdosis/tiptap/blob/b4db352f839e1d82f9add6ee7fb45561336286d8/packages/react/src/ReactRenderer.tsx#L183-L191) + this.editor.isInitialized = true; + + return ReactNodeViewRenderer(this.options.view); + }, + + addProseMirrorPlugins() { + return [ + ...(this.parent?.() || []), + LowlightPlugin({ + name: this.name, + lowlight: this.options.lowlight, + defaultLanguage: this.options.defaultLanguage, + }), + ]; + }, +}); diff --git a/packages/editor-ext/src/lib/custom-code-block/index.ts b/packages/editor-ext/src/lib/custom-code-block/index.ts new file mode 100644 index 00000000..f6e3470f --- /dev/null +++ b/packages/editor-ext/src/lib/custom-code-block/index.ts @@ -0,0 +1 @@ +export { CustomCodeBlock } from "./custom-code-block"; diff --git a/packages/editor-ext/src/lib/custom-code-block/lowlight-plugin.ts b/packages/editor-ext/src/lib/custom-code-block/lowlight-plugin.ts new file mode 100644 index 00000000..505b8f20 --- /dev/null +++ b/packages/editor-ext/src/lib/custom-code-block/lowlight-plugin.ts @@ -0,0 +1,159 @@ +import { findChildren } from '@tiptap/core' +import type { Node as ProsemirrorNode } from '@tiptap/pm/model' +import { Plugin, PluginKey } from '@tiptap/pm/state' +import { Decoration, DecorationSet } from '@tiptap/pm/view' +// @ts-ignore +import highlight from 'highlight.js/lib/core' + +function parseNodes(nodes: any[], className: string[] = []): { text: string; classes: string[] }[] { + return nodes + .map(node => { + const classes = [...className, ...(node.properties ? node.properties.className : [])] + + if (node.children) { + return parseNodes(node.children, classes) + } + + return { + text: node.value, + classes, + } + }) + .flat() +} + +function getHighlightNodes(result: any) { + // `.value` for lowlight v1, `.children` for lowlight v2 + return result.value || result.children || [] +} + +function registered(aliasOrLanguage: string) { + return Boolean(highlight.getLanguage(aliasOrLanguage)) +} + +function getDecorations({ + doc, + name, + lowlight, + defaultLanguage, +}: { + doc: ProsemirrorNode + name: string + lowlight: any + defaultLanguage: string | null | undefined +}) { + const decorations: Decoration[] = [] + + findChildren(doc, node => node.type.name === name).forEach(block => { + let from = block.pos + 1 + const language = block.node.attrs.language || defaultLanguage + const languages = lowlight.listLanguages() + + const nodes = + language && (languages.includes(language) || registered(language) || lowlight.registered?.(language)) + ? getHighlightNodes(lowlight.highlight(language, block.node.textContent)) + : getHighlightNodes(lowlight.highlightAuto(block.node.textContent)) + + parseNodes(nodes).forEach(node => { + const to = from + node.text.length + + if (node.classes.length) { + const decoration = Decoration.inline(from, to, { + class: node.classes.join(' '), + }) + + decorations.push(decoration) + } + + from = to + }) + }) + + return DecorationSet.create(doc, decorations) +} + +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type +function isFunction(param: any): param is Function { + return typeof param === 'function' +} + +export function LowlightPlugin({ + name, + lowlight, + defaultLanguage, +}: { + name: string + lowlight: any + defaultLanguage: string | null | undefined +}) { + if (!['highlight', 'highlightAuto', 'listLanguages'].every(api => isFunction(lowlight[api]))) { + throw Error('You should provide an instance of lowlight to use the code-block-lowlight extension') + } + + const lowlightPlugin: Plugin = new Plugin({ + key: new PluginKey('lowlight'), + + state: { + init: (_, { doc }) => + getDecorations({ + doc, + name, + lowlight, + defaultLanguage, + }), + apply: (transaction, decorationSet, oldState, newState) => { + const oldNodeName = oldState.selection.$head.parent.type.name + const newNodeName = newState.selection.$head.parent.type.name + const oldNodes = findChildren(oldState.doc, node => node.type.name === name) + const newNodes = findChildren(newState.doc, node => node.type.name === name) + + if ( + transaction.docChanged && + // Apply decorations if: + // selection includes named node, + ([oldNodeName, newNodeName].includes(name) || + // OR transaction adds/removes named node, + newNodes.length !== oldNodes.length || + // OR transaction has changes that completely encapsulte a node + // (for example, a transaction that affects the entire document). + // Such transactions can happen during collab syncing via y-prosemirror, for example. + transaction.steps.some(step => { + // @ts-ignore + return ( + // @ts-ignore + step.from !== undefined && + // @ts-ignore + step.to !== undefined && + oldNodes.some(node => { + // @ts-ignore + return ( + // @ts-ignore + node.pos >= step.from && + // @ts-ignore + node.pos + node.node.nodeSize <= step.to + ) + }) + ) + })) + ) { + return getDecorations({ + doc: transaction.doc, + name, + lowlight, + defaultLanguage, + }) + } + + return decorationSet.map(transaction.mapping, transaction.doc) + }, + }, + + props: { + decorations(state) { + return lowlightPlugin.getState(state) + }, + }, + }) + + return lowlightPlugin +} \ No newline at end of file diff --git a/packages/editor-ext/src/lib/details/details.ts b/packages/editor-ext/src/lib/details/details.ts index b28c4de7..41c66dca 100644 --- a/packages/editor-ext/src/lib/details/details.ts +++ b/packages/editor-ext/src/lib/details/details.ts @@ -27,6 +27,7 @@ export const Details = Node.create({ content: "detailsSummary detailsContent", defining: true, isolating: true, + // @ts-ignore allowGapCursor: false, addOptions() { return { diff --git a/packages/editor-ext/src/lib/drawio.ts b/packages/editor-ext/src/lib/drawio.ts index 319853b2..3cc041a2 100644 --- a/packages/editor-ext/src/lib/drawio.ts +++ b/packages/editor-ext/src/lib/drawio.ts @@ -41,45 +41,45 @@ export const Drawio = Node.create({ addAttributes() { return { src: { - default: '', - parseHTML: (element) => element.getAttribute('data-src'), + default: "", + parseHTML: (element) => element.getAttribute("data-src"), renderHTML: (attributes) => ({ - 'data-src': attributes.src, + "data-src": attributes.src, }), }, title: { default: undefined, - parseHTML: (element) => element.getAttribute('data-title'), + parseHTML: (element) => element.getAttribute("data-title"), renderHTML: (attributes: DrawioAttributes) => ({ - 'data-title': attributes.title, + "data-title": attributes.title, }), }, width: { - default: '100%', - parseHTML: (element) => element.getAttribute('data-width'), + default: "100%", + parseHTML: (element) => element.getAttribute("data-width"), renderHTML: (attributes: DrawioAttributes) => ({ - 'data-width': attributes.width, + "data-width": attributes.width, }), }, size: { default: null, - parseHTML: (element) => element.getAttribute('data-size'), + parseHTML: (element) => element.getAttribute("data-size"), renderHTML: (attributes: DrawioAttributes) => ({ - 'data-size': attributes.size, + "data-size": attributes.size, }), }, align: { - default: 'center', - parseHTML: (element) => element.getAttribute('data-align'), + default: "center", + parseHTML: (element) => element.getAttribute("data-align"), renderHTML: (attributes: DrawioAttributes) => ({ - 'data-align': attributes.align, + "data-align": attributes.align, }), }, attachmentId: { default: undefined, - parseHTML: (element) => element.getAttribute('data-attachment-id'), + parseHTML: (element) => element.getAttribute("data-attachment-id"), renderHTML: (attributes: DrawioAttributes) => ({ - 'data-attachment-id': attributes.attachmentId, + "data-attachment-id": attributes.attachmentId, }), }, }; @@ -95,13 +95,20 @@ export const Drawio = Node.create({ renderHTML({ HTMLAttributes }) { return [ - 'div', + "div", mergeAttributes( - { 'data-type': this.name }, + { "data-type": this.name }, this.options.HTMLAttributes, HTMLAttributes ), - ['img', { src: HTMLAttributes['data-src'], alt: HTMLAttributes['data-title'], width: HTMLAttributes['data-width'] }], + [ + "img", + { + src: HTMLAttributes["data-src"], + alt: HTMLAttributes["data-title"], + width: HTMLAttributes["data-width"], + }, + ], ]; }, @@ -119,6 +126,9 @@ export const Drawio = Node.create({ }, addNodeView() { + // Force the react node view to render immediately using flush sync (https://github.com/ueberdosis/tiptap/blob/b4db352f839e1d82f9add6ee7fb45561336286d8/packages/react/src/ReactRenderer.tsx#L183-L191) + this.editor.isInitialized = true; + return ReactNodeViewRenderer(this.options.view); }, }); diff --git a/packages/editor-ext/src/lib/embed.ts b/packages/editor-ext/src/lib/embed.ts index 47fc251e..a93648b1 100644 --- a/packages/editor-ext/src/lib/embed.ts +++ b/packages/editor-ext/src/lib/embed.ts @@ -1,6 +1,6 @@ -import { Node, mergeAttributes } from '@tiptap/core'; -import { ReactNodeViewRenderer } from '@tiptap/react'; -import { sanitizeUrl } from './utils'; +import { Node, mergeAttributes } from "@tiptap/core"; +import { ReactNodeViewRenderer } from "@tiptap/react"; +import { sanitizeUrl } from "./utils"; export interface EmbedOptions { HTMLAttributes: Record; @@ -14,7 +14,7 @@ export interface EmbedAttributes { height?: number; } -declare module '@tiptap/core' { +declare module "@tiptap/core" { interface Commands { embeds: { setEmbed: (attributes?: EmbedAttributes) => ReturnType; @@ -23,9 +23,9 @@ declare module '@tiptap/core' { } export const Embed = Node.create({ - name: 'embed', + name: "embed", inline: false, - group: 'block', + group: "block", isolating: true, atom: true, defining: true, @@ -40,41 +40,41 @@ export const Embed = Node.create({ addAttributes() { return { src: { - default: '', + default: "", parseHTML: (element) => { - const src = element.getAttribute('data-src'); + const src = element.getAttribute("data-src"); return sanitizeUrl(src); }, renderHTML: (attributes: EmbedAttributes) => ({ - 'data-src': sanitizeUrl(attributes.src), + "data-src": sanitizeUrl(attributes.src), }), }, provider: { - default: '', - parseHTML: (element) => element.getAttribute('data-provider'), + default: "", + parseHTML: (element) => element.getAttribute("data-provider"), renderHTML: (attributes: EmbedAttributes) => ({ - 'data-provider': attributes.provider, + "data-provider": attributes.provider, }), }, align: { - default: 'center', - parseHTML: (element) => element.getAttribute('data-align'), + default: "center", + parseHTML: (element) => element.getAttribute("data-align"), renderHTML: (attributes: EmbedAttributes) => ({ - 'data-align': attributes.align, + "data-align": attributes.align, }), }, width: { default: 640, - parseHTML: (element) => element.getAttribute('data-width'), + parseHTML: (element) => element.getAttribute("data-width"), renderHTML: (attributes: EmbedAttributes) => ({ - 'data-width': attributes.width, + "data-width": attributes.width, }), }, height: { default: 480, - parseHTML: (element) => element.getAttribute('data-height'), + parseHTML: (element) => element.getAttribute("data-height"), renderHTML: (attributes: EmbedAttributes) => ({ - 'data-height': attributes.height, + "data-height": attributes.height, }), }, }; @@ -91,13 +91,13 @@ export const Embed = Node.create({ renderHTML({ HTMLAttributes }) { const src = HTMLAttributes["data-src"]; const safeHref = sanitizeUrl(src); - + return [ "div", mergeAttributes( { "data-type": this.name }, this.options.HTMLAttributes, - HTMLAttributes, + HTMLAttributes ), [ "a", @@ -120,9 +120,9 @@ export const Embed = Node.create({ ...attrs, src: sanitizeUrl(attrs.src), }; - + return commands.insertContent({ - type: 'embed', + type: "embed", attrs: validatedAttrs, }); }, @@ -130,6 +130,9 @@ export const Embed = Node.create({ }, addNodeView() { + // Force the react node view to render immediately using flush sync (https://github.com/ueberdosis/tiptap/blob/b4db352f839e1d82f9add6ee7fb45561336286d8/packages/react/src/ReactRenderer.tsx#L183-L191) + this.editor.isInitialized = true; + return ReactNodeViewRenderer(this.options.view); }, }); diff --git a/packages/editor-ext/src/lib/excalidraw.ts b/packages/editor-ext/src/lib/excalidraw.ts index a7e3a468..28b064e4 100644 --- a/packages/editor-ext/src/lib/excalidraw.ts +++ b/packages/editor-ext/src/lib/excalidraw.ts @@ -1,5 +1,5 @@ -import { Node, mergeAttributes } from '@tiptap/core'; -import { ReactNodeViewRenderer } from '@tiptap/react'; +import { Node, mergeAttributes } from "@tiptap/core"; +import { ReactNodeViewRenderer } from "@tiptap/react"; export interface ExcalidrawOptions { HTMLAttributes: Record; @@ -14,7 +14,7 @@ export interface ExcalidrawAttributes { attachmentId?: string; } -declare module '@tiptap/core' { +declare module "@tiptap/core" { interface Commands { excalidraw: { setExcalidraw: (attributes?: ExcalidrawAttributes) => ReturnType; @@ -23,9 +23,9 @@ declare module '@tiptap/core' { } export const Excalidraw = Node.create({ - name: 'excalidraw', + name: "excalidraw", inline: false, - group: 'block', + group: "block", isolating: true, atom: true, defining: true, @@ -40,45 +40,45 @@ export const Excalidraw = Node.create({ addAttributes() { return { src: { - default: '', - parseHTML: (element) => element.getAttribute('data-src'), + default: "", + parseHTML: (element) => element.getAttribute("data-src"), renderHTML: (attributes) => ({ - 'data-src': attributes.src, + "data-src": attributes.src, }), }, title: { default: undefined, - parseHTML: (element) => element.getAttribute('data-title'), + parseHTML: (element) => element.getAttribute("data-title"), renderHTML: (attributes: ExcalidrawAttributes) => ({ - 'data-title': attributes.title, + "data-title": attributes.title, }), }, width: { - default: '100%', - parseHTML: (element) => element.getAttribute('data-width'), + default: "100%", + parseHTML: (element) => element.getAttribute("data-width"), renderHTML: (attributes: ExcalidrawAttributes) => ({ - 'data-width': attributes.width, + "data-width": attributes.width, }), }, size: { default: null, - parseHTML: (element) => element.getAttribute('data-size'), + parseHTML: (element) => element.getAttribute("data-size"), renderHTML: (attributes: ExcalidrawAttributes) => ({ - 'data-size': attributes.size, + "data-size": attributes.size, }), }, align: { - default: 'center', - parseHTML: (element) => element.getAttribute('data-align'), + default: "center", + parseHTML: (element) => element.getAttribute("data-align"), renderHTML: (attributes: ExcalidrawAttributes) => ({ - 'data-align': attributes.align, + "data-align": attributes.align, }), }, attachmentId: { default: undefined, - parseHTML: (element) => element.getAttribute('data-attachment-id'), + parseHTML: (element) => element.getAttribute("data-attachment-id"), renderHTML: (attributes: ExcalidrawAttributes) => ({ - 'data-attachment-id': attributes.attachmentId, + "data-attachment-id": attributes.attachmentId, }), }, }; @@ -94,13 +94,20 @@ export const Excalidraw = Node.create({ renderHTML({ HTMLAttributes }) { return [ - 'div', + "div", mergeAttributes( - { 'data-type': this.name }, + { "data-type": this.name }, this.options.HTMLAttributes, HTMLAttributes ), - ['img', { src: HTMLAttributes['data-src'], alt: HTMLAttributes['data-title'], width: HTMLAttributes['data-width'] }], + [ + "img", + { + src: HTMLAttributes["data-src"], + alt: HTMLAttributes["data-title"], + width: HTMLAttributes["data-width"], + }, + ], ]; }, @@ -110,7 +117,7 @@ export const Excalidraw = Node.create({ (attrs: ExcalidrawAttributes) => ({ commands }) => { return commands.insertContent({ - type: 'excalidraw', + type: "excalidraw", attrs: attrs, }); }, @@ -118,6 +125,9 @@ export const Excalidraw = Node.create({ }, addNodeView() { + // Force the react node view to render immediately using flush sync (https://github.com/ueberdosis/tiptap/blob/b4db352f839e1d82f9add6ee7fb45561336286d8/packages/react/src/ReactRenderer.tsx#L183-L191) + this.editor.isInitialized = true; + return ReactNodeViewRenderer(this.options.view); }, }); diff --git a/packages/editor-ext/src/lib/image/image.ts b/packages/editor-ext/src/lib/image/image.ts index 3f7683e4..cc8ba220 100644 --- a/packages/editor-ext/src/lib/image/image.ts +++ b/packages/editor-ext/src/lib/image/image.ts @@ -22,7 +22,7 @@ declare module "@tiptap/core" { imageBlock: { setImage: (attributes: ImageAttributes) => ReturnType; setImageAt: ( - attributes: ImageAttributes & { pos: number | Range }, + attributes: ImageAttributes & { pos: number | Range } ) => ReturnType; setImageAlign: (align: "left" | "center" | "right") => ReturnType; setImageWidth: (width: number) => ReturnType; @@ -135,6 +135,9 @@ export const TiptapImage = Image.extend({ }, addNodeView() { + // Force the react node view to render immediately using flush sync (https://github.com/ueberdosis/tiptap/blob/b4db352f839e1d82f9add6ee7fb45561336286d8/packages/react/src/ReactRenderer.tsx#L183-L191) + this.editor.isInitialized = true; + return ReactNodeViewRenderer(this.options.view); }, diff --git a/packages/editor-ext/src/lib/math/math-block.ts b/packages/editor-ext/src/lib/math/math-block.ts index a580596b..cf11e8f8 100644 --- a/packages/editor-ext/src/lib/math/math-block.ts +++ b/packages/editor-ext/src/lib/math/math-block.ts @@ -63,6 +63,9 @@ export const MathBlock = Node.create({ }, addNodeView() { + // Force the react node view to render immediately using flush sync (https://github.com/ueberdosis/tiptap/blob/b4db352f839e1d82f9add6ee7fb45561336286d8/packages/react/src/ReactRenderer.tsx#L183-L191) + this.editor.isInitialized = true; + return ReactNodeViewRenderer(this.options.view); }, diff --git a/packages/editor-ext/src/lib/math/math-inline.ts b/packages/editor-ext/src/lib/math/math-inline.ts index 39c1cd49..3de9d291 100644 --- a/packages/editor-ext/src/lib/math/math-inline.ts +++ b/packages/editor-ext/src/lib/math/math-inline.ts @@ -64,6 +64,9 @@ export const MathInline = Node.create({ }, addNodeView() { + // Force the react node view to render immediately using flush sync (https://github.com/ueberdosis/tiptap/blob/b4db352f839e1d82f9add6ee7fb45561336286d8/packages/react/src/ReactRenderer.tsx#L183-L191) + this.editor.isInitialized = true; + return ReactNodeViewRenderer(this.options.view); }, diff --git a/packages/editor-ext/src/lib/search-and-replace/search-and-replace.ts b/packages/editor-ext/src/lib/search-and-replace/search-and-replace.ts index ca66958f..1ed7632d 100644 --- a/packages/editor-ext/src/lib/search-and-replace/search-and-replace.ts +++ b/packages/editor-ext/src/lib/search-and-replace/search-and-replace.ts @@ -31,6 +31,9 @@ import { import { Node as PMNode, Mark } from "@tiptap/pm/model"; declare module "@tiptap/core" { + interface Storage { + searchAndReplace: SearchAndReplaceStorage; + } interface Commands { search: { /** @@ -184,21 +187,21 @@ const replace = ( if (dispatch) { const tr = state.tr; - + // Get all marks that span the text being replaced const marksSet = new Set(); state.doc.nodesBetween(from, to, (node) => { if (node.isText && node.marks) { - node.marks.forEach(mark => marksSet.add(mark)); + node.marks.forEach((mark) => marksSet.add(mark)); } }); - + const marks = Array.from(marksSet); - + // Delete the old text and insert new text with preserved marks tr.delete(from, to); tr.insert(from, state.schema.text(replaceTerm, marks)); - + dispatch(tr); } }; @@ -215,17 +218,17 @@ const replaceAll = ( // Process replacements in reverse order to avoid position shifting issues for (let i = resultsCopy.length - 1; i >= 0; i -= 1) { const { from, to } = resultsCopy[i]; - + // Get all marks that span the text being replaced const marksSet = new Set(); tr.doc.nodesBetween(from, to, (node) => { if (node.isText && node.marks) { - node.marks.forEach(mark => marksSet.add(mark)); + node.marks.forEach((mark) => marksSet.add(mark)); } }); - + const marks = Array.from(marksSet); - + // Delete and insert with preserved marks tr.delete(from, to); tr.insert(from, tr.doc.type.schema.text(replaceTerm, marks)); @@ -352,10 +355,17 @@ export const SearchAndReplace = Extension.create< // The results will be recalculated by the plugin, but we need to ensure // the index doesn't exceed the new bounds setTimeout(() => { - const newResultsLength = editor.storage.searchAndReplace.results.length; - if (newResultsLength > 0 && editor.storage.searchAndReplace.resultIndex >= newResultsLength) { + const newResultsLength = + editor.storage.searchAndReplace.results.length; + if ( + newResultsLength > 0 && + editor.storage.searchAndReplace.resultIndex >= newResultsLength + ) { // Keep the same position if possible, otherwise go to the last result - editor.storage.searchAndReplace.resultIndex = Math.min(resultIndex, newResultsLength - 1); + editor.storage.searchAndReplace.resultIndex = Math.min( + resultIndex, + newResultsLength - 1, + ); } }, 0); diff --git a/packages/editor-ext/src/lib/subpages/subpages.ts b/packages/editor-ext/src/lib/subpages/subpages.ts index 59eb9896..617f43ce 100644 --- a/packages/editor-ext/src/lib/subpages/subpages.ts +++ b/packages/editor-ext/src/lib/subpages/subpages.ts @@ -44,7 +44,7 @@ export const Subpages = Node.create({ mergeAttributes( { "data-type": this.name }, this.options.HTMLAttributes, - HTMLAttributes, + HTMLAttributes ), ]; }, @@ -63,6 +63,9 @@ export const Subpages = Node.create({ }, addNodeView() { + // Force the react node view to render immediately using flush sync (https://github.com/ueberdosis/tiptap/blob/b4db352f839e1d82f9add6ee7fb45561336286d8/packages/react/src/ReactRenderer.tsx#L183-L191) + this.editor.isInitialized = true; + return ReactNodeViewRenderer(this.options.view); }, }); diff --git a/packages/editor-ext/src/lib/table/cell.ts b/packages/editor-ext/src/lib/table/cell.ts index 63df7dcf..2f693573 100644 --- a/packages/editor-ext/src/lib/table/cell.ts +++ b/packages/editor-ext/src/lib/table/cell.ts @@ -1,4 +1,4 @@ -import { TableCell as TiptapTableCell } from "@tiptap/extension-table-cell"; +import { TableCell as TiptapTableCell } from "@tiptap/extension-table"; export const TableCell = TiptapTableCell.extend({ name: "tableCell", diff --git a/packages/editor-ext/src/lib/table/dnd/dnd-extension.ts b/packages/editor-ext/src/lib/table/dnd/dnd-extension.ts index b4ac2950..1ad57ec1 100644 --- a/packages/editor-ext/src/lib/table/dnd/dnd-extension.ts +++ b/packages/editor-ext/src/lib/table/dnd/dnd-extension.ts @@ -1,7 +1,12 @@ import { Editor, Extension } from "@tiptap/core"; import { PluginKey, Plugin, PluginSpec } from "@tiptap/pm/state"; import { EditorProps, EditorView } from "@tiptap/pm/view"; -import { DraggingDOMs, getDndRelatedDOMs, getHoveringCell, HoveringCellInfo } from "./utils"; +import { + DraggingDOMs, + getDndRelatedDOMs, + getHoveringCell, + HoveringCellInfo, +} from "./utils"; import { getDragOverColumn, getDragOverRow } from "./calc-drag-over"; import { moveColumn, moveRow } from "../utils"; import { PreviewController } from "./preview/preview-controller"; @@ -10,268 +15,302 @@ import { DragHandleController } from "./handle/drag-handle-controller"; import { EmptyImageController } from "./handle/empty-image-controller"; import { AutoScrollController } from "./auto-scroll-controller"; -export const TableDndKey = new PluginKey('table-drag-and-drop') +export const TableDndKey = new PluginKey("table-drag-and-drop"); class TableDragHandlePluginSpec implements PluginSpec { - key = TableDndKey - props: EditorProps> + key = TableDndKey; + props: EditorProps>; - private _colDragHandle: HTMLElement; - private _rowDragHandle: HTMLElement; - private _hoveringCell?: HoveringCellInfo; - private _disposables: (() => void)[] = []; - private _draggingCoords: { x: number; y: number } = { x: 0, y: 0 }; - private _dragging = false; - private _draggingDirection: 'col' | 'row' = 'col'; - private _draggingIndex = -1; - private _droppingIndex = -1; - private _draggingDOMs?: DraggingDOMs | undefined - private _startCoords: { x: number; y: number } = { x: 0, y: 0 }; - private _previewController: PreviewController; - private _dropIndicatorController: DropIndicatorController; - private _dragHandleController: DragHandleController; - private _emptyImageController: EmptyImageController; - private _autoScrollController: AutoScrollController; + private _colDragHandle: HTMLElement; + private _rowDragHandle: HTMLElement; + private _hoveringCell?: HoveringCellInfo; + private _disposables: (() => void)[] = []; + private _draggingCoords: { x: number; y: number } = { x: 0, y: 0 }; + private _dragging = false; + private _draggingDirection: "col" | "row" = "col"; + private _draggingIndex = -1; + private _droppingIndex = -1; + private _draggingDOMs?: DraggingDOMs | undefined; + private _startCoords: { x: number; y: number } = { x: 0, y: 0 }; + private _previewController: PreviewController; + private _dropIndicatorController: DropIndicatorController; + private _dragHandleController: DragHandleController; + private _emptyImageController: EmptyImageController; + private _autoScrollController: AutoScrollController; - constructor(public editor: Editor) { - this.props = { - handleDOMEvents: { - pointerover: this._pointerOver, - } - } + constructor(public editor: Editor) { + this.props = { + handleDOMEvents: { + pointerover: this._pointerOver, + }, + }; - this._dragHandleController = new DragHandleController(); - this._colDragHandle = this._dragHandleController.colDragHandle; - this._rowDragHandle = this._dragHandleController.rowDragHandle; + this._dragHandleController = new DragHandleController(); + this._colDragHandle = this._dragHandleController.colDragHandle; + this._rowDragHandle = this._dragHandleController.rowDragHandle; - this._previewController = new PreviewController(); - this._dropIndicatorController = new DropIndicatorController(); - this._emptyImageController = new EmptyImageController(); + this._previewController = new PreviewController(); + this._dropIndicatorController = new DropIndicatorController(); + this._emptyImageController = new EmptyImageController(); - this._autoScrollController = new AutoScrollController(); + this._autoScrollController = new AutoScrollController(); - this._bindDragEvents(); + this._bindDragEvents(); + } + + view = () => { + const wrapper = this.editor.options.element; + //@ts-ignore + wrapper.appendChild(this._colDragHandle); + //@ts-ignore + wrapper.appendChild(this._rowDragHandle); + //@ts-ignore + wrapper.appendChild(this._previewController.previewRoot); + //@ts-ignore + wrapper.appendChild(this._dropIndicatorController.dropIndicatorRoot); + + return { + update: this.update, + destroy: this.destroy, + }; + }; + + update = () => {}; + + destroy = () => { + if (!this.editor.isDestroyed) return; + this._dragHandleController.destroy(); + this._emptyImageController.destroy(); + this._previewController.destroy(); + this._dropIndicatorController.destroy(); + this._autoScrollController.stop(); + + this._disposables.forEach((disposable) => disposable()); + }; + + private _pointerOver = (view: EditorView, event: PointerEvent) => { + if (this._dragging) return; + + // Don't show drag handles in readonly mode + if (!this.editor.isEditable) { + this._dragHandleController.hide(); + return; } - view = () => { - const wrapper = this.editor.options.element; - wrapper.appendChild(this._colDragHandle) - wrapper.appendChild(this._rowDragHandle) - wrapper.appendChild(this._previewController.previewRoot) - wrapper.appendChild(this._dropIndicatorController.dropIndicatorRoot) + const hoveringCell = getHoveringCell(view, event); + this._hoveringCell = hoveringCell; + if (!hoveringCell) { + this._dragHandleController.hide(); + } else { + this._dragHandleController.show(this.editor, hoveringCell); + } + }; - return { - update: this.update, - destroy: this.destroy, - } + private _onDragColStart = (event: DragEvent) => { + this._onDragStart(event, "col"); + }; + + private _onDraggingCol = (event: DragEvent) => { + const draggingDOMs = this._draggingDOMs; + if (!draggingDOMs) return; + + this._draggingCoords = { x: event.clientX, y: event.clientY }; + this._previewController.onDragging( + draggingDOMs, + this._draggingCoords.x, + this._draggingCoords.y, + "col", + ); + + this._autoScrollController.checkXAutoScroll(event.clientX, draggingDOMs); + + const direction = + this._startCoords.x > this._draggingCoords.x ? "left" : "right"; + const dragOverColumn = getDragOverColumn( + draggingDOMs.table, + this._draggingCoords.x, + ); + if (!dragOverColumn) return; + + const [col, index] = dragOverColumn; + this._droppingIndex = index; + this._dropIndicatorController.onDragging(col, direction, "col"); + }; + + private _onDragRowStart = (event: DragEvent) => { + this._onDragStart(event, "row"); + }; + + private _onDraggingRow = (event: DragEvent) => { + const draggingDOMs = this._draggingDOMs; + if (!draggingDOMs) return; + + this._draggingCoords = { x: event.clientX, y: event.clientY }; + this._previewController.onDragging( + draggingDOMs, + this._draggingCoords.x, + this._draggingCoords.y, + "row", + ); + + this._autoScrollController.checkYAutoScroll(event.clientY); + + const direction = + this._startCoords.y > this._draggingCoords.y ? "up" : "down"; + const dragOverRow = getDragOverRow( + draggingDOMs.table, + this._draggingCoords.y, + ); + if (!dragOverRow) return; + + const [row, index] = dragOverRow; + this._droppingIndex = index; + this._dropIndicatorController.onDragging(row, direction, "row"); + }; + + private _onDragEnd = () => { + this._dragging = false; + this._draggingIndex = -1; + this._droppingIndex = -1; + this._startCoords = { x: 0, y: 0 }; + this._autoScrollController.stop(); + this._dropIndicatorController.onDragEnd(); + this._previewController.onDragEnd(); + }; + + private _bindDragEvents = () => { + this._colDragHandle.addEventListener("dragstart", this._onDragColStart); + this._disposables.push(() => { + this._colDragHandle.removeEventListener( + "dragstart", + this._onDragColStart, + ); + }); + + this._colDragHandle.addEventListener("dragend", this._onDragEnd); + this._disposables.push(() => { + this._colDragHandle.removeEventListener("dragend", this._onDragEnd); + }); + + this._rowDragHandle.addEventListener("dragstart", this._onDragRowStart); + this._disposables.push(() => { + this._rowDragHandle.removeEventListener( + "dragstart", + this._onDragRowStart, + ); + }); + + this._rowDragHandle.addEventListener("dragend", this._onDragEnd); + this._disposables.push(() => { + this._rowDragHandle.removeEventListener("dragend", this._onDragEnd); + }); + + const ownerDocument = this.editor.view.dom?.ownerDocument; + if (ownerDocument) { + // To make `drop` event work, we need to prevent the default behavior of the + // `dragover` event for drop zone. Here we set the whole document as the + // drop zone so that even the mouse moves outside the editor, the `drop` + // event will still be triggered. + ownerDocument.addEventListener("drop", this._onDrop); + ownerDocument.addEventListener("dragover", this._onDrag); + this._disposables.push(() => { + ownerDocument.removeEventListener("drop", this._onDrop); + ownerDocument.removeEventListener("dragover", this._onDrag); + }); + } + }; + + private _onDragStart = (event: DragEvent, type: "col" | "row") => { + const dataTransfer = event.dataTransfer; + if (dataTransfer) { + dataTransfer.effectAllowed = "move"; + this._emptyImageController.hideDragImage(dataTransfer); + } + this._dragging = true; + this._draggingDirection = type; + this._startCoords = { x: event.clientX, y: event.clientY }; + const draggingIndex = + (type === "col" + ? this._hoveringCell?.colIndex + : this._hoveringCell?.rowIndex) ?? 0; + + this._draggingIndex = draggingIndex; + + const relatedDoms = getDndRelatedDOMs( + this.editor.view, + this._hoveringCell?.cellPos, + draggingIndex, + type, + ); + this._draggingDOMs = relatedDoms; + + const index = + type === "col" + ? this._hoveringCell?.colIndex + : this._hoveringCell?.rowIndex; + + this._previewController.onDragStart(relatedDoms, index, type); + this._dropIndicatorController.onDragStart(relatedDoms, type); + }; + + private _onDrag = (event: DragEvent) => { + event.preventDefault(); + if (!this._dragging) return; + if (this._draggingDirection === "col") { + this._onDraggingCol(event); + } else { + this._onDraggingRow(event); + } + }; + + private _onDrop = () => { + if (!this._dragging) return; + const direction = this._draggingDirection; + const from = this._draggingIndex; + const to = this._droppingIndex; + const tr = this.editor.state.tr; + const pos = this.editor.state.selection.from; + + if (direction === "col") { + const canMove = moveColumn({ + tr, + originIndex: from, + targetIndex: to, + select: true, + pos, + }); + if (canMove) { + this.editor.view.dispatch(tr); + } + + return; } - update = () => {} + if (direction === "row") { + const canMove = moveRow({ + tr, + originIndex: from, + targetIndex: to, + select: true, + pos, + }); + if (canMove) { + this.editor.view.dispatch(tr); + } - destroy = () => { - if (!this.editor.isDestroyed) return; - this._dragHandleController.destroy(); - this._emptyImageController.destroy(); - this._previewController.destroy(); - this._dropIndicatorController.destroy(); - this._autoScrollController.stop(); - - this._disposables.forEach(disposable => disposable()); - } - - private _pointerOver = (view: EditorView, event: PointerEvent) => { - if (this._dragging) return; - - // Don't show drag handles in readonly mode - if (!this.editor.isEditable) { - this._dragHandleController.hide(); - return; - } - - const hoveringCell = getHoveringCell(view, event) - this._hoveringCell = hoveringCell; - if (!hoveringCell) { - this._dragHandleController.hide(); - } else { - this._dragHandleController.show(this.editor, hoveringCell); - } - } - - private _onDragColStart = (event: DragEvent) => { - this._onDragStart(event, 'col'); - } - - private _onDraggingCol = (event: DragEvent) => { - const draggingDOMs = this._draggingDOMs; - if (!draggingDOMs) return; - - this._draggingCoords = { x: event.clientX, y: event.clientY }; - this._previewController.onDragging(draggingDOMs, this._draggingCoords.x, this._draggingCoords.y, 'col'); - - this._autoScrollController.checkXAutoScroll(event.clientX, draggingDOMs); - - const direction = this._startCoords.x > this._draggingCoords.x ? 'left' : 'right'; - const dragOverColumn = getDragOverColumn(draggingDOMs.table, this._draggingCoords.x); - if (!dragOverColumn) return; - - const [col, index] = dragOverColumn; - this._droppingIndex = index; - this._dropIndicatorController.onDragging(col, direction, 'col'); - } - - private _onDragRowStart = (event: DragEvent) => { - this._onDragStart(event, 'row'); - } - - private _onDraggingRow = (event: DragEvent) => { - const draggingDOMs = this._draggingDOMs; - if (!draggingDOMs) return; - - this._draggingCoords = { x: event.clientX, y: event.clientY }; - this._previewController.onDragging(draggingDOMs, this._draggingCoords.x, this._draggingCoords.y, 'row'); - - this._autoScrollController.checkYAutoScroll(event.clientY); - - const direction = this._startCoords.y > this._draggingCoords.y ? 'up' : 'down'; - const dragOverRow = getDragOverRow(draggingDOMs.table, this._draggingCoords.y); - if (!dragOverRow) return; - - const [row, index] = dragOverRow; - this._droppingIndex = index; - this._dropIndicatorController.onDragging(row, direction, 'row'); - } - - private _onDragEnd = () => { - this._dragging = false; - this._draggingIndex = -1; - this._droppingIndex = -1; - this._startCoords = { x: 0, y: 0 }; - this._autoScrollController.stop(); - this._dropIndicatorController.onDragEnd(); - this._previewController.onDragEnd(); - } - - private _bindDragEvents = () => { - this._colDragHandle.addEventListener('dragstart', this._onDragColStart); - this._disposables.push(() => { - this._colDragHandle.removeEventListener('dragstart', this._onDragColStart); - }) - - this._colDragHandle.addEventListener('dragend', this._onDragEnd); - this._disposables.push(() => { - this._colDragHandle.removeEventListener('dragend', this._onDragEnd); - }) - - this._rowDragHandle.addEventListener('dragstart', this._onDragRowStart); - this._disposables.push(() => { - this._rowDragHandle.removeEventListener('dragstart', this._onDragRowStart); - }) - - this._rowDragHandle.addEventListener('dragend', this._onDragEnd); - this._disposables.push(() => { - this._rowDragHandle.removeEventListener('dragend', this._onDragEnd); - }) - - const ownerDocument = this.editor.view.dom?.ownerDocument - if (ownerDocument) { - // To make `drop` event work, we need to prevent the default behavior of the - // `dragover` event for drop zone. Here we set the whole document as the - // drop zone so that even the mouse moves outside the editor, the `drop` - // event will still be triggered. - ownerDocument.addEventListener('drop', this._onDrop); - ownerDocument.addEventListener('dragover', this._onDrag); - this._disposables.push(() => { - ownerDocument.removeEventListener('drop', this._onDrop); - ownerDocument.removeEventListener('dragover', this._onDrag); - }); - } - } - - private _onDragStart = (event: DragEvent, type: 'col' | 'row') => { - const dataTransfer = event.dataTransfer; - if (dataTransfer) { - dataTransfer.effectAllowed = 'move'; - this._emptyImageController.hideDragImage(dataTransfer); - } - this._dragging = true; - this._draggingDirection = type; - this._startCoords = { x: event.clientX, y: event.clientY }; - const draggingIndex = (type === 'col' ? this._hoveringCell?.colIndex : this._hoveringCell?.rowIndex) ?? 0; - - this._draggingIndex = draggingIndex; - - const relatedDoms = getDndRelatedDOMs( - this.editor.view, - this._hoveringCell?.cellPos, - draggingIndex, - type - ) - this._draggingDOMs = relatedDoms; - - const index = type === 'col' ? this._hoveringCell?.colIndex : this._hoveringCell?.rowIndex; - - this._previewController.onDragStart(relatedDoms, index, type); - this._dropIndicatorController.onDragStart(relatedDoms, type); - } - - private _onDrag = (event: DragEvent) => { - event.preventDefault() - if (!this._dragging) return; - if (this._draggingDirection === 'col') { - this._onDraggingCol(event); - } else { - this._onDraggingRow(event); - } - } - - private _onDrop = () => { - if (!this._dragging) return; - const direction = this._draggingDirection; - const from = this._draggingIndex; - const to = this._droppingIndex; - const tr = this.editor.state.tr; - const pos = this.editor.state.selection.from; - - if (direction === 'col') { - const canMove = moveColumn({ - tr, - originIndex: from, - targetIndex: to, - select: true, - pos, - }) - if (canMove) { - this.editor.view.dispatch(tr); - } - - return; - } - - if (direction === 'row') { - const canMove = moveRow({ - tr, - originIndex: from, - targetIndex: to, - select: true, - pos, - }) - if (canMove) { - this.editor.view.dispatch(tr); - } - - return; - } + return; } + }; } export const TableDndExtension = Extension.create({ - name: 'table-drag-and-drop', - addProseMirrorPlugins() { - const editor = this.editor + name: "table-drag-and-drop", + addProseMirrorPlugins() { + const editor = this.editor; - const dragHandlePluginSpec = new TableDragHandlePluginSpec(editor) - const dragHandlePlugin = new Plugin(dragHandlePluginSpec) + const dragHandlePluginSpec = new TableDragHandlePluginSpec(editor); + const dragHandlePlugin = new Plugin(dragHandlePluginSpec); - return [dragHandlePlugin] - } -}) + return [dragHandlePlugin]; + }, +}); diff --git a/packages/editor-ext/src/lib/table/header.ts b/packages/editor-ext/src/lib/table/header.ts index 501f089d..77ab02f1 100644 --- a/packages/editor-ext/src/lib/table/header.ts +++ b/packages/editor-ext/src/lib/table/header.ts @@ -1,4 +1,4 @@ -import { TableHeader as TiptapTableHeader } from "@tiptap/extension-table-header"; +import { TableHeader as TiptapTableHeader } from "@tiptap/extension-table"; export const TableHeader = TiptapTableHeader.extend({ name: "tableHeader", diff --git a/packages/editor-ext/src/lib/table/row.ts b/packages/editor-ext/src/lib/table/row.ts index 3aa67dcd..7839afdf 100644 --- a/packages/editor-ext/src/lib/table/row.ts +++ b/packages/editor-ext/src/lib/table/row.ts @@ -1,6 +1,5 @@ -import TiptapTableRow from "@tiptap/extension-table-row"; +import { TableRow as TiptapTableRow } from "@tiptap/extension-table"; export const TableRow = TiptapTableRow.extend({ - allowGapCursor: false, content: "(tableCell | tableHeader)*", }); diff --git a/packages/editor-ext/src/lib/table/table.ts b/packages/editor-ext/src/lib/table/table.ts index 87053832..f1436c28 100644 --- a/packages/editor-ext/src/lib/table/table.ts +++ b/packages/editor-ext/src/lib/table/table.ts @@ -1,4 +1,4 @@ -import Table from "@tiptap/extension-table"; +import { Table } from "@tiptap/extension-table"; import { Editor } from "@tiptap/core"; import { DOMOutputSpec } from "@tiptap/pm/model"; diff --git a/packages/editor-ext/src/lib/unique-id/helpers/findDuplicates.ts b/packages/editor-ext/src/lib/unique-id/helpers/findDuplicates.ts deleted file mode 100644 index d193e8b3..00000000 --- a/packages/editor-ext/src/lib/unique-id/helpers/findDuplicates.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { removeDuplicates } from './removeDuplicates.js' - -/** - * Returns a list of duplicated items within an array. - */ -export function findDuplicates(items: any[]): any[] { - const filtered = items.filter((el, index) => items.indexOf(el) !== index) - const duplicates = removeDuplicates(filtered) - - return duplicates -} diff --git a/packages/editor-ext/src/lib/unique-id/helpers/removeDuplicates.ts b/packages/editor-ext/src/lib/unique-id/helpers/removeDuplicates.ts deleted file mode 100644 index 2bae38fd..00000000 --- a/packages/editor-ext/src/lib/unique-id/helpers/removeDuplicates.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Removes duplicated values within an array. - * Supports numbers, strings and objects. - */ -export function removeDuplicates(array: T[], by = JSON.stringify): T[] { - const seen: Record = {} - - return array.filter(item => { - const key = by(item) - - return Object.prototype.hasOwnProperty.call(seen, key) - ? false - : (seen[key] = true) - }) -} diff --git a/packages/editor-ext/src/lib/unique-id/unique-id.ts b/packages/editor-ext/src/lib/unique-id/unique-id.ts index 6ecf15f0..8436cbd2 100644 --- a/packages/editor-ext/src/lib/unique-id/unique-id.ts +++ b/packages/editor-ext/src/lib/unique-id/unique-id.ts @@ -1,386 +1,11 @@ -import { - combineTransactionSteps, - Extension, - findChildren, - findChildrenInRange, - getChangedRanges, -} from "@tiptap/core"; -import type { Node as ProseMirrorNode } from "@tiptap/pm/model"; -import { Fragment, Slice } from "@tiptap/pm/model"; -import type { Transaction } from "@tiptap/pm/state"; -import { Plugin, PluginKey } from "@tiptap/pm/state"; - -import { findDuplicates } from "./helpers/findDuplicates.js"; import { generateNodeId } from "../utils"; +import { UniqueID as TiptapUniqueID } from "@tiptap/extension-unique-id"; -export type UniqueIDGenerationContext = { - node: ProseMirrorNode; - pos: number; -}; - -export interface UniqueIDOptions { - /** - * The name of the attribute to add the unique ID to. - * @default "id" - */ - attributeName: string; - /** - * The types of nodes to add unique IDs to. - * @default [] - */ - types: string[]; - /** - * The function that generates the unique ID. By default, a UUID v4 is - * generated. However, you can provide your own function to generate the - * unique ID based on the node type and the position. - */ - generateID: (ctx: UniqueIDGenerationContext) => any; - /** - * Ignore some mutations, for example applied from other users through the collaboration plugin. - * - * @default null - */ - filterTransaction: ((transaction: Transaction) => boolean) | null; - /** - * Whether to update the document by adding unique IDs to the nodes. Set this - * property to `false` if the document is in `readonly` mode, is immutable, or - * you don't want it to be modified. - * - * @default true - */ - updateDocument: boolean; -} - -export const UniqueID = Extension.create({ - name: "uniqueID", - - // we’ll set a very high priority to make sure this runs first - // and is compatible with `appendTransaction` hooks of other extensions - priority: 10000, - +export const UniqueID = TiptapUniqueID.extend({ addOptions() { return { - attributeName: "id", - types: [], + ...this.parent?.(), generateID: () => generateNodeId(), - filterTransaction: null, - updateDocument: true, }; }, - - addGlobalAttributes() { - return [ - { - types: this.options.types, - attributes: { - [this.options.attributeName]: { - default: null, - parseHTML: (element) => - element.getAttribute(`data-${this.options.attributeName}`), - renderHTML: (attributes) => { - if (!attributes[this.options.attributeName]) { - return {}; - } - - return { - [`data-${this.options.attributeName}`]: - attributes[this.options.attributeName], - }; - }, - }, - }, - }, - ]; - }, - - // check initial content for missing ids - onCreate() { - if (!this.options.updateDocument) { - return; - } - - const collaboration = this.editor.extensionManager.extensions.find( - (ext) => ext.name === "collaboration", - ); - const collaborationCursor = this.editor.extensionManager.extensions.find( - (ext) => ext.name === "collaborationCursor", - ); - - const collabExtensions = [collaboration, collaborationCursor].filter( - Boolean, - ); - const collab = collabExtensions.find((ext) => ext?.options?.provider); - const provider = collab?.options?.provider; - - const createIds = () => { - const { view, state } = this.editor; - const { tr, doc } = state; - const { types, attributeName, generateID } = this.options; - const nodesWithoutId = findChildren(doc, (node) => { - return ( - types.includes(node.type.name) && node.attrs[attributeName] === null - ); - }); - - nodesWithoutId.forEach(({ node, pos }) => { - tr.setNodeMarkup(pos, undefined, { - ...node.attrs, - [attributeName]: generateID({ node, pos }), - }); - }); - - tr.setMeta("addToHistory", false); - - view.dispatch(tr); - - if (provider) { - provider.off("synced", createIds); - } - }; - - /** - * We need to handle collaboration a bit different here - * because we can't automatically add IDs when the provider is not yet synced - * otherwise we end up with empty paragraphs - */ - if (collab) { - if (!provider) { - return createIds(); - } - - provider.on("synced", createIds); - } else { - return createIds(); - } - }, - - addProseMirrorPlugins() { - if (!this.options.updateDocument) { - return []; - } - - let dragSourceElement: Element | null = null; - let transformPasted = false; - - return [ - new Plugin({ - key: new PluginKey("uniqueID"), - - appendTransaction: (transactions, oldState, newState) => { - const hasDocChanges = - transactions.some((transaction) => transaction.docChanged) && - !oldState.doc.eq(newState.doc); - const filterTransactions = - this.options.filterTransaction && - transactions.some((tr) => !this.options.filterTransaction?.(tr)); - - const isCollabTransaction = transactions.find((tr) => - tr.getMeta("y-sync$"), - ); - - if (isCollabTransaction) { - return; - } - - if (!hasDocChanges || filterTransactions) { - return; - } - - const { tr } = newState; - - const { types, attributeName, generateID } = this.options; - const transform = combineTransactionSteps( - oldState.doc, - transactions as Transaction[], - ); - const { mapping } = transform; - - // get changed ranges based on the old state - const changes = getChangedRanges(transform); - - changes.forEach(({ newRange }) => { - const newNodes = findChildrenInRange( - newState.doc, - newRange, - (node) => { - return types.includes(node.type.name); - }, - ); - - const newIds = newNodes - .map(({ node }) => node.attrs[attributeName]) - .filter((id) => id !== null); - - newNodes.forEach(({ node, pos }, i) => { - // instead of checking `node.attrs[attributeName]` directly - // we look at the current state of the node within `tr.doc`. - // this helps to prevent adding new ids to the same node - // if the node changed multiple times within one transaction - const id = tr.doc.nodeAt(pos)?.attrs[attributeName]; - - if (id === null) { - tr.setNodeMarkup(pos, undefined, { - ...node.attrs, - [attributeName]: generateID({ node, pos }), - }); - - return; - } - - const nextNode = newNodes[i + 1]; - - if (nextNode && node.content.size === 0) { - tr.setNodeMarkup(nextNode.pos, undefined, { - ...nextNode.node.attrs, - [attributeName]: id, - }); - newIds[i + 1] = id; - - if (nextNode.node.attrs[attributeName]) { - return; - } - - const generatedId = generateID({ node, pos }); - - tr.setNodeMarkup(pos, undefined, { - ...node.attrs, - [attributeName]: generatedId, - }); - newIds[i] = generatedId; - - return tr; - } - - const duplicatedNewIds = findDuplicates(newIds); - - // check if the node doesn’t exist in the old state - const { deleted } = mapping.invert().mapResult(pos); - - const newNode = deleted && duplicatedNewIds.includes(id); - - if (newNode) { - tr.setNodeMarkup(pos, undefined, { - ...node.attrs, - [attributeName]: generateID({ node, pos }), - }); - } - }); - }); - - if (!tr.steps.length) { - return; - } - - // `tr.setNodeMarkup` resets the stored marks - // so we'll restore them if they exist - tr.setStoredMarks(newState.tr.storedMarks); - - // Mark this transaction as coming from UniqueID - // to prevent infinite loops with other extensions (e.g., TrailingNode) - tr.setMeta("__uniqueIDTransaction", true); - - return tr; - }, - - // we register a global drag handler to track the current drag source element - view(view) { - const handleDragstart = (event: DragEvent) => { - dragSourceElement = view.dom.parentElement?.contains( - event.target as Element, - ) - ? view.dom.parentElement - : null; - }; - - window.addEventListener("dragstart", handleDragstart); - - return { - destroy() { - window.removeEventListener("dragstart", handleDragstart); - }, - }; - }, - - props: { - // `handleDOMEvents` is called before `transformPasted` - // so we can do some checks before - handleDOMEvents: { - // only create new ids for dropped content - // or dropped content while holding `alt` - // or content is dragged from another editor - drop: (view, event) => { - if ( - dragSourceElement !== view.dom.parentElement || - event.dataTransfer?.effectAllowed === "copyMove" || - event.dataTransfer?.effectAllowed === "copy" - ) { - dragSourceElement = null; - transformPasted = true; - } - - return false; - }, - // always create new ids on pasted content - paste: () => { - transformPasted = true; - - return false; - }, - }, - - // we’ll remove ids for every pasted node - // so we can create a new one within `appendTransaction` - transformPasted: (slice) => { - if (!transformPasted) { - return slice; - } - - const { types, attributeName } = this.options; - const removeId = (fragment: Fragment): Fragment => { - const list: ProseMirrorNode[] = []; - - fragment.forEach((node) => { - // don’t touch text nodes - if (node.isText) { - list.push(node); - - return; - } - - // check for any other child nodes - if (!types.includes(node.type.name)) { - list.push(node.copy(removeId(node.content))); - - return; - } - - // remove id - const nodeWithoutId = node.type.create( - { - ...node.attrs, - [attributeName]: null, - }, - removeId(node.content), - node.marks, - ); - - list.push(nodeWithoutId); - }); - - return Fragment.from(list); - }; - - // reset check - transformPasted = false; - - return new Slice( - removeId(slice.content), - slice.openStart, - slice.openEnd, - ); - }, - }, - }), - ]; - }, }); diff --git a/packages/editor-ext/src/lib/utils.ts b/packages/editor-ext/src/lib/utils.ts index e4e7fda4..350ab3bb 100644 --- a/packages/editor-ext/src/lib/utils.ts +++ b/packages/editor-ext/src/lib/utils.ts @@ -1,6 +1,6 @@ -// @ts-nocheck import { Editor, findParentNode, isTextSelection } from "@tiptap/core"; -import { Selection, Transaction } from "@tiptap/pm/state"; +import { EditorState, Selection, Transaction } from "@tiptap/pm/state"; +import { EditorView } from "@tiptap/pm/view"; import { CellSelection, TableMap } from "@tiptap/pm/tables"; import { Node, ResolvedPos } from "@tiptap/pm/model"; import { sanitizeUrl as braintreeSanitizeUrl } from "@braintree/sanitize-url"; @@ -287,11 +287,7 @@ export const isColumnGripSelected = ({ const nodeDOM = view.nodeDOM(from) as HTMLElement; const node = nodeDOM || domAtPos; - if ( - !editor.isActive("table") || - !node || - isTableSelected(state.selection) - ) { + if (!editor.isActive("table") || !node || isTableSelected(state.selection)) { return false; } @@ -324,11 +320,7 @@ export const isRowGripSelected = ({ const nodeDOM = view.nodeDOM(from) as HTMLElement; const node = nodeDOM || domAtPos; - if ( - !editor.isActive(Table.name) || - !node || - isTableSelected(state.selection) - ) { + if (!editor.isActive("table") || !node || isTableSelected(state.selection)) { return false; } diff --git a/packages/editor-ext/src/lib/video/video.ts b/packages/editor-ext/src/lib/video/video.ts index 6f28e7c0..40f6db32 100644 --- a/packages/editor-ext/src/lib/video/video.ts +++ b/packages/editor-ext/src/lib/video/video.ts @@ -20,7 +20,7 @@ declare module "@tiptap/core" { videoBlock: { setVideo: (attributes: VideoAttributes) => ReturnType; setVideoAt: ( - attributes: VideoAttributes & { pos: number | Range }, + attributes: VideoAttributes & { pos: number | Range } ) => ReturnType; setVideoAlign: (align: "left" | "center" | "right") => ReturnType; setVideoWidth: (width: number) => ReturnType; @@ -87,9 +87,9 @@ export const TiptapVideo = Node.create({ parseHTML() { return [ { - tag: 'video', + tag: "video", }, - ] + ]; }, renderHTML({ HTMLAttributes }) { @@ -126,6 +126,9 @@ export const TiptapVideo = Node.create({ }, addNodeView() { + // Force the react node view to render immediately using flush sync (https://github.com/ueberdosis/tiptap/blob/b4db352f839e1d82f9add6ee7fb45561336286d8/packages/react/src/ReactRenderer.tsx#L183-L191) + this.editor.isInitialized = true; + return ReactNodeViewRenderer(this.options.view); }, diff --git a/packages/editor-ext/tsconfig.json b/packages/editor-ext/tsconfig.json index efbfcd61..974fea06 100644 --- a/packages/editor-ext/tsconfig.json +++ b/packages/editor-ext/tsconfig.json @@ -8,6 +8,7 @@ "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2022", + "jsx": "react-jsx", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 882fa54a..eea76ecb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,17 +31,17 @@ importers: specifier: ^1.7.3 version: 1.7.3 '@hocuspocus/extension-redis': - specifier: ^2.15.3 - version: 2.15.3(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27) + specifier: 3.4.3 + version: 3.4.3(y-protocols@1.0.6(yjs@13.6.29))(yjs@13.6.29) '@hocuspocus/provider': - specifier: ^2.15.3 - version: 2.15.3(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27) + specifier: 3.4.3 + version: 3.4.3(y-protocols@1.0.6(yjs@13.6.29))(yjs@13.6.29) '@hocuspocus/server': - specifier: ^2.15.3 - version: 2.15.3(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27) + specifier: 3.4.3 + version: 3.4.3(y-protocols@1.0.6(yjs@13.6.29))(yjs@13.6.29) '@hocuspocus/transformer': - specifier: ^2.15.3 - version: 2.15.3(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)(y-prosemirror@1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))(yjs@13.6.27) + specifier: 3.4.3 + version: 3.4.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)(y-prosemirror@1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.29))(yjs@13.6.29))(yjs@13.6.29) '@joplin/turndown': specifier: ^4.0.74 version: 4.0.74 @@ -52,107 +52,86 @@ importers: specifier: 1.1.0 version: 1.1.0 '@tiptap/core': - specifier: 2.27.1 - version: 2.27.1(@tiptap/pm@2.27.1) + specifier: 3.15.3 + version: 3.15.3(@tiptap/pm@3.15.3) '@tiptap/extension-code-block': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/extension-code-block-lowlight': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/extension-code-block@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)(highlight.js@11.11.1)(lowlight@3.3.0) + specifier: 3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) '@tiptap/extension-collaboration': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)(y-prosemirror@1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27)) - '@tiptap/extension-collaboration-cursor': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(y-prosemirror@1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27)) + specifier: 3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)(@tiptap/y-tiptap@3.0.1(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.29))(yjs@13.6.29))(yjs@13.6.29) + '@tiptap/extension-collaboration-caret': + specifier: 3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)(@tiptap/y-tiptap@3.0.1(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.29))(yjs@13.6.29)) '@tiptap/extension-color': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/extension-text-style@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))) + specifier: 3.15.3 + version: 3.15.3(@tiptap/extension-text-style@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))) '@tiptap/extension-document': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) '@tiptap/extension-heading': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) '@tiptap/extension-highlight': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) '@tiptap/extension-history': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) + specifier: 3.15.3 + version: 3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)) '@tiptap/extension-image': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) '@tiptap/extension-link': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/extension-list-item': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-list-keymap': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) + '@tiptap/extension-list': + specifier: 3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) '@tiptap/extension-placeholder': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) + specifier: 3.15.3 + version: 3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)) '@tiptap/extension-subscript': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) '@tiptap/extension-superscript': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) '@tiptap/extension-table': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/extension-table-cell': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-table-header': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-table-row': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-task-item': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/extension-task-list': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) '@tiptap/extension-text': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) '@tiptap/extension-text-align': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) '@tiptap/extension-text-style': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) '@tiptap/extension-typography': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-underline': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) + '@tiptap/extension-unique-id': + specifier: ^3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) '@tiptap/extension-youtube': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) '@tiptap/html': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) + specifier: 3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)(happy-dom@20.1.0) '@tiptap/pm': - specifier: 2.27.1 - version: 2.27.1 + specifier: 3.15.3 + version: 3.15.3 '@tiptap/react': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: 3.15.3 + version: 3.15.3(@floating-ui/dom@1.7.3)(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tiptap/starter-kit': - specifier: 2.27.1 - version: 2.27.1 + specifier: 3.15.3 + version: 3.15.3 '@tiptap/suggestion': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) + specifier: 3.15.3 + version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) '@types/qrcode': specifier: ^1.5.5 version: 1.5.5 @@ -171,6 +150,9 @@ importers: fractional-indexing-jittered: specifier: ^1.0.0 version: 1.0.0 + highlight.js: + specifier: ^11.11.1 + version: 11.11.1 ioredis: specifier: ^5.4.1 version: 5.4.1 @@ -194,13 +176,13 @@ importers: version: 11.1.0 y-indexeddb: specifier: ^9.0.12 - version: 9.0.12(yjs@13.6.27) + version: 9.0.12(yjs@13.6.29) y-prosemirror: specifier: 1.3.7 - version: 1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27) + version: 1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.29))(yjs@13.6.29) yjs: - specifier: ^13.6.27 - version: 13.6.27 + specifier: ^13.6.29 + version: 13.6.29 devDependencies: '@nx/js': specifier: 20.4.5 @@ -265,9 +247,6 @@ importers: '@tanstack/react-query': specifier: ^5.90.17 version: 5.90.17(react@18.3.1) - '@tiptap/extension-character-count': - specifier: ^2.27.1 - version: 2.27.2(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) alfaaz: specifier: ^1.1.0 version: 1.1.0 @@ -355,9 +334,6 @@ importers: socket.io-client: specifier: ^4.8.3 version: 4.8.3 - tippy.js: - specifier: ^6.3.7 - version: 6.3.7 tiptap-extension-global-drag-handle: specifier: ^0.1.18 version: 0.1.18 @@ -2389,32 +2365,32 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} - '@hocuspocus/common@2.15.3': - resolution: {integrity: sha512-Rzh1HF0a2o/tf90A3w2XNdXd9Ym3aQzMDfD3lAUONCX9B9QOdqdyiORrj6M25QEaJrEIbXFy8LtAFcL0wRdWzA==} + '@hocuspocus/common@3.4.3': + resolution: {integrity: sha512-wnBBO9sWcVAoUPEXN1qO+zk3HaEF9VTemxB6kjuuH6e1dHnD0v12m4P4X1wiZVhmMIX/PMl/fu3MGtYWQJz8gA==} - '@hocuspocus/extension-redis@2.15.3': - resolution: {integrity: sha512-gKeiiuQcAoRYb+QK9vyIczRrjNy8NW6ky+oyVv7raMcaizfFxeWP3TaAHPyC2pjGKfXsqN2m3YM0GbBGZfMiCg==} + '@hocuspocus/extension-redis@3.4.3': + resolution: {integrity: sha512-r64Vpgk6tt0VZaQPEo1dQuyur2ozr243ncDcDM+4gFPuV8ZRUjL1rvaJTidb2HCcAW2zjfwshNxw4+OixeksBA==} peerDependencies: y-protocols: ^1.0.6 yjs: ^13.6.8 - '@hocuspocus/provider@2.15.3': - resolution: {integrity: sha512-oadN05m+KL4ylNKVo5YspNG4MXkT2Y+FUFzrgigpQeTjQibkPUwCNmUnkUxMgrGRgxb+O0lJCfirFIJMxedctA==} + '@hocuspocus/provider@3.4.3': + resolution: {integrity: sha512-zt+UgVXGsEQrqnDZgavc2PT9yKJjmVjV+5YxvhlmFVFLVORqawT4l601aKmLPhvyK97un4ZApZ5rso8iO6crWg==} peerDependencies: y-protocols: ^1.0.6 yjs: ^13.6.8 - '@hocuspocus/server@2.15.3': - resolution: {integrity: sha512-Ju4ty4/7JtmvivcP7gKReOLf8KrFwN7Yx/5VhXYh4TRULy4kSo2fsDVUaluPp0neZa6PbVhizJuzlOim73IEbQ==} + '@hocuspocus/server@3.4.3': + resolution: {integrity: sha512-a9bqAXUMBo9YBeuzqNf9C3eVbu1RIWUrtmFMGq+ZssQr3Jugt/5PCkZskgqhJNvPkyTARHcUtN80j/SDLylZmg==} peerDependencies: y-protocols: ^1.0.6 yjs: ^13.6.8 - '@hocuspocus/transformer@2.15.3': - resolution: {integrity: sha512-01UU3iZA9MF+MmB2SweKyC70nBM/FkBt3veWiAMoXPiegUG47wY8QO2MksBD/ucnz7C5M/0oAsTjqrx+j0ynIw==} + '@hocuspocus/transformer@3.4.3': + resolution: {integrity: sha512-jQZiqFGCvGQJLgE0nHZ4TdpEJlI7WkM8CKA1wLcs0beVs0kNXg32lykGckjveJwwJuJ/hieMqIEqj9POxTWPEw==} peerDependencies: - '@tiptap/core': ^2.6.4 - '@tiptap/pm': ^2.6.4 + '@tiptap/core': ^3.0.1 + '@tiptap/pm': ^3.0.1 y-prosemirror: 1.3.7 yjs: ^13.6.8 @@ -3383,9 +3359,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==} @@ -3896,6 +3869,12 @@ packages: '@selderee/plugin-htmlparser2@0.11.0': resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} + '@sesamecare-oss/redlock@1.4.0': + resolution: {integrity: sha512-2z589R+yxKLN4CgKxP1oN4dsg6Y548SE4bVYam/R0kHk7Q9VrQ9l66q+k1ehhSLLY4or9hcchuF9/MhuuZdjJg==} + engines: {node: '>=16'} + peerDependencies: + ioredis: '>=5' + '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -4231,271 +4210,261 @@ packages: peerDependencies: react: ^18 || ^19 - '@tiptap/core@2.27.1': - resolution: {integrity: sha512-nkerkl8syHj44ZzAB7oA2GPmmZINKBKCa79FuNvmGJrJ4qyZwlkDzszud23YteFZEytbc87kVd/fP76ROS6sLg==} + '@tiptap/core@3.15.3': + resolution: {integrity: sha512-bmXydIHfm2rEtGju39FiQNfzkFx9CDvJe+xem1dgEZ2P6Dj7nQX9LnA1ZscW7TuzbBRkL5p3dwuBIi3f62A66A==} peerDependencies: - '@tiptap/pm': ^2.7.0 + '@tiptap/pm': ^3.15.3 - '@tiptap/extension-blockquote@2.27.1': - resolution: {integrity: sha512-QrUX3muElDrNjKM3nqCSAtm3H3pT33c6ON8kwRiQboOAjT/9D57Cs7XEVY7r6rMaJPeKztrRUrNVF9w/w/6B0A==} + '@tiptap/extension-blockquote@3.15.3': + resolution: {integrity: sha512-13x5UsQXtttFpoS/n1q173OeurNxppsdWgP3JfsshzyxIghhC141uL3H6SGYQLPU31AizgDs2OEzt6cSUevaZg==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.15.3 - '@tiptap/extension-bold@2.27.1': - resolution: {integrity: sha512-g4l4p892x/r7mhea8syp3fNYODxsDrimgouQ+q4DKXIgQmm5+uNhyuEPexP3I8TFNXqQ4DlMNFoM9yCqk97etQ==} + '@tiptap/extension-bold@3.15.3': + resolution: {integrity: sha512-I8JYbkkUTNUXbHd/wCse2bR0QhQtJD7+0/lgrKOmGfv5ioLxcki079Nzuqqay3PjgYoJLIJQvm3RAGxT+4X91w==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.15.3 - '@tiptap/extension-bubble-menu@2.27.1': - resolution: {integrity: sha512-ki1R27VsSvY2tT9Q2DIlcATwLOoEjf5DsN+5sExarQ8S/ZxT/tvIjRxB8Dx7lb2a818W5f/NER26YchGtmHfpg==} + '@tiptap/extension-bubble-menu@3.15.3': + resolution: {integrity: sha512-e88DG1bTy6hKxrt7iPVQhJnH5/EOrnKpIyp09dfRDgWrrW88fE0Qjys7a/eT8W+sXyXM3z10Ye7zpERWsrLZDg==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.15.3 + '@tiptap/pm': ^3.15.3 - '@tiptap/extension-bullet-list@2.27.1': - resolution: {integrity: sha512-5FmnfXkJ76wN4EbJNzBhAlmQxho8yEMIJLchTGmXdsD/n/tsyVVtewnQYaIOj/Z7naaGySTGDmjVtLgTuQ+Sxw==} + '@tiptap/extension-bullet-list@3.15.3': + resolution: {integrity: sha512-MGwEkNT7ltst6XaWf0ObNgpKQ4PvuuV3igkBrdYnQS+qaAx9IF4isygVPqUc9DvjYC306jpyKsNqNrENIXcosA==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/extension-list': ^3.15.3 - '@tiptap/extension-character-count@2.27.2': - resolution: {integrity: sha512-EcQRIvbLbMDDzo7uFqXYgh1CfgedS9sYX4BllktY2OlXLPdNpwo9t8WMK/a7soESNv0Le3WZ5pNvnNhv7Z2YdA==} + '@tiptap/extension-code-block@3.15.3': + resolution: {integrity: sha512-q1UB9icNfdJppTqMIUWfoRKkx5SSdWIpwZoL2NeOI5Ah3E20/dQKVttIgLhsE521chyvxCYCRaHD5tMNGKfhyw==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.15.3 + '@tiptap/pm': ^3.15.3 - '@tiptap/extension-code-block-lowlight@2.27.1': - resolution: {integrity: sha512-Ijg9724uX/l4LXLELEeztZIgg+bDE/jJCkgS1+mavkRA/qtidpQkHo7L/Ry22fmj/ktCtZLjPXE5JAPAoRU6zA==} + '@tiptap/extension-code@3.15.3': + resolution: {integrity: sha512-x6LFt3Og6MFINYpsMzrJnz7vaT9Yk1t4oXkbJsJRSavdIWBEBcoRudKZ4sSe/AnsYlRJs8FY2uR76mt9e+7xAQ==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/extension-code-block': ^2.7.0 - '@tiptap/pm': ^2.7.0 - highlight.js: ^11 - lowlight: ^2 || ^3 + '@tiptap/core': ^3.15.3 - '@tiptap/extension-code-block@2.27.1': - resolution: {integrity: sha512-wCI5VIOfSAdkenCWFvh4m8FFCJ51EOK+CUmOC/PWUjyo2Dgn8QC8HMi015q8XF7886T0KvYVVoqxmxJSUDAYNg==} + '@tiptap/extension-collaboration-caret@3.15.3': + resolution: {integrity: sha512-kXGOL99CLFzc8IdmRpQQwyOqeCWX9Eo4ferz6hwK7YfpKWZoJi9HaiEb6z2gA8Q24ecedcIjBF1l6kLHQiQ2QQ==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.15.3 + '@tiptap/pm': ^3.15.3 + '@tiptap/y-tiptap': ^3.0.0 - '@tiptap/extension-code@2.27.1': - resolution: {integrity: sha512-i65wUGJevzBTIIUBHBc1ggVa27bgemvGl/tY1/89fEuS/0Xmre+OQjw8rCtSLevoHSiYYLgLRlvjtUSUhE4kgg==} + '@tiptap/extension-collaboration@3.15.3': + resolution: {integrity: sha512-AM/UkKkxnKA+NDJ1todoQoj8dMuOI1VcuoUyLVkGn1Jx7GjOng2IMouWkH1of8+dbq9qVWzmbN4VWelsz8vuvw==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.15.3 + '@tiptap/pm': ^3.15.3 + '@tiptap/y-tiptap': ^3.0.0 + yjs: ^13 - '@tiptap/extension-collaboration-cursor@2.27.1': - resolution: {integrity: sha512-k4vLA1QeGM4FfO9BMKw8O0Nxv2zDrsUpnP7wKAJp/zmr2lHbQX86cO+SGEy+kcRtPeIp6Y4Phytp6F+1HMjbLA==} + '@tiptap/extension-color@3.15.3': + resolution: {integrity: sha512-GS+LEJ7YC7J6CiQ/caTDVyKg+ZlU4B5ofzAZ0iCWPahjMyUUZImzXvoRlfMumAiPG+IUW9PC2BztSGd3SCLpGA==} peerDependencies: - '@tiptap/core': ^2.7.0 - y-prosemirror: 1.3.7 + '@tiptap/extension-text-style': ^3.15.3 - '@tiptap/extension-collaboration@2.27.1': - resolution: {integrity: sha512-fR35dIYDHM9870zl2sHaA2ytSVcjASv8Nfnb1Mgslt/F3Lqsu9TOv/oJWi9nYBvjjrfK0RNaoGFVH7p2z7FR3w==} + '@tiptap/extension-document@3.15.3': + resolution: {integrity: sha512-AC72nI2gnogBuETCKbZekn+h6t5FGGcZG2abPGKbz/x9rwpb6qV2hcbAQ30t6M7H6cTOh2/Ut8bEV2MtMB15sw==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 - y-prosemirror: 1.3.7 + '@tiptap/core': ^3.15.3 - '@tiptap/extension-color@2.27.1': - resolution: {integrity: sha512-raYRsdG2tZvVvY1LV/VTZnDG44Y0xRBwo5CZEat0OUqdx34dfvCtYm8HIOTyWBwr7OOW+yR4O1Vc2zFkmfthZw==} + '@tiptap/extension-dropcursor@3.15.3': + resolution: {integrity: sha512-jGI5XZpdo8GSYQFj7HY15/oEwC2m2TqZz0/Fln5qIhY32XlZhWrsMuMI6WbUJrTH16es7xO6jmRlDsc6g+vJWg==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/extension-text-style': ^2.7.0 + '@tiptap/extensions': ^3.15.3 - '@tiptap/extension-document@2.27.1': - resolution: {integrity: sha512-NtJzJY7Q/6XWjpOm5OXKrnEaofrcc1XOTYlo/SaTwl8k2bZo918Vl0IDBWhPVDsUN7kx767uHwbtuQZ+9I82hA==} + '@tiptap/extension-floating-menu@3.15.3': + resolution: {integrity: sha512-+3DVBleKKffadEJEdLYxmYAJOjHjLSqtiSFUE3RABT4V2ka1ODy2NIpyKX0o1SvQ5N1jViYT9Q+yUbNa6zCcDw==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@floating-ui/dom': ^1.0.0 + '@tiptap/core': ^3.15.3 + '@tiptap/pm': ^3.15.3 - '@tiptap/extension-dropcursor@2.27.1': - resolution: {integrity: sha512-3MBQRGHHZ0by3OT0CWbLKS7J3PH9PpobrXjmIR7kr0nde7+bHqxXiVNuuIf501oKU9rnEUSedipSHkLYGkmfsA==} + '@tiptap/extension-gapcursor@3.15.3': + resolution: {integrity: sha512-Kaw0sNzP0bQI/xEAMSfIpja6xhsu9WqqAK/puzOIS1RKWO47Wps/tzqdSJ9gfslPIb5uY5mKCfy8UR8Xgiia8w==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/extensions': ^3.15.3 - '@tiptap/extension-floating-menu@2.27.1': - resolution: {integrity: sha512-nUk/8DbiXO69l6FDwkWso94BTf52IBoWALo+YGWT6o+FO6cI9LbUGghEX2CdmQYXCvSvwvISF2jXeLQWNZvPZQ==} + '@tiptap/extension-hard-break@3.15.3': + resolution: {integrity: sha512-8HjxmeRbBiXW+7JKemAJtZtHlmXQ9iji398CPQ0yYde68WbIvUhHXjmbJE5pxFvvQTJ/zJv1aISeEOZN2bKBaw==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.15.3 - '@tiptap/extension-gapcursor@2.27.1': - resolution: {integrity: sha512-A9e1jr+jGhDWzNSXtIO6PYVYhf5j/udjbZwMja+wCE/3KvZU9V3IrnGKz1xNW+2Q2BDOe1QO7j5uVL9ElR6nTA==} + '@tiptap/extension-heading@3.15.3': + resolution: {integrity: sha512-G1GG6iN1YXPS+75arDpo+bYRzhr3dNDw99c7D7na3aDawa9Qp7sZ/bVrzFUUcVEce0cD6h83yY7AooBxEc67hA==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.15.3 - '@tiptap/extension-hard-break@2.27.1': - resolution: {integrity: sha512-W4hHa4Io6QCTwpyTlN6UAvqMIQ7t56kIUByZhyY9EWrg/+JpbfpxE1kXFLPB4ZGgwBknFOw+e4bJ1j3oAbTJFw==} + '@tiptap/extension-highlight@3.15.3': + resolution: {integrity: sha512-ZZyuKGW4WrMx3pBEfsHqOcqEklfiiAjVuvhji9FJcip1w0B2OnMWkgZw7rdAlsQG8pGH6NWh9Gf2DOUsjuAa6A==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.15.3 - '@tiptap/extension-heading@2.27.1': - resolution: {integrity: sha512-6xoC7igZlW1EmnQ5WVH9IL7P1nCQb3bBUaIDLvk7LbweEogcTUECI4Xg1vxMOVmj9tlDe1I4BsgfcKpB5KEsZw==} + '@tiptap/extension-history@3.15.3': + resolution: {integrity: sha512-nzayl9Iv+lkd6Om9bip8iWSAS8mr/pw2EwOlEAogBueNhVc+VoBKwq3DGnBTbqAddc4g0T7oOtHmmmovBoZduQ==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/extensions': ^3.15.3 - '@tiptap/extension-highlight@2.27.1': - resolution: {integrity: sha512-ntuYX09tvHQE/R/8WbTOxbFuQhRr2jhTkKz/gLwDD2o8IhccSy3f0nm+mVmVamKQnbsBBbLohojd5IGOnX9f1A==} + '@tiptap/extension-horizontal-rule@3.15.3': + resolution: {integrity: sha512-FYkN7L6JsfwwNEntmLklCVKvgL0B0N47OXMacRk6kYKQmVQ4Nvc7q/VJLpD9sk4wh4KT1aiCBfhKEBTu5pv1fg==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.15.3 + '@tiptap/pm': ^3.15.3 - '@tiptap/extension-history@2.27.1': - resolution: {integrity: sha512-K8PHC9gegSAt0wzSlsd4aUpoEyIJYOmVVeyniHr1P1mIblW1KYEDbRGbDlrLALTyUEfMcBhdIm8zrB9X2Nihvg==} + '@tiptap/extension-image@3.15.3': + resolution: {integrity: sha512-Tjq9BHlC/0bGR9/uySA0tv6I1Ua1Q5t5P/mdbWyZi4JdUpKHRfgenzfXF5DYnklJ01QJ7uOPSp9sAGgPzBixtQ==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.15.3 - '@tiptap/extension-horizontal-rule@2.27.1': - resolution: {integrity: sha512-WxXWGEEsqDmGIF2o9av+3r9Qje4CKrqrpeQY6aRO5bxvWX9AabQCfasepayBok6uwtvNzh3Xpsn9zbbSk09dNA==} + '@tiptap/extension-italic@3.15.3': + resolution: {integrity: sha512-6XeuPjcWy7OBxpkgOV7bD6PATO5jhIxc8SEK4m8xn8nelGTBIbHGqK37evRv+QkC7E0MUryLtzwnmmiaxcKL0Q==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.15.3 - '@tiptap/extension-image@2.27.1': - resolution: {integrity: sha512-wu3vMKDYWJwKS6Hrw5PPCKBO2RxyHNeFLiA/uDErEV7axzNpievK/U9DyaDXmtK3K/h1XzJAJz19X+2d/pY68w==} + '@tiptap/extension-link@3.15.3': + resolution: {integrity: sha512-PdDXyBF9Wco9U1x6e+b7tKBWG+kqBDXDmaYXHkFm/gYuQCQafVJ5mdrDdKgkHDWVnJzMWZXBcZjT9r57qtlLWg==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.15.3 + '@tiptap/pm': ^3.15.3 - '@tiptap/extension-italic@2.27.1': - resolution: {integrity: sha512-rcm0GyniWW0UhcNI9+1eIK64GqWQLyIIrWGINslvqSUoBc+WkfocLvv4CMpRkzKlfsAxwVIBuH2eLxHKDtAREA==} + '@tiptap/extension-list-item@3.15.3': + resolution: {integrity: sha512-CCxL5ek1p0lO5e8aqhnPzIySldXRSigBFk2fP9OLgdl5qKFLs2MGc19jFlx5+/kjXnEsdQTFbGY1Sizzt0TVDw==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/extension-list': ^3.15.3 - '@tiptap/extension-link@2.27.1': - resolution: {integrity: sha512-cCwWPZsnVh9MXnGOqSIRXPPuUixRDK8eMN2TvqwbxUBb1TU7b/HtNvfMU4tAOqAuMRJ0aJkFuf3eB0Gi8LVb1g==} + '@tiptap/extension-list-keymap@3.15.3': + resolution: {integrity: sha512-UxqnTEEAKrL+wFQeSyC9z0mgyUUVRS2WTcVFoLZCE6/Xus9F53S4bl7VKFadjmqI4GpDk5Oe2IOUc72o129jWg==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/extension-list': ^3.15.3 - '@tiptap/extension-list-item@2.27.1': - resolution: {integrity: sha512-dtsxvtzxfwOJP6dKGf0vb2MJAoDF2NxoiWzpq0XTvo7NGGYUHfuHjX07Zp0dYqb4seaDXjwsi5BIQUOp3+WMFQ==} + '@tiptap/extension-list@3.15.3': + resolution: {integrity: sha512-n7y/MF9lAM5qlpuH5IR4/uq+kJPEJpe9NrEiH+NmkO/5KJ6cXzpJ6F4U17sMLf2SNCq+TWN9QK8QzoKxIn50VQ==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.15.3 + '@tiptap/pm': ^3.15.3 - '@tiptap/extension-list-keymap@2.27.1': - resolution: {integrity: sha512-k7+Ulz9B1NjqwU6NEFYkJh4rGGT/iRVaCBa8OL9YYrVS3H44LgEqUCEbRu6TeEq4XXrLwueQpkkyl4Evi15lAQ==} + '@tiptap/extension-ordered-list@3.15.3': + resolution: {integrity: sha512-/8uhw528Iy0c9wF6tHCiIn0ToM0Ml6Ll2c/3iPRnKr4IjXwx2Lr994stUFihb+oqGZwV1J8CPcZJ4Ufpdqi4Dw==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/extension-list': ^3.15.3 - '@tiptap/extension-ordered-list@2.27.1': - resolution: {integrity: sha512-U1/sWxc2TciozQsZjH35temyidYUjvroHj3PUPzPyh19w2fwKh1NSbFybWuoYs6jS3XnMSwnM2vF52tOwvfEmA==} + '@tiptap/extension-paragraph@3.15.3': + resolution: {integrity: sha512-lc0Qu/1AgzcEfS67NJMj5tSHHhH6NtA6uUpvppEKGsvJwgE2wKG1onE4isrVXmcGRdxSMiCtyTDemPNMu6/ozQ==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.15.3 - '@tiptap/extension-paragraph@2.27.1': - resolution: {integrity: sha512-R3QdrHcUdFAsdsn2UAIvhY0yWyHjqGyP/Rv8RRdN0OyFiTKtwTPqreKMHKJOflgX4sMJl/OpHTpNG1Kaf7Lo2A==} + '@tiptap/extension-placeholder@3.15.3': + resolution: {integrity: sha512-XcHHnojT186hKIoOgcPBesXk89+caNGVUdMtc171Vcr/5s0dpnr4q5LfE+YRC+S85CpCxCRRnh84Ou+XRtOqrw==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/extensions': ^3.15.3 - '@tiptap/extension-placeholder@2.27.1': - resolution: {integrity: sha512-UbXaibHHFE+lOTlw/vs3jPzBoj1sAfbXuTAhXChjgYIcTTY5Cr6yxwcymLcimbQ79gf04Xkua2FCN3YsJxIFmw==} + '@tiptap/extension-strike@3.15.3': + resolution: {integrity: sha512-Y1P3eGNY7RxQs2BcR6NfLo9VfEOplXXHAqkOM88oowWWOE7dMNeFFZM9H8HNxoQgXJ7H0aWW9B7ZTWM9hWli2Q==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.15.3 - '@tiptap/extension-strike@2.27.1': - resolution: {integrity: sha512-S9I//K8KPgfFTC5I5lorClzXk0g4lrAv9y5qHzHO5EOWt7AFl0YTg2oN8NKSIBK4bHRnPIrjJJKv+dDFnUp5jQ==} + '@tiptap/extension-subscript@3.15.3': + resolution: {integrity: sha512-XkWBgLm1dqV+fP7OrnU1rOozdMO+EFq1gkWJ2+OZo4iN+zsWXIFqlUvDsB4w761foX1jxyzyZeCX9Y16XmeB4Q==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.15.3 + '@tiptap/pm': ^3.15.3 - '@tiptap/extension-subscript@2.27.1': - resolution: {integrity: sha512-n2jTaYriewwz3ES1o6Wt/OwREvPwi97n+yEsJ7i31wiuxGTdCP31eAuppC6DvixEvDt3/rZMZcNp8Ah9crlbnw==} + '@tiptap/extension-superscript@3.15.3': + resolution: {integrity: sha512-DAZ7ezI/Y065s3p6i9w65yb/FqUW8BuZkep+uKFUs2K0frrvmbpxREjmUyXjYRC1oB4KRGKV7wfP7F4XFE/4QQ==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.15.3 + '@tiptap/pm': ^3.15.3 - '@tiptap/extension-superscript@2.27.1': - resolution: {integrity: sha512-zTYOD7k3txm21rjeYHsf/VIpBe9IvVfNHSNayyY/JOgyQ/fW40cgX0gADNoT2ayAtRes4TvpcUYdgF9vC5bkJw==} + '@tiptap/extension-table@3.15.3': + resolution: {integrity: sha512-dJk0u2JX1J/3x/ps641qdxQPOiie5txQhs2M1srgDeeFu//ORCePAxryJCw1bgf0TEVwFWwFTCtcOFR5SSgMZQ==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.15.3 + '@tiptap/pm': ^3.15.3 - '@tiptap/extension-table-cell@2.27.1': - resolution: {integrity: sha512-VowNmz1kub2qfntWkU8jGA6DoCl9xjJBWSypuQIeiN/IRId3BMrJodT26pTNJ3ChDMtYaanWaUvYqckRxgTC2A==} + '@tiptap/extension-text-align@3.15.3': + resolution: {integrity: sha512-hkLeEKm44aqimyjv+D8JUxzDG/iNjDrSCGvGrMOPcpaKn4f8C5z1EKnEufT61RitNPBAxQMXUhmGQUNrmlICmQ==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.15.3 - '@tiptap/extension-table-header@2.27.1': - resolution: {integrity: sha512-lSbGB6kBp/sTVzAWl4v7v7ztL5XU3aTdlS7FhfGjpdsxd4zPKYG8kx+Uxgq25W9/BlCbnqHnO0poAMfOlspDQw==} + '@tiptap/extension-text-style@3.15.3': + resolution: {integrity: sha512-/M7fuGRPVkeM14rQ1bNiLZUs2N+FuVhIsLEwNKKk7GaTGKHzmkC1b2COmbICivuFYf90KWzaG0R+Pm7cnW6KaA==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.15.3 - '@tiptap/extension-table-row@2.27.1': - resolution: {integrity: sha512-3xtlmZ6NWDi5a42gK0qQQTeBUpJ2j1o7qyXTFkhQaJAeIFEqsemgSRhgXZxbwSmQQZsPJ/86KWBNVkT0FaRFDw==} + '@tiptap/extension-text@3.15.3': + resolution: {integrity: sha512-MhkBz8ZvrqOKtKNp+ZWISKkLUlTrDR7tbKZc2OnNcUTttL9dz0HwT+cg91GGz19fuo7ttDcfsPV6eVmflvGToA==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.15.3 - '@tiptap/extension-table@2.27.1': - resolution: {integrity: sha512-iOoOo0vYFzAogAZlw36DgmFfNM5vOkLqnApm81soO/YWpqtKAvBn+TMY4ss4OMDsOefUzBa6xqOJ0gJR5ZygjA==} + '@tiptap/extension-typography@3.15.3': + resolution: {integrity: sha512-BIpoSEIh1rB5pJtEmDbksRhRxy3og52CvYcG9EA8807WnCvLqgXXUEAYFZ0spbHhmMD0V5EwnHJOR1hHBVF4ww==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.15.3 - '@tiptap/extension-task-item@2.27.1': - resolution: {integrity: sha512-vaEtdos+9jApD6yRfD6F/xShikiZFHi7I0nswAmGKT/kE1wmHCUxme8OFMe7642e2OK0lqgHsUaOLxP/0nZJ5A==} + '@tiptap/extension-underline@3.15.3': + resolution: {integrity: sha512-r/IwcNN0W366jGu4Y0n2MiFq9jGa4aopOwtfWO4d+J0DyeS2m7Go3+KwoUqi0wQTiVU74yfi4DF6eRsMQ9/iHQ==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.15.3 - '@tiptap/extension-task-list@2.27.1': - resolution: {integrity: sha512-KRlYOZ6kdURvAspUrLVsC7mLkVW2DYhpj+7QxH7gVDZuAuoPUEmpJVcBVPq7GhPF9PccaRLru+n1Ege5VqvZ+Q==} + '@tiptap/extension-unique-id@3.15.3': + resolution: {integrity: sha512-QPkYkmeymoMq87rQBY6hnc6m9z+JiD+8Z66XifP9w4WKPAF4DAMkhtrMXyR07acMSNbtdTyelbDYVM1Hb0vi7Q==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.15.3 + '@tiptap/pm': ^3.15.3 - '@tiptap/extension-text-align@2.27.1': - resolution: {integrity: sha512-D7dLPk7y5mDn9ZNANQ4K2gCq4vy+Emm5AdeWOGzNeqJsYrBotiQYXd9rb1QYjdup2kzAoKduMTUXV92ujo5cEg==} + '@tiptap/extension-youtube@3.15.3': + resolution: {integrity: sha512-D/kohNEdXC54sGpzJyXa57uVrOvh3Clf+6OZL29fuewxCDmIrOOpeGlc96oeerGZoaIcoVst948mpgo0KwRrzA==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.15.3 - '@tiptap/extension-text-style@2.27.1': - resolution: {integrity: sha512-NagQ9qLk0Ril83gfrk+C65SvTqPjL3WVnLF2arsEVnCrxcx3uDOvdJW67f/K5HEwEHsoqJ4Zq9Irco/koXrOXA==} + '@tiptap/extensions@3.15.3': + resolution: {integrity: sha512-ycx/BgxR4rc9tf3ZyTdI98Z19yKLFfqM3UN+v42ChuIwkzyr9zyp7kG8dB9xN2lNqrD+5y/HyJobz/VJ7T90gA==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.15.3 + '@tiptap/pm': ^3.15.3 - '@tiptap/extension-text@2.27.1': - resolution: {integrity: sha512-a4GCT+GZ9tUwl82F4CEum9/+WsuW0/De9Be/NqrMmi7eNfAwbUTbLCTFU0gEvv25WMHCoUzaeNk/qGmzeVPJ1Q==} + '@tiptap/html@3.15.3': + resolution: {integrity: sha512-ftoWrgev05gDyor3YtJ5LJ0KHb/CKTR45zltGB9/cn+3IAOGuDrhmd8qO3o+E2VbsKR50yaiOCxtS36HYM9tQA==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.15.3 + '@tiptap/pm': ^3.15.3 + happy-dom: ^20.0.2 - '@tiptap/extension-typography@2.27.1': - resolution: {integrity: sha512-jAZU5IuWH9CtZlolQ1gRhV+bT75s19SXjadQwkk18gMMiapcaIVVTxUDWY6ycv9ge4cjRoaP3lqBviW3cGqhOA==} + '@tiptap/pm@3.15.3': + resolution: {integrity: sha512-Zm1BaU1TwFi3CQiisxjgnzzIus+q40bBKWLqXf6WEaus8Z6+vo1MT2pU52dBCMIRaW9XNDq3E5cmGtMc1AlveA==} + + '@tiptap/react@3.15.3': + resolution: {integrity: sha512-XvouB+Hrqw8yFmZLPEh+HWlMeRSjZfHSfWfWuw5d8LSwnxnPeu3Bg/rjHrRrdwb+7FumtzOnNWMorpb/PSOttQ==} peerDependencies: - '@tiptap/core': ^2.7.0 - - '@tiptap/extension-underline@2.27.1': - resolution: {integrity: sha512-fPTmfJFAQWg1O/os1pYSPVdtvly6eW/w5sDofG7pre+bdQUN+8s1cZYelSuj/ltNVioRaB2Ws7tvNgnHL0aAJQ==} - peerDependencies: - '@tiptap/core': ^2.7.0 - - '@tiptap/extension-youtube@2.27.1': - resolution: {integrity: sha512-HjBBgE0Zbch/S2UP0YYQXervfoBd4Trw0dYmlZbX9cXJcZv+QFx0vsPGmjAGlqzXf9Y8ZioWm8fso4u6AsUfTw==} - peerDependencies: - '@tiptap/core': ^2.7.0 - - '@tiptap/html@2.27.1': - resolution: {integrity: sha512-5iPo36g4nbBVoEVBQb6my4KNpNzu38gtCFXIIlAJdAZQvPs+XC8TkrnGK/G4UGpwBXCuQjSQm0iyn4znmQPDsw==} - peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 - - '@tiptap/pm@2.27.1': - resolution: {integrity: sha512-ijKo3+kIjALthYsnBmkRXAuw2Tswd9gd7BUR5OMfIcjGp8v576vKxOxrRfuYiUM78GPt//P0sVc1WV82H5N0PQ==} - - '@tiptap/react@2.27.1': - resolution: {integrity: sha512-leJximSjYJuhLJQv9azOP9R7w6zuxVgKOHYT4w83Gte7GhWMpNL6xRWzld280vyq/YW/cSYjPb/8ESEOgKNBdQ==} - peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.15.3 + '@tiptap/pm': ^3.15.3 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tiptap/starter-kit@2.27.1': - resolution: {integrity: sha512-uQQlP0Nmn9eq19qm8YoOeloEfmcGbPpB1cujq54Q6nPgxaBozR7rE7tXbFTinxRW2+Hr7XyNWhpjB7DMNkdU2Q==} + '@tiptap/starter-kit@3.15.3': + resolution: {integrity: sha512-ia+eQr9Mt1ln2UO+kK4kFTJOrZK4GhvZXFjpCCYuHtco3rhr2fZAIxEEY4cl/vo5VO5WWyPqxhkFeLcoWmNjSw==} - '@tiptap/suggestion@2.27.1': - resolution: {integrity: sha512-yTy75ZMYgVWM18cl7YxLqMJ7TorQTGysSd1aKmBA9qd8uzYlvLMmHKE9qBDxM9HXODBz1DA/BLLm9esv2enmFw==} + '@tiptap/suggestion@3.15.3': + resolution: {integrity: sha512-+CbaHhPfKUe+fNpUIQaOPhh6xI+xL5jbK1zw++U+CZIRrVAAmHRhO+D0O2HdiE1RK7596y8bRqMiB2CRHF7emA==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.15.3 + '@tiptap/pm': ^3.15.3 + + '@tiptap/y-tiptap@3.0.1': + resolution: {integrity: sha512-F3hj5X77ckmyIywbCQpKgyX3xKra2/acJPWaV5R9wqp0cUPBmm62FYbkQ6HaqxH1VhCkUhhAZcDSQjbjj7tnWw==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + peerDependencies: + prosemirror-model: ^1.7.1 + prosemirror-state: ^1.2.3 + prosemirror-view: ^1.9.10 + y-protocols: ^1.0.1 + yjs: ^13.5.38 '@tokenizer/inflate@0.4.1': resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} @@ -5263,6 +5232,9 @@ packages: async-lock@1.4.1: resolution: {integrity: sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==} + async-mutex@0.5.0: + resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==} + async@3.2.5: resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} @@ -5369,9 +5341,6 @@ packages: bluebird@3.4.7: resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} - bluebird@3.7.2: - resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -6077,10 +6046,6 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - denque@1.5.1: - resolution: {integrity: sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==} - engines: {node: '>=0.10'} - denque@2.1.0: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} @@ -6260,10 +6225,6 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - entities@5.0.0: - resolution: {integrity: sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==} - engines: {node: '>=0.12'} - entities@6.0.1: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} @@ -6492,6 +6453,10 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-equals@5.3.4: + resolution: {integrity: sha512-d+yU9iNQbbC098NOuMlAIth/g+owbpX/uuOkH/DQcC2fMMyjOlX292Op29DrUKq388m4UUyOdWakUH/msGypOg==} + engines: {node: '>=6.0.0'} + fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -6965,10 +6930,6 @@ packages: invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} - ioredis@4.28.5: - resolution: {integrity: sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==} - engines: {node: '>=6'} - ioredis@5.4.1: resolution: {integrity: sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==} engines: {node: '>=12.22.0'} @@ -7586,11 +7547,6 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - lib0@0.2.108: - resolution: {integrity: sha512-+3eK/B0SqYoZiQu9fNk4VEc6EX8cb0Li96tPGKgugzoGj/OdRdREtuTLvUW+mtinoB2mFiJjSqOJBIaMkAGhxQ==} - engines: {node: '>=16'} - hasBin: true - lib0@0.2.114: resolution: {integrity: sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==} engines: {node: '>=16'} @@ -7655,9 +7611,6 @@ packages: lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} - lodash.flatten@4.4.0: - resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} - lodash.includes@4.3.0: resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} @@ -8036,6 +7989,7 @@ packages: next@14.2.10: resolution: {integrity: sha512-sDDExXnh33cY3RkS9JuFEKaS4HmlWmDKP1VJioucCG6z5KuA008DPsDZOzi8UfqEk3Ii+2NCQSJrfbEWtZZfww==} engines: {node: '>=18.17.0'} + deprecated: This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/security-update-2025-12-11 for more details. hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 @@ -8266,10 +8220,6 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - p-map@2.1.0: - resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} - engines: {node: '>=6'} - p-queue@6.6.2: resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} engines: {node: '>=8'} @@ -8702,8 +8652,8 @@ packages: prosemirror-schema-basic@1.2.3: resolution: {integrity: sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA==} - prosemirror-schema-list@1.4.1: - resolution: {integrity: sha512-jbDyaP/6AFfDfu70VzySsD75Om2t3sXTOdl5+31Wlxlg62td1haUpty/ybajSfJ1pkGadlOfwQq9kgW5IMo1Rg==} + prosemirror-schema-list@1.5.1: + resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} prosemirror-state@1.4.3: resolution: {integrity: sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==} @@ -8970,9 +8920,6 @@ packages: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} - redis-commands@1.7.0: - resolution: {integrity: sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==} - redis-errors@1.2.0: resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} engines: {node: '>=4'} @@ -8981,10 +8928,6 @@ packages: resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} engines: {node: '>=4'} - redlock@4.2.0: - resolution: {integrity: sha512-j+oQlG+dOwcetUt2WJWttu4CZVeRzUrcVcISFmEmfyuwCVSJ93rDT7YSgg7H7rnxwoRyk/jU46kycVka5tW7jA==} - engines: {node: '>=8.0.0'} - redux@4.2.1: resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} @@ -9536,9 +9479,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==} @@ -9888,6 +9828,11 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + utf8-byte-length@1.0.4: resolution: {integrity: sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==} @@ -10273,8 +10218,8 @@ packages: resolution: {integrity: sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==} engines: {node: '>=12'} - yjs@13.6.27: - resolution: {integrity: sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==} + yjs@13.6.29: + resolution: {integrity: sha512-kHqDPdltoXH+X4w1lVmMtddE3Oeqq48nM40FD5ojTd8xYhQpzIDcfE2keMSU5bAgRPJBe225WTUdyUgj1DtbiQ==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} yn@3.1.1: @@ -10293,10 +10238,6 @@ packages: resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} engines: {node: '>=18'} - zeed-dom@0.15.1: - resolution: {integrity: sha512-dtZ0aQSFyZmoJS0m06/xBN1SazUBPL5HpzlAcs/KcRW0rzadYw12deQBjeMhGKMMeGEp7bA9vmikMLaO4exBcg==} - engines: {node: '>=14.13.1'} - zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -12673,58 +12614,57 @@ snapshots: '@floating-ui/utils@0.2.10': {} - '@hocuspocus/common@2.15.3': + '@hocuspocus/common@3.4.3': dependencies: lib0: 0.2.114 - '@hocuspocus/extension-redis@2.15.3(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27)': + '@hocuspocus/extension-redis@3.4.3(y-protocols@1.0.6(yjs@13.6.29))(yjs@13.6.29)': dependencies: - '@hocuspocus/server': 2.15.3(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27) - ioredis: 4.28.5 + '@hocuspocus/server': 3.4.3(y-protocols@1.0.6(yjs@13.6.29))(yjs@13.6.29) + '@sesamecare-oss/redlock': 1.4.0(ioredis@5.8.2) + ioredis: 5.8.2 kleur: 4.1.5 lodash.debounce: 4.0.8 - redlock: 4.2.0 - uuid: 11.1.0 - y-protocols: 1.0.6(yjs@13.6.27) - yjs: 13.6.27 + y-protocols: 1.0.6(yjs@13.6.29) + yjs: 13.6.29 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - '@hocuspocus/provider@2.15.3(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27)': + '@hocuspocus/provider@3.4.3(y-protocols@1.0.6(yjs@13.6.29))(yjs@13.6.29)': dependencies: - '@hocuspocus/common': 2.15.3 + '@hocuspocus/common': 3.4.3 '@lifeomic/attempt': 3.0.3 lib0: 0.2.114 ws: 8.19.0 - y-protocols: 1.0.6(yjs@13.6.27) - yjs: 13.6.27 + y-protocols: 1.0.6(yjs@13.6.29) + yjs: 13.6.29 transitivePeerDependencies: - bufferutil - utf-8-validate - '@hocuspocus/server@2.15.3(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27)': + '@hocuspocus/server@3.4.3(y-protocols@1.0.6(yjs@13.6.29))(yjs@13.6.29)': dependencies: - '@hocuspocus/common': 2.15.3 + '@hocuspocus/common': 3.4.3 async-lock: 1.4.1 + async-mutex: 0.5.0 kleur: 4.1.5 lib0: 0.2.114 - uuid: 11.1.0 ws: 8.19.0 - y-protocols: 1.0.6(yjs@13.6.27) - yjs: 13.6.27 + y-protocols: 1.0.6(yjs@13.6.29) + yjs: 13.6.29 transitivePeerDependencies: - bufferutil - utf-8-validate - '@hocuspocus/transformer@2.15.3(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)(y-prosemirror@1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))(yjs@13.6.27)': + '@hocuspocus/transformer@3.4.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)(y-prosemirror@1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.29))(yjs@13.6.29))(yjs@13.6.29)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 - '@tiptap/starter-kit': 2.27.1 - y-prosemirror: 1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27) - yjs: 13.6.27 + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) + '@tiptap/pm': 3.15.3 + '@tiptap/starter-kit': 3.15.3 + y-prosemirror: 1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.29))(yjs@13.6.29) + yjs: 13.6.29 '@humanfs/core@0.19.1': {} @@ -13747,8 +13687,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)': @@ -14172,6 +14110,10 @@ snapshots: domhandler: 5.0.3 selderee: 0.11.0 + '@sesamecare-oss/redlock@1.4.0(ioredis@5.8.2)': + dependencies: + ioredis: 5.8.2 + '@sinclair/typebox@0.27.8': {} '@sindresorhus/slugify@1.1.0': @@ -14622,212 +14564,195 @@ snapshots: '@tanstack/query-core': 5.90.17 react: 18.3.1 - '@tiptap/core@2.27.1(@tiptap/pm@2.27.1)': + '@tiptap/core@3.15.3(@tiptap/pm@3.15.3)': dependencies: - '@tiptap/pm': 2.27.1 + '@tiptap/pm': 3.15.3 - '@tiptap/extension-blockquote@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-blockquote@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) - '@tiptap/extension-bold@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-bold@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) - '@tiptap/extension-bubble-menu@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-bubble-menu@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 - tippy.js: 6.3.7 + '@floating-ui/dom': 1.7.3 + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) + '@tiptap/pm': 3.15.3 + optional: true - '@tiptap/extension-bullet-list@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-bullet-list@3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/extension-list': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) - '@tiptap/extension-character-count@2.27.2(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-code-block@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) + '@tiptap/pm': 3.15.3 - '@tiptap/extension-code-block-lowlight@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/extension-code-block@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)(highlight.js@11.11.1)(lowlight@3.3.0)': + '@tiptap/extension-code@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/extension-code-block': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 - highlight.js: 11.11.1 - lowlight: 3.3.0 + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) - '@tiptap/extension-code-block@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-collaboration-caret@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)(@tiptap/y-tiptap@3.0.1(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.29))(yjs@13.6.29))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) + '@tiptap/pm': 3.15.3 + '@tiptap/y-tiptap': 3.0.1(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.29))(yjs@13.6.29) - '@tiptap/extension-code@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-collaboration@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)(@tiptap/y-tiptap@3.0.1(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.29))(yjs@13.6.29))(yjs@13.6.29)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) + '@tiptap/pm': 3.15.3 + '@tiptap/y-tiptap': 3.0.1(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.29))(yjs@13.6.29) + yjs: 13.6.29 - '@tiptap/extension-collaboration-cursor@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(y-prosemirror@1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))': + '@tiptap/extension-color@3.15.3(@tiptap/extension-text-style@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - y-prosemirror: 1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27) + '@tiptap/extension-text-style': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) - '@tiptap/extension-collaboration@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)(y-prosemirror@1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))': + '@tiptap/extension-document@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 - y-prosemirror: 1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27) + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) - '@tiptap/extension-color@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/extension-text-style@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)))': + '@tiptap/extension-dropcursor@3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/extension-text-style': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + '@tiptap/extensions': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) - '@tiptap/extension-document@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-floating-menu@3.15.3(@floating-ui/dom@1.7.3)(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@floating-ui/dom': 1.7.3 + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) + '@tiptap/pm': 3.15.3 + optional: true - '@tiptap/extension-dropcursor@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-gapcursor@3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/extensions': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) - '@tiptap/extension-floating-menu@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-hard-break@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 - tippy.js: 6.3.7 + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) - '@tiptap/extension-gapcursor@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-heading@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) - '@tiptap/extension-hard-break@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-highlight@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) - '@tiptap/extension-heading@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-history@3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/extensions': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) - '@tiptap/extension-highlight@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-horizontal-rule@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) + '@tiptap/pm': 3.15.3 - '@tiptap/extension-history@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-image@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) - '@tiptap/extension-horizontal-rule@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-italic@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) - '@tiptap/extension-image@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-link@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - - '@tiptap/extension-italic@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': - dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - - '@tiptap/extension-link@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': - dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) + '@tiptap/pm': 3.15.3 linkifyjs: 4.3.2 - '@tiptap/extension-list-item@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-list-item@3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/extension-list': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) - '@tiptap/extension-list-keymap@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-list-keymap@3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/extension-list': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) - '@tiptap/extension-ordered-list@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) + '@tiptap/pm': 3.15.3 - '@tiptap/extension-paragraph@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-ordered-list@3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/extension-list': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) - '@tiptap/extension-placeholder@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-paragraph@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) - '@tiptap/extension-strike@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-placeholder@3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/extensions': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) - '@tiptap/extension-subscript@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-strike@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) - '@tiptap/extension-superscript@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-subscript@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) + '@tiptap/pm': 3.15.3 - '@tiptap/extension-table-cell@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-superscript@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) + '@tiptap/pm': 3.15.3 - '@tiptap/extension-table-header@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-table@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) + '@tiptap/pm': 3.15.3 - '@tiptap/extension-table-row@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-text-align@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) - '@tiptap/extension-table@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-text-style@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) - '@tiptap/extension-task-item@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-text@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) - '@tiptap/extension-task-list@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-typography@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) - '@tiptap/extension-text-align@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-underline@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) - '@tiptap/extension-text-style@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-unique-id@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) + '@tiptap/pm': 3.15.3 + uuid: 10.0.0 - '@tiptap/extension-text@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-youtube@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) - '@tiptap/extension-typography@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extensions@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) + '@tiptap/pm': 3.15.3 - '@tiptap/extension-underline@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/html@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)(happy-dom@20.1.0)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) + '@tiptap/pm': 3.15.3 + happy-dom: 20.1.0 - '@tiptap/extension-youtube@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': - dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - - '@tiptap/html@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': - dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 - zeed-dom: 0.15.1 - - '@tiptap/pm@2.27.1': + '@tiptap/pm@3.15.3': dependencies: prosemirror-changeset: 2.3.1 prosemirror-collab: 1.3.1 @@ -14841,53 +14766,70 @@ snapshots: prosemirror-menu: 1.2.4 prosemirror-model: 1.25.1 prosemirror-schema-basic: 1.2.3 - prosemirror-schema-list: 1.4.1 + prosemirror-schema-list: 1.5.1 prosemirror-state: 1.4.3 prosemirror-tables: 1.7.1 prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0) prosemirror-transform: 1.10.4 prosemirror-view: 1.40.0 - '@tiptap/react@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@tiptap/react@3.15.3(@floating-ui/dom@1.7.3)(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/extension-bubble-menu': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/extension-floating-menu': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) + '@tiptap/pm': 3.15.3 + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 '@types/use-sync-external-store': 0.0.6 - fast-deep-equal: 3.1.3 + fast-equals: 5.3.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - use-sync-external-store: 1.2.2(react@18.3.1) + use-sync-external-store: 1.6.0(react@18.3.1) + optionalDependencies: + '@tiptap/extension-bubble-menu': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) + '@tiptap/extension-floating-menu': 3.15.3(@floating-ui/dom@1.7.3)(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) + transitivePeerDependencies: + - '@floating-ui/dom' - '@tiptap/starter-kit@2.27.1': + '@tiptap/starter-kit@3.15.3': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/extension-blockquote': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-bold': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-bullet-list': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-code': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-code-block': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/extension-document': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-dropcursor': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/extension-gapcursor': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/extension-hard-break': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-heading': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-history': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/extension-horizontal-rule': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/extension-italic': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-list-item': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-ordered-list': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-paragraph': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-strike': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-text': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-text-style': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/pm': 2.27.1 + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) + '@tiptap/extension-blockquote': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) + '@tiptap/extension-bold': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) + '@tiptap/extension-bullet-list': 3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)) + '@tiptap/extension-code': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) + '@tiptap/extension-code-block': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) + '@tiptap/extension-document': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) + '@tiptap/extension-dropcursor': 3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)) + '@tiptap/extension-gapcursor': 3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)) + '@tiptap/extension-hard-break': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) + '@tiptap/extension-heading': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) + '@tiptap/extension-horizontal-rule': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) + '@tiptap/extension-italic': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) + '@tiptap/extension-link': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) + '@tiptap/extension-list': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) + '@tiptap/extension-list-item': 3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)) + '@tiptap/extension-list-keymap': 3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)) + '@tiptap/extension-ordered-list': 3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)) + '@tiptap/extension-paragraph': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) + '@tiptap/extension-strike': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) + '@tiptap/extension-text': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) + '@tiptap/extension-underline': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)) + '@tiptap/extensions': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3) + '@tiptap/pm': 3.15.3 - '@tiptap/suggestion@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/suggestion@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/core': 3.15.3(@tiptap/pm@3.15.3) + '@tiptap/pm': 3.15.3 + + '@tiptap/y-tiptap@3.0.1(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.29))(yjs@13.6.29)': + dependencies: + lib0: 0.2.114 + prosemirror-model: 1.25.1 + prosemirror-state: 1.4.3 + prosemirror-view: 1.40.0 + y-protocols: 1.0.6(yjs@13.6.29) + yjs: 13.6.29 '@tokenizer/inflate@0.4.1': dependencies: @@ -15820,6 +15762,10 @@ snapshots: async-lock@1.4.1: {} + async-mutex@0.5.0: + dependencies: + tslib: 2.8.1 + async@3.2.5: {} asynckit@0.4.0: {} @@ -16002,8 +15948,6 @@ snapshots: bluebird@3.4.7: {} - bluebird@3.7.2: {} - boolbase@1.0.0: {} bowser@2.11.0: {} @@ -16750,8 +16694,6 @@ snapshots: delayed-stream@1.0.0: {} - denque@1.5.1: {} - denque@2.1.0: {} depd@2.0.0: {} @@ -16939,8 +16881,6 @@ snapshots: entities@4.5.0: {} - entities@5.0.0: {} - entities@6.0.1: {} env-paths@2.2.1: {} @@ -17326,6 +17266,8 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-equals@5.3.4: {} + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -17846,22 +17788,6 @@ snapshots: dependencies: loose-envify: 1.4.0 - ioredis@4.28.5: - dependencies: - cluster-key-slot: 1.1.2 - debug: 4.4.1 - denque: 1.5.1 - lodash.defaults: 4.2.0 - lodash.flatten: 4.4.0 - lodash.isarguments: 3.1.0 - p-map: 2.1.0 - redis-commands: 1.7.0 - redis-errors: 1.2.0 - redis-parser: 3.0.0 - standard-as-callback: 2.1.0 - transitivePeerDependencies: - - supports-color - ioredis@5.4.1: dependencies: '@ioredis/commands': 1.2.0 @@ -18704,10 +18630,6 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - lib0@0.2.108: - dependencies: - isomorphic.js: 0.2.5 - lib0@0.2.114: dependencies: isomorphic.js: 0.2.5 @@ -18764,8 +18686,6 @@ snapshots: lodash.defaults@4.2.0: {} - lodash.flatten@4.4.0: {} - lodash.includes@4.3.0: {} lodash.isarguments@3.1.0: {} @@ -19502,8 +19422,6 @@ snapshots: dependencies: p-limit: 3.1.0 - p-map@2.1.0: {} - p-queue@6.6.2: dependencies: eventemitter3: 4.0.7 @@ -19948,7 +19866,7 @@ snapshots: dependencies: prosemirror-model: 1.25.1 - prosemirror-schema-list@1.4.1: + prosemirror-schema-list@1.5.1: dependencies: prosemirror-model: 1.25.1 prosemirror-state: 1.4.3 @@ -20248,18 +20166,12 @@ snapshots: dependencies: resolve: 1.22.8 - redis-commands@1.7.0: {} - redis-errors@1.2.0: {} redis-parser@3.0.0: dependencies: redis-errors: 1.2.0 - redlock@4.2.0: - dependencies: - bluebird: 3.7.2 - redux@4.2.1: dependencies: '@babel/runtime': 7.25.6 @@ -20901,10 +20813,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: {} @@ -21245,6 +21153,10 @@ snapshots: dependencies: react: 18.3.1 + use-sync-external-store@1.6.0(react@18.3.1): + dependencies: + react: 18.3.1 + utf8-byte-length@1.0.4: {} util-deprecate@1.0.2: {} @@ -21521,24 +21433,24 @@ snapshots: xtend@4.0.2: {} - y-indexeddb@9.0.12(yjs@13.6.27): + y-indexeddb@9.0.12(yjs@13.6.29): dependencies: lib0: 0.2.88 - yjs: 13.6.27 + yjs: 13.6.29 - y-prosemirror@1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27): + y-prosemirror@1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.29))(yjs@13.6.29): dependencies: lib0: 0.2.114 prosemirror-model: 1.25.1 prosemirror-state: 1.4.3 prosemirror-view: 1.40.0 - y-protocols: 1.0.6(yjs@13.6.27) - yjs: 13.6.27 + y-protocols: 1.0.6(yjs@13.6.29) + yjs: 13.6.29 - y-protocols@1.0.6(yjs@13.6.27): + y-protocols@1.0.6(yjs@13.6.29): dependencies: lib0: 0.2.114 - yjs: 13.6.27 + yjs: 13.6.29 y18n@4.0.3: {} @@ -21588,9 +21500,9 @@ snapshots: buffer-crc32: 0.2.13 pend: 1.2.0 - yjs@13.6.27: + yjs@13.6.29: dependencies: - lib0: 0.2.108 + lib0: 0.2.114 yn@3.1.1: {} @@ -21600,11 +21512,6 @@ snapshots: yoctocolors-cjs@2.1.2: {} - zeed-dom@0.15.1: - dependencies: - css-what: 6.1.0 - entities: 5.0.0 - zod@3.25.76: {} zod@4.3.5: {}