From 9f38c618824ed53714141fdeae9fd715b683cbfa Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Fri, 27 Mar 2026 22:25:35 +0000 Subject: [PATCH] use local resizable --- .../components/common/node-resize-handles.ts | 2 +- package.json | 3 +- packages/editor-ext/src/lib/drawio.ts | 5 +- packages/editor-ext/src/lib/excalidraw.ts | 5 +- packages/editor-ext/src/lib/image/image.ts | 4 +- .../editor-ext/src/lib/resizable-nodeview.ts | 1101 +++++++++++++++++ packages/editor-ext/src/lib/video/video.ts | 5 +- pnpm-lock.yaml | 279 +++-- 8 files changed, 1252 insertions(+), 152 deletions(-) create mode 100644 packages/editor-ext/src/lib/resizable-nodeview.ts diff --git a/apps/client/src/features/editor/components/common/node-resize-handles.ts b/apps/client/src/features/editor/components/common/node-resize-handles.ts index 0785845d..2ad4404c 100644 --- a/apps/client/src/features/editor/components/common/node-resize-handles.ts +++ b/apps/client/src/features/editor/components/common/node-resize-handles.ts @@ -1,4 +1,4 @@ -import type { ResizableNodeViewDirection } from "@tiptap/core"; +import type { ResizableNodeViewDirection } from "@docmost/editor-ext/lib/resizable-nodeview"; import classes from "./node-resize.module.css"; export function createResizeHandle( diff --git a/package.json b/package.json index 63994abd..78a7c295 100644 --- a/package.json +++ b/package.json @@ -94,8 +94,7 @@ "packageManager": "pnpm@10.4.0", "pnpm": { "patchedDependencies": { - "react-arborist@3.4.0": "patches/react-arborist@3.4.0.patch", - "@tiptap/core": "patches/@tiptap__core.patch" + "react-arborist@3.4.0": "patches/react-arborist@3.4.0.patch" }, "overrides": { "prosemirror-changeset": "2.4.0", diff --git a/packages/editor-ext/src/lib/drawio.ts b/packages/editor-ext/src/lib/drawio.ts index d3b69c72..8b000bcc 100644 --- a/packages/editor-ext/src/lib/drawio.ts +++ b/packages/editor-ext/src/lib/drawio.ts @@ -1,5 +1,6 @@ -import { Node, mergeAttributes, ResizableNodeView } from "@tiptap/core"; -import type { ResizableNodeViewDirection } from "@tiptap/core"; +import { Node, mergeAttributes } from "@tiptap/core"; +import { ResizableNodeView } from "./resizable-nodeview"; +import type { ResizableNodeViewDirection } from "./resizable-nodeview"; import { ReactNodeViewRenderer } from "@tiptap/react"; import { normalizeFileUrl } from "./media-utils"; diff --git a/packages/editor-ext/src/lib/excalidraw.ts b/packages/editor-ext/src/lib/excalidraw.ts index 45142776..de08b677 100644 --- a/packages/editor-ext/src/lib/excalidraw.ts +++ b/packages/editor-ext/src/lib/excalidraw.ts @@ -1,5 +1,6 @@ -import { Node, mergeAttributes, ResizableNodeView } from "@tiptap/core"; -import type { ResizableNodeViewDirection } from "@tiptap/core"; +import { Node, mergeAttributes } from "@tiptap/core"; +import { ResizableNodeView } from "./resizable-nodeview"; +import type { ResizableNodeViewDirection } from "./resizable-nodeview"; import { ReactNodeViewRenderer } from "@tiptap/react"; import { normalizeFileUrl } from "./media-utils"; diff --git a/packages/editor-ext/src/lib/image/image.ts b/packages/editor-ext/src/lib/image/image.ts index 8631d7fe..d5d42921 100644 --- a/packages/editor-ext/src/lib/image/image.ts +++ b/packages/editor-ext/src/lib/image/image.ts @@ -4,10 +4,10 @@ import { ReactNodeViewRenderer } from "@tiptap/react"; import { mergeAttributes, Range, - ResizableNodeView, } from "@tiptap/core"; +import { ResizableNodeView } from "../resizable-nodeview"; +import type { ResizableNodeViewDirection } from "../resizable-nodeview"; import { normalizeFileUrl } from "../media-utils"; -import type { ResizableNodeViewDirection } from "@tiptap/core"; export type ImageResizeOptions = { enabled: boolean; diff --git a/packages/editor-ext/src/lib/resizable-nodeview.ts b/packages/editor-ext/src/lib/resizable-nodeview.ts new file mode 100644 index 00000000..3a353c1f --- /dev/null +++ b/packages/editor-ext/src/lib/resizable-nodeview.ts @@ -0,0 +1,1101 @@ +// https://github.com/ueberdosis/tiptap/blob/91c51be53c4655ef07e29ec489471524debfa0ca/packages/core/src/lib/ResizableNodeView.ts - MIT +import type { Node as PMNode } from '@tiptap/pm/model'; +import type { Decoration, DecorationSource, NodeView } from '@tiptap/pm/view'; +import type { Editor } from '@tiptap/core'; + +const isTouchEvent = (e: MouseEvent | TouchEvent): e is TouchEvent => { + return 'touches' in e; +}; + +/** + * Directions where resize handles can be placed + * + * @example + * - `'top'` - Top edge handle + * - `'bottom-right'` - Bottom-right corner handle + */ +export type ResizableNodeViewDirection = + | 'top' + | 'right' + | 'bottom' + | 'left' + | 'top-right' + | 'top-left' + | 'bottom-right' + | 'bottom-left'; + +/** + * Dimensions for the resizable node in pixels + */ +export type ResizableNodeDimensions = { + /** Width in pixels */ + width: number; + /** Height in pixels */ + height: number; +}; + +/** + * Configuration options for creating a ResizableNodeView + * + * @example + * ```ts + * new ResizableNodeView({ + * element: imgElement, + * node, + * getPos, + * onResize: (width, height) => { + * imgElement.style.width = `${width}px` + * imgElement.style.height = `${height}px` + * }, + * onCommit: (width, height) => { + * editor.commands.updateAttributes('image', { width, height }) + * }, + * onUpdate: (node) => true, + * options: { + * directions: ['bottom-right', 'bottom-left'], + * min: { width: 100, height: 100 }, + * preserveAspectRatio: true + * } + * }) + * ``` + */ +export type ResizableNodeViewOptions = { + /** + * The DOM element to make resizable (e.g., an img, video, or iframe element) + */ + element: HTMLElement; + + /** + * The DOM element that will hold the editable content element + */ + contentElement?: HTMLElement; + + /** + * The ProseMirror node instance + */ + node: PMNode; + + /** + * The Tiptap editor instance + */ + editor: Editor; + + /** + * Function that returns the current position of the node in the document + */ + getPos: () => number | undefined; + + /** + * Callback fired continuously during resize with current dimensions. + * Use this to update the element's visual size in real-time. + * + * @param width - Current width in pixels + * @param height - Current height in pixels + * + * @example + * ```ts + * onResize: (width, height) => { + * element.style.width = `${width}px` + * element.style.height = `${height}px` + * } + * ``` + */ + onResize?: (width: number, height: number) => void; + + /** + * Callback fired once when resize completes with final dimensions. + * Use this to persist the new size to the node's attributes. + * + * @param width - Final width in pixels + * @param height - Final height in pixels + * + * @example + * ```ts + * onCommit: (width, height) => { + * const pos = getPos() + * if (pos !== undefined) { + * editor.commands.updateAttributes('image', { width, height }) + * } + * } + * ``` + */ + onCommit: (width: number, height: number) => void; + + /** + * Callback for handling node updates. + * Return `true` to accept the update, `false` to reject it. + * + * @example + * ```ts + * onUpdate: (node, decorations, innerDecorations) => { + * if (node.type !== this.node.type) return false + * return true + * } + * ``` + */ + onUpdate: NodeView['update']; + + /** + * Optional configuration for resize behavior and styling + */ + options?: { + /** + * Which resize handles to display. + * @default ['bottom-left', 'bottom-right', 'top-left', 'top-right'] + * + * @example + * ```ts + * // Only show corner handles + * directions: ['top-left', 'top-right', 'bottom-left', 'bottom-right'] + * + * // Only show right edge handle + * directions: ['right'] + * ``` + */ + directions?: ResizableNodeViewDirection[]; + + /** + * Minimum dimensions in pixels + * @default { width: 8, height: 8 } + * + * @example + * ```ts + * min: { width: 100, height: 50 } + * ``` + */ + min?: Partial; + + /** + * Maximum dimensions in pixels + * @default undefined (no maximum) + * + * @example + * ```ts + * max: { width: 1000, height: 800 } + * ``` + */ + max?: Partial; + + /** + * Always preserve aspect ratio when resizing. + * When `false`, aspect ratio is preserved only when Shift key is pressed. + * @default false + * + * @example + * ```ts + * preserveAspectRatio: true // Always lock aspect ratio + * ``` + */ + preserveAspectRatio?: boolean; + + /** + * Custom CSS class names for styling + * + * @example + * ```ts + * className: { + * container: 'resize-container', + * wrapper: 'resize-wrapper', + * handle: 'resize-handle', + * resizing: 'is-resizing' + * } + * ``` + */ + className?: { + /** Class for the outer container element */ + container?: string; + /** Class for the wrapper element that contains the resizable element */ + wrapper?: string; + /** Class applied to all resize handles */ + handle?: string; + /** Class added to container while actively resizing */ + resizing?: string; + }; + + /** + * Optional callback for creating custom resize handle elements. + * + * This function allows developers to define their own handle element + * (e.g., custom icons, classes, or styles) for a given resize direction. + * It is called internally for each handle direction. + * + * @param direction - The direction of the handle being created (e.g., 'top', 'bottom-right'). + * @returns The custom handle HTMLElement. + * + * @example + * ```ts + * createCustomHandle: (direction) => { + * const handle = document.createElement('div') + * handle.dataset.resizeHandle = direction + * handle.style.position = 'absolute' + * handle.className = 'tiptap-custom-handle' + * + * const isTop = direction.includes('top') + * const isBottom = direction.includes('bottom') + * const isLeft = direction.includes('left') + * const isRight = direction.includes('right') + * + * if (isTop) handle.style.top = '0' + * if (isBottom) handle.style.bottom = '0' + * if (isLeft) handle.style.left = '0' + * if (isRight) handle.style.right = '0' + * + * // Edge handles span the full width or height + * if (direction === 'top' || direction === 'bottom') { + * handle.style.left = '0' + * handle.style.right = '0' + * } + * + * if (direction === 'left' || direction === 'right') { + * handle.style.top = '0' + * handle.style.bottom = '0' + * } + * + * return handle + * } + * ``` + */ + createCustomHandle?: (direction: ResizableNodeViewDirection) => HTMLElement; + }; +}; + +/** + * A NodeView implementation that adds resize handles to any DOM element. + * + * This class creates a resizable node view for Tiptap/ProseMirror editors. + * It wraps your element with resize handles and manages the resize interaction, + * including aspect ratio preservation, min/max constraints, and keyboard modifiers. + * + * @example + * ```ts + * // Basic usage in a Tiptap extension + * addNodeView() { + * return ({ node, getPos }) => { + * const img = document.createElement('img') + * img.src = node.attrs.src + * + * return new ResizableNodeView({ + * element: img, + * node, + * getPos, + * onResize: (width, height) => { + * img.style.width = `${width}px` + * img.style.height = `${height}px` + * }, + * onCommit: (width, height) => { + * this.editor.commands.updateAttributes('image', { width, height }) + * }, + * onUpdate: () => true, + * options: { + * min: { width: 100, height: 100 }, + * preserveAspectRatio: true + * } + * }) + * } + * } + * ``` + */ +export class ResizableNodeView { + /** The ProseMirror node instance */ + node: PMNode; + + /** The Tiptap editor instance */ + editor: Editor; + + /** The DOM element being made resizable */ + element: HTMLElement; + + /** The editable DOM element inside the DOM */ + contentElement?: HTMLElement; + + /** The outer container element (returned as NodeView.dom) */ + container: HTMLElement; + + /** The wrapper element that contains the element and handles */ + wrapper: HTMLElement; + + /** Function to get the current node position */ + getPos: () => number | undefined; + + /** Callback fired during resize */ + onResize?: (width: number, height: number) => void; + + /** Callback fired when resize completes */ + onCommit: (width: number, height: number) => void; + + /** Callback for node updates */ + onUpdate?: NodeView['update']; + + /** Active resize handle directions */ + directions: ResizableNodeViewDirection[] = [ + 'bottom-left', + 'bottom-right', + 'top-left', + 'top-right', + ]; + + /** Minimum allowed dimensions */ + minSize: ResizableNodeDimensions = { + height: 8, + width: 8, + }; + + /** Maximum allowed dimensions (optional) */ + maxSize?: Partial; + + /** Whether to always preserve aspect ratio */ + preserveAspectRatio: boolean = false; + + /** CSS class names for elements */ + classNames = { + container: '', + wrapper: '', + handle: '', + resizing: '', + }; + + /** Optional callback for creating custom resize handles */ + createCustomHandle?: (direction: ResizableNodeViewDirection) => HTMLElement; + + /** Initial width of the element (for aspect ratio calculation) */ + private initialWidth: number = 0; + + /** Initial height of the element (for aspect ratio calculation) */ + private initialHeight: number = 0; + + /** Calculated aspect ratio (width / height) */ + private aspectRatio: number = 1; + + /** Whether a resize operation is currently active */ + private isResizing: boolean = false; + + /** The handle currently being dragged */ + private activeHandle: ResizableNodeViewDirection | null = null; + + /** Starting mouse X position when resize began */ + private startX: number = 0; + + /** Starting mouse Y position when resize began */ + private startY: number = 0; + + /** Element width when resize began */ + private startWidth: number = 0; + + /** Element height when resize began */ + private startHeight: number = 0; + + /** Whether Shift key is currently pressed (for temporary aspect ratio lock) */ + private isShiftKeyPressed: boolean = false; + + /** Last known editable state of the editor */ + private lastEditableState: boolean | undefined = undefined; + + /** Map of handle elements by direction */ + private handleMap = new Map(); + + /** + * Creates a new ResizableNodeView instance. + * + * The constructor sets up the resize handles, applies initial sizing from + * node attributes, and configures all resize behavior options. + * + * @param options - Configuration options for the resizable node view + */ + constructor(options: ResizableNodeViewOptions) { + this.node = options.node; + this.editor = options.editor; + this.element = options.element; + this.contentElement = options.contentElement; + + this.getPos = options.getPos; + + this.onResize = options.onResize; + this.onCommit = options.onCommit; + this.onUpdate = options.onUpdate; + + if (options.options?.min) { + this.minSize = { + ...this.minSize, + ...options.options.min, + }; + } + + if (options.options?.max) { + this.maxSize = options.options.max; + } + + if (options?.options?.directions) { + this.directions = options.options.directions; + } + + if (options.options?.preserveAspectRatio) { + this.preserveAspectRatio = options.options.preserveAspectRatio; + } + + if (options.options?.className) { + this.classNames = { + container: options.options.className.container || '', + wrapper: options.options.className.wrapper || '', + handle: options.options.className.handle || '', + resizing: options.options.className.resizing || '', + }; + } + + if (options.options?.createCustomHandle) { + this.createCustomHandle = options.options.createCustomHandle; + } + + this.wrapper = this.createWrapper(); + this.container = this.createContainer(); + + this.applyInitialSize(); + this.attachHandles(); + + this.editor.on('update', this.handleEditorUpdate.bind(this)); + } + + /** + * Returns the top-level DOM node that should be placed in the editor. + * + * This is required by the ProseMirror NodeView interface. The container + * includes the wrapper, handles, and the actual content element. + * + * @returns The container element to be inserted into the editor + */ + get dom() { + return this.container; + } + + get contentDOM(): HTMLElement | null { + return this.contentElement ?? null; + } + + private handleEditorUpdate() { + const isEditable = this.editor.isEditable; + + // Only if state actually changed + if (isEditable === this.lastEditableState) { + return; + } + + this.lastEditableState = isEditable; + + if (!isEditable) { + this.removeHandles(); + } else if (isEditable && this.handleMap.size === 0) { + this.attachHandles(); + } + } + + /** + * Called when the node's content or attributes change. + * + * Updates the internal node reference. If a custom `onUpdate` callback + * was provided, it will be called to handle additional update logic. + * + * @param node - The new/updated node + * @param decorations - Node decorations + * @param innerDecorations - Inner decorations + * @returns `false` if the node type has changed (requires full rebuild), otherwise the result of `onUpdate` or `true` + */ + update( + node: PMNode, + decorations: readonly Decoration[], + innerDecorations: DecorationSource, + ): boolean { + if (node.type !== this.node.type) { + return false; + } + + this.node = node; + + if (this.onUpdate) { + return this.onUpdate(node, decorations, innerDecorations); + } + + return true; + } + + /** + * Cleanup method called when the node view is being removed. + * + * Removes all event listeners to prevent memory leaks. This is required + * by the ProseMirror NodeView interface. If a resize is active when + * destroy is called, it will be properly cancelled. + */ + destroy() { + if (this.isResizing) { + this.container.dataset.resizeState = 'false'; + + if (this.classNames.resizing) { + this.container.classList.remove(this.classNames.resizing); + } + + document.removeEventListener('mousemove', this.handleMouseMove); + document.removeEventListener('touchmove', this.handleTouchMove); + document.removeEventListener('mouseup', this.handleMouseUp); + document.removeEventListener('touchend', this.handleMouseUp); + window.removeEventListener('blur', this.handleMouseUp); + document.removeEventListener('keydown', this.handleKeyDown); + document.removeEventListener('keyup', this.handleKeyUp); + this.isResizing = false; + this.activeHandle = null; + } + + this.editor.off('update', this.handleEditorUpdate.bind(this)); + + this.container.remove(); + } + + /** + * Creates the outer container element. + * + * The container is the top-level element returned by the NodeView and + * wraps the entire resizable node. It's set up with flexbox to handle + * alignment and includes data attributes for styling and identification. + * + * @returns The container element + */ + createContainer() { + const element = document.createElement('div'); + element.dataset.resizeContainer = ''; + element.dataset.node = this.node.type.name; + element.style.display = 'flex'; + + if (this.classNames.container) { + element.className = this.classNames.container; + } + + element.appendChild(this.wrapper); + + return element; + } + + /** + * Creates the wrapper element that contains the content and handles. + * + * The wrapper uses relative positioning so that resize handles can be + * positioned absolutely within it. This is the direct parent of the + * content element being made resizable. + * + * @returns The wrapper element + */ + createWrapper() { + const element = document.createElement('div'); + element.style.position = 'relative'; + element.style.display = 'block'; + element.dataset.resizeWrapper = ''; + + if (this.classNames.wrapper) { + element.className = this.classNames.wrapper; + } + + element.appendChild(this.element); + + return element; + } + + /** + * Creates a resize handle element for a specific direction. + * + * Each handle is absolutely positioned and includes a data attribute + * identifying its direction for styling purposes. + * + * @param direction - The resize direction for this handle + * @returns The handle element + */ + private createHandle(direction: ResizableNodeViewDirection): HTMLElement { + const handle = document.createElement('div'); + handle.dataset.resizeHandle = direction; + handle.style.position = 'absolute'; + + if (this.classNames.handle) { + handle.className = this.classNames.handle; + } + + return handle; + } + + /** + * Positions a handle element according to its direction. + * + * Corner handles (e.g., 'top-left') are positioned at the intersection + * of two edges. Edge handles (e.g., 'top') span the full width or height. + * + * @param handle - The handle element to position + * @param direction - The direction determining the position + */ + private positionHandle( + handle: HTMLElement, + direction: ResizableNodeViewDirection, + ): void { + const isTop = direction.includes('top'); + const isBottom = direction.includes('bottom'); + const isLeft = direction.includes('left'); + const isRight = direction.includes('right'); + + if (isTop) { + handle.style.top = '0'; + } + + if (isBottom) { + handle.style.bottom = '0'; + } + + if (isLeft) { + handle.style.left = '0'; + } + + if (isRight) { + handle.style.right = '0'; + } + + // Edge handles span the full width or height + if (direction === 'top' || direction === 'bottom') { + handle.style.left = '0'; + handle.style.right = '0'; + } + + if (direction === 'left' || direction === 'right') { + handle.style.top = '0'; + handle.style.bottom = '0'; + } + } + + /** + * Creates and attaches all resize handles to the wrapper. + * + * Iterates through the configured directions, creates a handle for each, + * positions it, attaches the mousedown listener, and appends it to the DOM. + */ + private attachHandles(): void { + this.directions.forEach((direction) => { + let handle: HTMLElement; + + if (this.createCustomHandle) { + handle = this.createCustomHandle(direction); + } else { + handle = this.createHandle(direction); + } + + if (!(handle instanceof HTMLElement)) { + console.warn( + `[ResizableNodeView] createCustomHandle("${direction}") did not return an HTMLElement. Falling back to default handle.`, + ); + handle = this.createHandle(direction); + } + + if (!this.createCustomHandle) { + this.positionHandle(handle, direction); + } + + handle.addEventListener('mousedown', (event) => + this.handleResizeStart(event, direction), + ); + handle.addEventListener('touchstart', (event) => + this.handleResizeStart(event as unknown as MouseEvent, direction), + ); + + this.handleMap.set(direction, handle); + + this.wrapper.appendChild(handle); + }); + } + + /** + * Removes all resize handles from the wrapper. + * + * Cleans up the handle map and removes each handle element from the DOM. + */ + private removeHandles(): void { + this.handleMap.forEach((el) => el.remove()); + this.handleMap.clear(); + } + + /** + * Applies initial sizing from node attributes to the element. + * + * If width/height attributes exist on the node, they're applied to the element. + * Otherwise, the element's natural/current dimensions are measured. The aspect + * ratio is calculated for later use in aspect-ratio-preserving resizes. + */ + private applyInitialSize(): void { + const width = this.node.attrs.width as number | undefined; + const height = this.node.attrs.height as number | undefined; + + if (width) { + this.element.style.width = `${width}px`; + this.initialWidth = width; + } else { + this.initialWidth = this.element.offsetWidth; + } + + if (height) { + this.element.style.height = `${height}px`; + this.initialHeight = height; + } else { + this.initialHeight = this.element.offsetHeight; + } + + // Calculate aspect ratio for use during resizing + if (this.initialWidth > 0 && this.initialHeight > 0) { + this.aspectRatio = this.initialWidth / this.initialHeight; + } + } + + /** + * Initiates a resize operation when a handle is clicked. + * + * Captures the starting mouse position and element dimensions, sets up + * the resize state, adds the resizing class and state attribute, and + * attaches document-level listeners for mouse movement and keyboard input. + * + * @param event - The mouse down event + * @param direction - The direction of the handle being dragged + */ + private handleResizeStart( + event: MouseEvent | TouchEvent, + direction: ResizableNodeViewDirection, + ): void { + event.preventDefault(); + event.stopPropagation(); + + // Capture initial state + this.isResizing = true; + this.activeHandle = direction; + + if (isTouchEvent(event)) { + this.startX = event.touches[0].clientX; + this.startY = event.touches[0].clientY; + } else { + this.startX = event.clientX; + this.startY = event.clientY; + } + + this.startWidth = this.element.offsetWidth; + this.startHeight = this.element.offsetHeight; + + // Recalculate aspect ratio at resize start for accuracy + if (this.startWidth > 0 && this.startHeight > 0) { + this.aspectRatio = this.startWidth / this.startHeight; + } + + const pos = this.getPos(); + if (pos !== undefined) { + // TODO: Select the node in the editor + } + + // Update UI state + this.container.dataset.resizeState = 'true'; + + if (this.classNames.resizing) { + this.container.classList.add(this.classNames.resizing); + } + + // Attach document-level listeners for resize + document.addEventListener('mousemove', this.handleMouseMove); + document.addEventListener('touchmove', this.handleTouchMove); + document.addEventListener('mouseup', this.handleMouseUp); + document.addEventListener('touchend', this.handleMouseUp); + window.addEventListener('blur', this.handleMouseUp); + document.addEventListener('keydown', this.handleKeyDown); + document.addEventListener('keyup', this.handleKeyUp); + } + + /** + * Handles mouse movement during an active resize. + * + * Calculates the delta from the starting position, computes new dimensions + * based on the active handle direction, applies constraints and aspect ratio, + * then updates the element's style and calls the onResize callback. + * + * @param event - The mouse move event + */ + private handleMouseMove = (event: MouseEvent): void => { + if (!this.isResizing || !this.activeHandle) { + return; + } + + const deltaX = event.clientX - this.startX; + const deltaY = event.clientY - this.startY; + + this.handleResize(deltaX, deltaY); + }; + + private handleTouchMove = (event: TouchEvent): void => { + if (!this.isResizing || !this.activeHandle) { + return; + } + + const touch = event.touches[0]; + if (!touch) { + return; + } + + const deltaX = touch.clientX - this.startX; + const deltaY = touch.clientY - this.startY; + + this.handleResize(deltaX, deltaY); + }; + + private handleResize(deltaX: number, deltaY: number) { + if (!this.activeHandle) { + return; + } + + const shouldPreserveAspectRatio = + this.preserveAspectRatio || this.isShiftKeyPressed; + const { width, height } = this.calculateNewDimensions( + this.activeHandle, + deltaX, + deltaY, + ); + const constrained = this.applyConstraints( + width, + height, + shouldPreserveAspectRatio, + ); + + this.element.style.width = `${constrained.width}px`; + this.element.style.height = `${constrained.height}px`; + + if (this.onResize) { + this.onResize(constrained.width, constrained.height); + } + } + + /** + * Completes the resize operation when the mouse button is released. + * + * Captures final dimensions, calls the onCommit callback to persist changes, + * removes the resizing state and class, and cleans up document-level listeners. + */ + private handleMouseUp = (): void => { + if (!this.isResizing) { + return; + } + + const finalWidth = this.element.offsetWidth; + const finalHeight = this.element.offsetHeight; + + this.onCommit(finalWidth, finalHeight); + + this.isResizing = false; + this.activeHandle = null; + + // Remove UI state + this.container.dataset.resizeState = 'false'; + + if (this.classNames.resizing) { + this.container.classList.remove(this.classNames.resizing); + } + + // Clean up document-level listeners + document.removeEventListener('mousemove', this.handleMouseMove); + document.removeEventListener('touchmove', this.handleTouchMove); + document.removeEventListener('mouseup', this.handleMouseUp); + document.removeEventListener('touchend', this.handleMouseUp); + window.removeEventListener('blur', this.handleMouseUp); + document.removeEventListener('keydown', this.handleKeyDown); + document.removeEventListener('keyup', this.handleKeyUp); + }; + + /** + * Tracks Shift key state to enable temporary aspect ratio locking. + * + * When Shift is pressed during resize, aspect ratio is preserved even if + * preserveAspectRatio is false. + * + * @param event - The keyboard event + */ + private handleKeyDown = (event: KeyboardEvent): void => { + if (event.key === 'Shift') { + this.isShiftKeyPressed = true; + } + }; + + /** + * Tracks Shift key release to disable temporary aspect ratio locking. + * + * @param event - The keyboard event + */ + private handleKeyUp = (event: KeyboardEvent): void => { + if (event.key === 'Shift') { + this.isShiftKeyPressed = false; + } + }; + + /** + * Calculates new dimensions based on mouse delta and resize direction. + * + * Takes the starting dimensions and applies the mouse movement delta + * according to the handle direction. For corner handles, both dimensions + * are affected. For edge handles, only one dimension changes. If aspect + * ratio should be preserved, delegates to applyAspectRatio. + * + * @param direction - The active resize handle direction + * @param deltaX - Horizontal mouse movement since resize start + * @param deltaY - Vertical mouse movement since resize start + * @returns The calculated width and height + */ + private calculateNewDimensions( + direction: ResizableNodeViewDirection, + deltaX: number, + deltaY: number, + ): ResizableNodeDimensions { + let newWidth = this.startWidth; + let newHeight = this.startHeight; + + const isRight = direction.includes('right'); + const isLeft = direction.includes('left'); + const isBottom = direction.includes('bottom'); + const isTop = direction.includes('top'); + + // Apply horizontal delta + if (isRight) { + newWidth = this.startWidth + deltaX; + } else if (isLeft) { + newWidth = this.startWidth - deltaX; + } + + // Apply vertical delta + if (isBottom) { + newHeight = this.startHeight + deltaY; + } else if (isTop) { + newHeight = this.startHeight - deltaY; + } + + // For pure horizontal/vertical handles, only one dimension changes + if (direction === 'right' || direction === 'left') { + newWidth = this.startWidth + (isRight ? deltaX : -deltaX); + } + + if (direction === 'top' || direction === 'bottom') { + newHeight = this.startHeight + (isBottom ? deltaY : -deltaY); + } + + const shouldPreserveAspectRatio = + this.preserveAspectRatio || this.isShiftKeyPressed; + + if (shouldPreserveAspectRatio) { + return this.applyAspectRatio(newWidth, newHeight, direction); + } + + return { width: newWidth, height: newHeight }; + } + + /** + * Applies min/max constraints to dimensions. + * + * When aspect ratio is NOT preserved, constraints are applied independently + * to width and height. When aspect ratio IS preserved, constraints are + * applied while maintaining the aspect ratio—if one dimension hits a limit, + * the other is recalculated proportionally. + * + * This ensures that aspect ratio is never broken when constrained. + * + * @param width - The unconstrained width + * @param height - The unconstrained height + * @param preserveAspectRatio - Whether to maintain aspect ratio while constraining + * @returns The constrained dimensions + */ + private applyConstraints( + width: number, + height: number, + preserveAspectRatio: boolean, + ): ResizableNodeDimensions { + if (!preserveAspectRatio) { + // Independent constraints for each dimension + let constrainedWidth = Math.max(this.minSize.width, width); + let constrainedHeight = Math.max(this.minSize.height, height); + + if (this.maxSize?.width) { + constrainedWidth = Math.min(this.maxSize.width, constrainedWidth); + } + + if (this.maxSize?.height) { + constrainedHeight = Math.min(this.maxSize.height, constrainedHeight); + } + + return { width: constrainedWidth, height: constrainedHeight }; + } + + // Aspect-ratio-aware constraints: adjust both dimensions proportionally + let constrainedWidth = width; + let constrainedHeight = height; + + // Check minimum constraints + if (constrainedWidth < this.minSize.width) { + constrainedWidth = this.minSize.width; + constrainedHeight = constrainedWidth / this.aspectRatio; + } + + if (constrainedHeight < this.minSize.height) { + constrainedHeight = this.minSize.height; + constrainedWidth = constrainedHeight * this.aspectRatio; + } + + // Check maximum constraints + if (this.maxSize?.width && constrainedWidth > this.maxSize.width) { + constrainedWidth = this.maxSize.width; + constrainedHeight = constrainedWidth / this.aspectRatio; + } + + if (this.maxSize?.height && constrainedHeight > this.maxSize.height) { + constrainedHeight = this.maxSize.height; + constrainedWidth = constrainedHeight * this.aspectRatio; + } + + return { width: constrainedWidth, height: constrainedHeight }; + } + + /** + * Adjusts dimensions to maintain the original aspect ratio. + * + * For horizontal handles (left/right), uses width as the primary dimension + * and calculates height from it. For vertical handles (top/bottom), uses + * height as primary and calculates width. For corner handles, uses width + * as the primary dimension. + * + * @param width - The new width + * @param height - The new height + * @param direction - The active resize direction + * @returns Dimensions adjusted to preserve aspect ratio + */ + private applyAspectRatio( + width: number, + height: number, + direction: ResizableNodeViewDirection, + ): ResizableNodeDimensions { + const isHorizontal = direction === 'left' || direction === 'right'; + const isVertical = direction === 'top' || direction === 'bottom'; + + if (isHorizontal) { + // For horizontal resize, width is primary + return { + width, + height: width / this.aspectRatio, + }; + } + + if (isVertical) { + // For vertical resize, height is primary + return { + width: height * this.aspectRatio, + height, + }; + } + + // For corner resize, width is primary + return { + width, + height: width / this.aspectRatio, + }; + } +} + +/** + * Alias for ResizableNodeView to maintain consistent naming. + * @deprecated Use ResizableNodeView instead - will be removed in future versions. + */ +export const ResizableNodeview = ResizableNodeView; diff --git a/packages/editor-ext/src/lib/video/video.ts b/packages/editor-ext/src/lib/video/video.ts index 75bc3910..9102324d 100644 --- a/packages/editor-ext/src/lib/video/video.ts +++ b/packages/editor-ext/src/lib/video/video.ts @@ -1,7 +1,8 @@ import { ReactNodeViewRenderer } from "@tiptap/react"; -import { Range, Node, mergeAttributes, ResizableNodeView } from "@tiptap/core"; +import { Range, Node, mergeAttributes } from "@tiptap/core"; +import { ResizableNodeView } from "../resizable-nodeview"; +import type { ResizableNodeViewDirection } from "../resizable-nodeview"; import { normalizeFileUrl } from "../media-utils"; -import type { ResizableNodeViewDirection } from "@tiptap/core"; export type VideoResizeOptions = { enabled: boolean; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22d805cd..55165ca3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,9 +34,6 @@ overrides: yaml@>=2.0.0 <2.8.3: 2.8.3 patchedDependencies: - '@tiptap/core': - hash: efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00 - path: patches/@tiptap__core.patch react-arborist@3.4.0: hash: 419b3b02e24afe928cc006a006f6e906666aff19aa6fd7daaa788ccc2202678a path: patches/react-arborist@3.4.0.patch @@ -65,7 +62,7 @@ importers: version: 3.4.4(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30) '@hocuspocus/transformer': specifier: 3.4.4 - version: 3.4.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(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.30))(yjs@13.6.30))(yjs@13.6.30) + version: 3.4.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(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.30))(yjs@13.6.30))(yjs@13.6.30) '@joplin/turndown': specifier: ^4.0.82 version: 4.0.82 @@ -77,85 +74,85 @@ importers: version: 3.0.0 '@tiptap/core': specifier: 3.20.4 - version: 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + version: 3.20.4(@tiptap/pm@3.20.4) '@tiptap/extension-code-block': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) '@tiptap/extension-collaboration': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30) '@tiptap/extension-collaboration-caret': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)) '@tiptap/extension-color': specifier: 3.20.4 - version: 3.20.4(@tiptap/extension-text-style@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))) + version: 3.20.4(@tiptap/extension-text-style@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))) '@tiptap/extension-document': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) '@tiptap/extension-heading': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) '@tiptap/extension-highlight': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) '@tiptap/extension-history': specifier: 3.20.4 - version: 3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)) + version: 3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)) '@tiptap/extension-image': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) '@tiptap/extension-link': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) '@tiptap/extension-list': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) '@tiptap/extension-placeholder': specifier: 3.20.4 - version: 3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)) + version: 3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)) '@tiptap/extension-subscript': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) '@tiptap/extension-superscript': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) '@tiptap/extension-table': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) '@tiptap/extension-text': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) '@tiptap/extension-text-align': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) '@tiptap/extension-text-style': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) '@tiptap/extension-typography': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) '@tiptap/extension-unique-id': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) '@tiptap/extension-youtube': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) '@tiptap/html': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(happy-dom@20.8.4) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(happy-dom@20.8.4) '@tiptap/pm': specifier: 3.20.4 version: 3.20.4 '@tiptap/react': specifier: 3.20.4 - version: 3.20.4(@floating-ui/dom@1.7.3)(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 3.20.4(@floating-ui/dom@1.7.3)(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@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: 3.20.4 version: 3.20.4 '@tiptap/suggestion': specifier: 3.20.4 - version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) '@tiptap/y-tiptap': specifier: 3.0.2 version: 3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30) @@ -12707,9 +12704,9 @@ snapshots: - bufferutil - utf-8-validate - '@hocuspocus/transformer@3.4.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(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.30))(yjs@13.6.30))(yjs@13.6.30)': + '@hocuspocus/transformer@3.4.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(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.30))(yjs@13.6.30))(yjs@13.6.30)': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) '@tiptap/pm': 3.20.4 '@tiptap/starter-kit': 3.20.4 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.30))(yjs@13.6.30) @@ -15281,191 +15278,191 @@ snapshots: '@tanstack/query-core': 5.90.17 react: 18.3.1 - '@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)': + '@tiptap/core@3.20.4(@tiptap/pm@3.20.4)': dependencies: '@tiptap/pm': 3.20.4 - '@tiptap/extension-blockquote@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))': + '@tiptap/extension-blockquote@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) - '@tiptap/extension-bold@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))': + '@tiptap/extension-bold@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) - '@tiptap/extension-bubble-menu@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': + '@tiptap/extension-bubble-menu@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': dependencies: '@floating-ui/dom': 1.7.4 - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) '@tiptap/pm': 3.20.4 optional: true - '@tiptap/extension-bullet-list@3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))': + '@tiptap/extension-bullet-list@3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + '@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) - '@tiptap/extension-code-block@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': + '@tiptap/extension-code-block@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) '@tiptap/pm': 3.20.4 - '@tiptap/extension-code@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))': + '@tiptap/extension-code@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) - '@tiptap/extension-collaboration-caret@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))': + '@tiptap/extension-collaboration-caret@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) '@tiptap/pm': 3.20.4 '@tiptap/y-tiptap': 3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30) - '@tiptap/extension-collaboration@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30)': + '@tiptap/extension-collaboration@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30)': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) '@tiptap/pm': 3.20.4 '@tiptap/y-tiptap': 3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30) yjs: 13.6.30 - '@tiptap/extension-color@3.20.4(@tiptap/extension-text-style@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)))': + '@tiptap/extension-color@3.20.4(@tiptap/extension-text-style@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)))': dependencies: - '@tiptap/extension-text-style': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) + '@tiptap/extension-text-style': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) - '@tiptap/extension-document@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))': + '@tiptap/extension-document@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) - '@tiptap/extension-dropcursor@3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))': + '@tiptap/extension-dropcursor@3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + '@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) - '@tiptap/extension-floating-menu@3.20.4(@floating-ui/dom@1.7.3)(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': + '@tiptap/extension-floating-menu@3.20.4(@floating-ui/dom@1.7.3)(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': dependencies: '@floating-ui/dom': 1.7.3 - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) '@tiptap/pm': 3.20.4 optional: true - '@tiptap/extension-gapcursor@3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))': + '@tiptap/extension-gapcursor@3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + '@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) - '@tiptap/extension-hard-break@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))': + '@tiptap/extension-hard-break@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) - '@tiptap/extension-heading@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))': + '@tiptap/extension-heading@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) - '@tiptap/extension-highlight@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))': + '@tiptap/extension-highlight@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) - '@tiptap/extension-history@3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))': + '@tiptap/extension-history@3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + '@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) - '@tiptap/extension-horizontal-rule@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': + '@tiptap/extension-horizontal-rule@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) '@tiptap/pm': 3.20.4 - '@tiptap/extension-image@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))': + '@tiptap/extension-image@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) - '@tiptap/extension-italic@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))': + '@tiptap/extension-italic@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) - '@tiptap/extension-link@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': + '@tiptap/extension-link@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) '@tiptap/pm': 3.20.4 linkifyjs: 4.3.2 - '@tiptap/extension-list-item@3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))': + '@tiptap/extension-list-item@3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + '@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) - '@tiptap/extension-list-keymap@3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))': + '@tiptap/extension-list-keymap@3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + '@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) - '@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': + '@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) '@tiptap/pm': 3.20.4 - '@tiptap/extension-ordered-list@3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))': + '@tiptap/extension-ordered-list@3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + '@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) - '@tiptap/extension-paragraph@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))': + '@tiptap/extension-paragraph@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) - '@tiptap/extension-placeholder@3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))': + '@tiptap/extension-placeholder@3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + '@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) - '@tiptap/extension-strike@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))': + '@tiptap/extension-strike@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) - '@tiptap/extension-subscript@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': + '@tiptap/extension-subscript@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) '@tiptap/pm': 3.20.4 - '@tiptap/extension-superscript@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': + '@tiptap/extension-superscript@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) '@tiptap/pm': 3.20.4 - '@tiptap/extension-table@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': + '@tiptap/extension-table@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) '@tiptap/pm': 3.20.4 - '@tiptap/extension-text-align@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))': + '@tiptap/extension-text-align@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) - '@tiptap/extension-text-style@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))': + '@tiptap/extension-text-style@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) - '@tiptap/extension-text@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))': + '@tiptap/extension-text@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) - '@tiptap/extension-typography@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))': + '@tiptap/extension-typography@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) - '@tiptap/extension-underline@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))': + '@tiptap/extension-underline@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) - '@tiptap/extension-unique-id@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': + '@tiptap/extension-unique-id@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) '@tiptap/pm': 3.20.4 uuid: 10.0.0 - '@tiptap/extension-youtube@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))': + '@tiptap/extension-youtube@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) - '@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': + '@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) '@tiptap/pm': 3.20.4 - '@tiptap/html@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(happy-dom@20.8.4)': + '@tiptap/html@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(happy-dom@20.8.4)': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) '@tiptap/pm': 3.20.4 happy-dom: 20.8.4 @@ -15490,9 +15487,9 @@ snapshots: prosemirror-transform: 1.10.4 prosemirror-view: 1.40.0 - '@tiptap/react@3.20.4(@floating-ui/dom@1.7.3)(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@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/react@3.20.4(@floating-ui/dom@1.7.3)(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@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': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) '@tiptap/pm': 3.20.4 '@types/react': 18.3.12 '@types/react-dom': 18.3.1 @@ -15502,41 +15499,41 @@ snapshots: react-dom: 18.3.1(react@18.3.1) use-sync-external-store: 1.6.0(react@18.3.1) optionalDependencies: - '@tiptap/extension-bubble-menu': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) - '@tiptap/extension-floating-menu': 3.20.4(@floating-ui/dom@1.7.3)(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + '@tiptap/extension-bubble-menu': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + '@tiptap/extension-floating-menu': 3.20.4(@floating-ui/dom@1.7.3)(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) transitivePeerDependencies: - '@floating-ui/dom' '@tiptap/starter-kit@3.20.4': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) - '@tiptap/extension-blockquote': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) - '@tiptap/extension-bold': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) - '@tiptap/extension-bullet-list': 3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)) - '@tiptap/extension-code': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) - '@tiptap/extension-code-block': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) - '@tiptap/extension-document': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) - '@tiptap/extension-dropcursor': 3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)) - '@tiptap/extension-gapcursor': 3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)) - '@tiptap/extension-hard-break': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) - '@tiptap/extension-heading': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) - '@tiptap/extension-horizontal-rule': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) - '@tiptap/extension-italic': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) - '@tiptap/extension-link': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) - '@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) - '@tiptap/extension-list-item': 3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)) - '@tiptap/extension-list-keymap': 3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)) - '@tiptap/extension-ordered-list': 3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)) - '@tiptap/extension-paragraph': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) - '@tiptap/extension-strike': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) - '@tiptap/extension-text': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) - '@tiptap/extension-underline': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)) - '@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) + '@tiptap/extension-blockquote': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) + '@tiptap/extension-bold': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) + '@tiptap/extension-bullet-list': 3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)) + '@tiptap/extension-code': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) + '@tiptap/extension-code-block': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + '@tiptap/extension-document': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) + '@tiptap/extension-dropcursor': 3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)) + '@tiptap/extension-gapcursor': 3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)) + '@tiptap/extension-hard-break': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) + '@tiptap/extension-heading': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) + '@tiptap/extension-horizontal-rule': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + '@tiptap/extension-italic': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) + '@tiptap/extension-link': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + '@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) + '@tiptap/extension-list-item': 3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)) + '@tiptap/extension-list-keymap': 3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)) + '@tiptap/extension-ordered-list': 3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)) + '@tiptap/extension-paragraph': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) + '@tiptap/extension-strike': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) + '@tiptap/extension-text': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) + '@tiptap/extension-underline': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)) + '@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4) '@tiptap/pm': 3.20.4 - '@tiptap/suggestion@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': + '@tiptap/suggestion@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)': dependencies: - '@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4) + '@tiptap/core': 3.20.4(@tiptap/pm@3.20.4) '@tiptap/pm': 3.20.4 '@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)':