From 7536a7886ab74fe23841a86bb7f95c9fcb314a85 Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Fri, 3 Jan 2025 20:56:22 +0530 Subject: [PATCH] chore: remove testing code --- .../src/core/extensions/drop-cursor-huh.ts | 364 ------------ .../drop-cursor-somewhat-working.ts | 325 ---------- .../src/core/extensions/drop-cursor-weird.ts | 374 ------------ .../core/extensions/drop-cursor-working.ts | 359 ----------- .../core/extensions/drop-cursor.optimized.ts | 557 ------------------ .../editor/src/core/extensions/drop-cursor.ts | 18 - 6 files changed, 1997 deletions(-) delete mode 100644 packages/editor/src/core/extensions/drop-cursor-huh.ts delete mode 100644 packages/editor/src/core/extensions/drop-cursor-somewhat-working.ts delete mode 100644 packages/editor/src/core/extensions/drop-cursor-weird.ts delete mode 100644 packages/editor/src/core/extensions/drop-cursor-working.ts delete mode 100644 packages/editor/src/core/extensions/drop-cursor.optimized.ts diff --git a/packages/editor/src/core/extensions/drop-cursor-huh.ts b/packages/editor/src/core/extensions/drop-cursor-huh.ts deleted file mode 100644 index 0b76ea6b1a7..00000000000 --- a/packages/editor/src/core/extensions/drop-cursor-huh.ts +++ /dev/null @@ -1,364 +0,0 @@ -import { Plugin, EditorState, PluginKey } from "@tiptap/pm/state"; -import { EditorView } from "@tiptap/pm/view"; -import { dropPoint } from "@tiptap/pm/transform"; -import { Editor, Extension } from "@tiptap/core"; -import { NodeType, Node as ProseMirrorNode, ResolvedPos } from "@tiptap/pm/model"; - -interface DropCursorOptions { - /// The color of the cursor. Defaults to `black`. Use `false` to apply no color and rely only on class. - color?: string | false; - - /// The precise width of the cursor in pixels. Defaults to 1. - width?: number; - - /// A CSS class name to add to the cursor element. - class?: string; -} - -/// Create a plugin that, when added to a ProseMirror instance, -/// causes a decoration to show up at the drop position when something -/// is dragged over the editor. -/// -/// Nodes may add a `disableDropCursor` property to their spec to -/// control the showing of a drop cursor inside them. This may be a -/// boolean or a function, which will be called with a view and a -/// position, and should return a boolean. -export function dropCursor(options: DropCursorOptions = {}, tiptapEditorOptions: any): Plugin { - const pluginKey = new PluginKey("dropCursor"); - return new Plugin({ - key: pluginKey, - state: { - init() { - return { dropPosByDropCursorPos: null }; - }, - apply(tr, state) { - // Get the new state from meta - const meta = tr.getMeta(pluginKey); - if (meta) { - return { dropPosByDropCursorPos: meta.dropPosByDropCursorPos }; - } - return state; - }, - }, - view(editorView) { - return new DropCursorView(editorView, options, tiptapEditorOptions.editor, pluginKey); - }, - props: { - handleDrop(view, event, slice, moved) { - const coordinates = { left: event.clientX, top: event.clientY }; - const pos = view.posAtCoords(coordinates); - - if (!pos) return false; - - const $pos = view.state.doc.resolve(pos.pos); - - // const { isBetweenNodesOfType: isBetweenLists, position } = isBetweenNodesOfType($pos, "list"); - - // if (isBetweenLists && position !== null) { - const state = pluginKey.getState(view.state); - let dropPosByDropCursorPos = state?.dropPosByDropCursorPos; - - if (dropPosByDropCursorPos != null) { - const tr = view.state.tr; - - if (moved) { - // Get the size of content to be deleted - const selection = tr.selection; - const deleteSize = selection.to - selection.from; - - // Adjust drop position if it's after the deletion point - if (dropPosByDropCursorPos > selection.from) { - dropPosByDropCursorPos -= deleteSize; - } - - tr.deleteSelection(); - } - - tr.insert(dropPosByDropCursorPos, slice.content); - view.dispatch(tr); - return true; - } - // } - return false; - }, - }, - }); -} - -// Add disableDropCursor to NodeSpec -declare module "prosemirror-model" { - interface NodeSpec { - disableDropCursor?: - | boolean - | ((view: EditorView, pos: { pos: number; inside: number }, event: DragEvent) => boolean); - } -} - -class DropCursorView { - private width: number; - private color: string | undefined; - private class: string | undefined; - private cursorPos: number | null = null; - private element: HTMLElement | null = null; - private timeout: ReturnType | null = null; - private handlers: { name: string; handler: (event: Event) => void }[]; - private editor: Editor; - - constructor( - private readonly editorView: EditorView, - options: DropCursorOptions, - editor: Editor, - private readonly pluginKey: PluginKey - ) { - this.width = options.width ?? 1; - this.color = options.color === false ? undefined : options.color || "red"; - this.class = options.class; - this.editor = editor; - - this.handlers = ["dragover", "dragend", "drop", "dragleave"].map((name) => { - const handler = (e: Event) => { - (this as any)[name](e); - }; - editorView.dom.addEventListener(name, handler); - return { name, handler }; - }); - } - - destroy() { - this.handlers.forEach(({ name, handler }) => this.editorView.dom.removeEventListener(name, handler)); - } - - isBetweenNodesOfType($pos: ResolvedPos, nodeTypeName: string) { - const { doc } = $pos; - const nodeType = doc.type.schema.nodes[nodeTypeName]; - - function isNodeType(node: ProseMirrorNode | null, type: NodeType) { - return node && node.type === type; - } - - let finalPos: number | null = null; - let isBetweenNodesOfType = false; - const listDomNode = this.editorView.nodeDOM($pos.pos); - if (listDomNode) { - const listElement = (listDomNode as HTMLElement)?.closest(".prosemirror-flat-list"); - if (listElement) { - isBetweenNodesOfType = true; - } - - // __AUTO_GENERATED_PRINT_VAR_START__ - console.log("DropCursorView#isBetweenNodesOfType#if listElement: s", listElement); // __AUTO_GENERATED_PRINT_VAR_END__ - finalPos = this.editorView.posAtDOM(listElement, 0); - if (listElement.nextElementSibling === null) { - // const - // if (listElement) { - // const nextListElement = listElement.nextElementSibling; - // // __AUTO_GENERATED_PRINT_VAR_START__ - // console.log("DropCursorView#isBetweenNodesOfType#if#if nextListElement: s", nextListElement); // __AUTO_GENERATED_PRINT_VAR_END__ - // } - // __AUTO_GENERATED_PRINT_VAR_START__ - console.log("DropCursorView#isBetweenNodesOfType#if finalPos: %s", finalPos); // __AUTO_GENERATED_PRINT_VAR_END__ - } - // let isBetweenNodesOfType = false; - // let positionToShowAndDrop: number | null = null; - // - // const nodeBefore = $pos.nodeBefore; - // const nodeAfter = $pos.nodeAfter; - // const nodeBeforeIsType = isNodeType(nodeBefore, nodeType); - // const nodeAfterIsType = isNodeType(nodeAfter, nodeType); - - // if (nodeBeforeIsType || nodeAfterIsType) { - // isBetweenNodesOfType = true; - // positionToShowAndDrop = $pos.pos; - // } else { - // const nextListPos = findNextNodeOfType($pos, nodeType); - // if (nextListPos != null) { - // isBetweenNodesOfType = true; - // positionToShowAndDrop = nextListPos; - // } - // } - - // const node = this.editorView.nodeDOM(positionToShowAndDrop); - // const listElement = (node as HTMLElement)?.closest(".prosemirror-flat-list"); - // const finalPos = this.editorView.posAtDOM(listElement, 0); - return { - isBetweenNodesOfType, - position: finalPos - 1, - }; - } - - update(editorView: EditorView, prevState: EditorState) { - if (this.cursorPos != null && prevState.doc != editorView.state.doc) { - if (this.cursorPos > editorView.state.doc.content.size) this.setCursor(null); - else this.updateOverlay(); - } - } - - setCursor(pos: number | null) { - if (pos == this.cursorPos) return; - this.cursorPos = pos; - if (pos == null) { - this.element!.parentNode!.removeChild(this.element!); - this.element = null; - } else { - this.updateOverlay(); - } - } - - updateOverlay() { - const $pos = this.editorView.state.doc.resolve(this.cursorPos!); - const isBlock = !$pos.parent.inlineContent; - const isSpecialCase = isNodeAtDepthAndItsParentIsParagraphWhoseParentIsList($pos); - let rect: Partial; - const editorDOM = this.editorView.dom; - const editorRect = editorDOM.getBoundingClientRect(); - const scaleX = editorRect.width / editorDOM.offsetWidth; - const scaleY = editorRect.height / editorDOM.offsetHeight; - - if (isBlock) { - const before = $pos.nodeBefore; - const after = $pos.nodeAfter; - if (before || after) { - const node = this.editorView.nodeDOM(this.cursorPos! - (before ? before.nodeSize : 0)); - if (node) { - const nodeRect = (node as HTMLElement).getBoundingClientRect(); - let top = before ? nodeRect.bottom : nodeRect.top; - if (before && after) { - top = (top + (this.editorView.nodeDOM(this.cursorPos!) as HTMLElement).getBoundingClientRect().top) / 2; - } - const halfWidth = (this.width / 2) * scaleY; - rect = { left: nodeRect.left, right: nodeRect.right, top: top - halfWidth, bottom: top + halfWidth }; - } - } - } - if (!rect) { - const coords = this.editorView.coordsAtPos(this.cursorPos!); - const halfWidth = (this.width / 2) * scaleX; - rect = { left: coords.left - halfWidth, right: coords.left + halfWidth, top: coords.top, bottom: coords.bottom }; - } - - const parent = this.editorView.dom.offsetParent as HTMLElement; - if (!this.element) { - this.element = parent.appendChild(document.createElement("div")); - if (this.class) this.element.className = this.class; - this.element.style.cssText = "position: absolute; z-index: 50; pointer-events: none;"; - if (this.color) { - this.element.style.backgroundColor = this.color; - } - } - this.element.classList.toggle("prosemirror-dropcursor-block", isBlock); - this.element.classList.toggle("prosemirror-dropcursor-inline", !isBlock); - - let parentLeft: number, parentTop: number; - if (!parent || (parent == document.body && getComputedStyle(parent).position == "static")) { - parentLeft = -window.scrollX; - parentTop = -window.scrollY; - } else { - const rect = parent.getBoundingClientRect(); - const parentScaleX = rect.width / parent.offsetWidth, - parentScaleY = rect.height / parent.offsetHeight; - parentLeft = rect.left - parent.scrollLeft * parentScaleX; - parentTop = rect.top - parent.scrollTop * parentScaleY; - } - this.element.style.left = (rect.left - parentLeft) / scaleX + "px"; - this.element.style.top = (rect.top - parentTop) / scaleY + "px"; - this.element.style.width = (rect.right - rect.left) / scaleX + "px"; - this.element.style.height = (rect.bottom - rect.top) / scaleY + "px"; - } - - scheduleRemoval(timeout: number) { - if (this.timeout) clearTimeout(this.timeout); - this.timeout = setTimeout(() => this.setCursor(null), timeout); - } - - dragover(event: DragEvent) { - if (!this.editorView.editable) return; - const pos = this.editorView.posAtCoords({ left: event.clientX, top: event.clientY }); - - if (pos) { - const $pos = this.editorView.state.doc.resolve(pos.pos); - - const node = pos.inside >= 0 && this.editorView.state.doc.nodeAt(pos.inside); - const disableDropCursor = node && node.type.spec.disableDropCursor; - const disabled = - typeof disableDropCursor == "function" ? disableDropCursor(this.editorView, pos, event) : disableDropCursor; - - if (!disabled) { - const { isBetweenNodesOfType: isBetweenNodesOfTypeLists, position } = this.isBetweenNodesOfType($pos, "list"); - - if (isBetweenNodesOfTypeLists && position !== undefined) { - this.dropPosByDropCursorPos = position; - this.setCursor(position); - return; - } - - let target = pos.pos; - if (this.editorView.dragging && this.editorView.dragging.slice) { - const point = dropPoint(this.editorView.state.doc, target, this.editorView.dragging.slice); - if (point != null) target = point; - } - this.setCursor(target); - this.scheduleRemoval(5000); - } - } - } - - dragend() { - this.scheduleRemoval(20); - } - - drop() { - this.scheduleRemoval(20); - } - - dragleave(event: DragEvent) { - const relatedTarget = event.relatedTarget as Node | null; - if (relatedTarget && !this.editorView.dom.contains(relatedTarget)) { - this.setCursor(null); - } - } - - set dropPosByDropCursorPos(pos: number | null) { - const tr = this.editorView.state.tr; - tr.setMeta(this.pluginKey, { dropPosByDropCursorPos: pos }); - this.editorView.dispatch(tr); - } - - get dropPosByDropCursorPos(): number | null { - return this.pluginKey.getState(this.editorView.state)?.dropPosByDropCursorPos; - } -} - -export const DropCursorExtension = Extension.create({ - name: "dropCursor", - addProseMirrorPlugins(this) { - return [ - dropCursor( - { - width: 2, - class: "transition-all duration-200 ease-[cubic-bezier(0.165, 0.84, 0.44, 1)] text-custom-text-300", - }, - this - ), - ]; - }, -}); - -function findNextNodeOfType($pos: ResolvedPos, nodeType: NodeType): number | null { - for (let i = $pos.pos; i < $pos.doc.content.size; i++) { - const node = $pos.doc.nodeAt(i); - if (node && node.type === nodeType) { - return i; - } - } - return null; -} - -function isNodeAtDepthAndItsParentIsParagraphWhoseParentIsList($pos: ResolvedPos): boolean { - const depth = $pos.depth; - if (depth >= 0) { - const parent = $pos.node(depth); - const grandParent = $pos.node(depth - 1); - return parent.type.name === "paragraph" && grandParent.type.name === "list"; - } - return false; -} diff --git a/packages/editor/src/core/extensions/drop-cursor-somewhat-working.ts b/packages/editor/src/core/extensions/drop-cursor-somewhat-working.ts deleted file mode 100644 index ccfb3ff64a6..00000000000 --- a/packages/editor/src/core/extensions/drop-cursor-somewhat-working.ts +++ /dev/null @@ -1,325 +0,0 @@ -import { Plugin, EditorState, NodeSelection } from "@tiptap/pm/state"; -import { EditorView } from "@tiptap/pm/view"; -import { dropPoint } from "@tiptap/pm/transform"; -import { Editor, Extension } from "@tiptap/core"; -import { Node as ProseMirrorNode, ResolvedPos } from "@tiptap/pm/model"; - -interface DropCursorOptions { - /// The color of the cursor. Defaults to `black`. Use `false` to apply no color and rely only on class. - color?: string | false; - - /// The precise width of the cursor in pixels. Defaults to 1. - width?: number; - - /// A CSS class name to add to the cursor element. - class?: string; -} - -/// Create a plugin that, when added to a ProseMirror instance, -/// causes a decoration to show up at the drop position when something -/// is dragged over the editor. -/// -/// Nodes may add a `disableDropCursor` property to their spec to -/// control the showing of a drop cursor inside them. This may be a -/// boolean or a function, which will be called with a view and a -/// position, and should return a boolean. -export function dropCursor( - options: DropCursorOptions = {}, - tiptapEditorOptions: { - name: string; - options: any; - storage: any; - editor: Editor; - parent: () => Plugin[]; - } -): Plugin { - return new Plugin({ - view(editorView) { - return new DropCursorView(editorView, options, tiptapEditorOptions.editor); - }, - props: { - handleDrop(view, event) { - const pos = view.posAtCoords({ left: event.clientX, top: event.clientY }); - if (pos) { - const $pos = view.state.doc.resolve(pos.pos); - // Only prevent default if we're between lists - if (isBetweenNodesOfType($pos, "list").isBetweenNodesOfType) { - return true; - } - } - return false; // Let other drop handlers work normally - }, - }, - }); -} - -function isBetweenNodesOfType( - $pos: ResolvedPos, - nodeType: string -): { isBetweenNodesOfType: boolean; isDirectlyBetweenLists: boolean; position?: number } { - // Check direct siblings first - if ($pos.nodeBefore?.type.name === nodeType && $pos.nodeAfter?.type.name === nodeType) { - return { isBetweenNodesOfType: true, isDirectlyBetweenLists: true, position: $pos.pos }; - } - - // Helper function to get all parent types up to root and their positions - const getParentTypes = ( - node: ProseMirrorNode | null, - $pos: ResolvedPos - ): { types: Set; position?: number } => { - const types = new Set(); - if (!node) return { types }; - - types.add(node.type.name); - if (node.type.name === nodeType) { - return { types, position: $pos.pos }; - } - - // Traverse up through all depths - for (let depth = $pos.depth; depth > 0; depth--) { - const parent = $pos.node(depth); - types.add(parent.type.name); - if (parent.type.name === nodeType) { - return { types, position: $pos.before(depth) }; - } - } - return { types }; - }; - - // Get parent types and positions for both before and after nodes - const before = getParentTypes($pos.nodeBefore, $pos); - const after = getParentTypes($pos.nodeAfter, $pos); - - console.log("before", before.position); - console.log("after", after.position); - // Check if both branches contain the nodeType and return the relevant position - if (before.types.has(nodeType) && before.position !== undefined) { - return { isBetweenNodesOfType: true, isDirectlyBetweenLists: false, position: before.position }; - } - if (after.types.has(nodeType) && after.position !== undefined) { - return { isBetweenNodesOfType: true, isDirectlyBetweenLists: false, position: after.position }; - } - - return { isBetweenNodesOfType: false, isDirectlyBetweenLists: false }; -} - -// Add disableDropCursor to NodeSpec -declare module "prosemirror-model" { - interface NodeSpec { - disableDropCursor?: - | boolean - | ((view: EditorView, pos: { pos: number; inside: number }, event: DragEvent) => boolean); - } -} - -class DropCursorView { - private width: number; - private color: string | undefined; - private class: string | undefined; - private cursorPos: number | null = null; - private element: HTMLElement | null = null; - private timeout: ReturnType | null = null; - private handlers: { name: string; handler: (event: Event) => void }[]; - private editor: Editor; - private lastValidPosition: number | null = null; - - constructor( - private readonly editorView: EditorView, - options: DropCursorOptions, - editor: Editor - ) { - this.width = options.width ?? 1; - this.color = options.color === false ? undefined : options.color || "red"; - this.class = options.class; - this.editor = editor; - - this.handlers = ["dragover", "dragend", "drop", "dragleave"].map((name) => { - const handler = (e: Event) => { - (this as any)[name](e); - }; - editorView.dom.addEventListener(name, handler); - return { name, handler }; - }); - } - - destroy() { - this.handlers.forEach(({ name, handler }) => this.editorView.dom.removeEventListener(name, handler)); - } - - update(editorView: EditorView, prevState: EditorState) { - if (this.cursorPos != null && prevState.doc != editorView.state.doc) { - if (this.cursorPos > editorView.state.doc.content.size) this.setCursor(null); - else this.updateOverlay(); - } - } - - setCursor(pos: number | null) { - if (pos == this.cursorPos) return; - this.cursorPos = pos; - if (pos == null) { - this.element!.parentNode!.removeChild(this.element!); - this.element = null; - } else { - this.updateOverlay(); - } - } - - updateOverlay() { - const $pos = this.editorView.state.doc.resolve(this.cursorPos!); - const isBlock = !$pos.parent.inlineContent; - let rect: Partial; - const editorDOM = this.editorView.dom; - const editorRect = editorDOM.getBoundingClientRect(); - const scaleX = editorRect.width / editorDOM.offsetWidth; - const scaleY = editorRect.height / editorDOM.offsetHeight; - - if (isBlock) { - const before = $pos.nodeBefore; - const after = $pos.nodeAfter; - if (before || after) { - const node = this.editorView.nodeDOM(this.cursorPos! - (before ? before.nodeSize : 0)); - if (node) { - const nodeRect = (node as HTMLElement).getBoundingClientRect(); - let top = before ? nodeRect.bottom : nodeRect.top; - if (before && after) { - top = (top + (this.editorView.nodeDOM(this.cursorPos!) as HTMLElement).getBoundingClientRect().top) / 2; - } - const halfWidth = (this.width / 2) * scaleY; - rect = { left: nodeRect.left, right: nodeRect.right, top: top - halfWidth, bottom: top + halfWidth }; - } - } - } - if (!rect) { - const coords = this.editorView.coordsAtPos(this.cursorPos!); - const halfWidth = (this.width / 2) * scaleX; - rect = { left: coords.left - halfWidth, right: coords.left + halfWidth, top: coords.top, bottom: coords.bottom }; - } - - const parent = this.editorView.dom.offsetParent as HTMLElement; - if (!this.element) { - this.element = parent.appendChild(document.createElement("div")); - if (this.class) this.element.className = this.class; - this.element.style.cssText = "position: absolute; z-index: 50; pointer-events: none;"; - if (this.color) { - this.element.style.backgroundColor = this.color; - } - } - this.element.classList.toggle("prosemirror-dropcursor-block", isBlock); - this.element.classList.toggle("prosemirror-dropcursor-inline", !isBlock); - let parentLeft: number, parentTop: number; - if (!parent || (parent == document.body && getComputedStyle(parent).position == "static")) { - parentLeft = -window.scrollX; - parentTop = -window.scrollY; - } else { - const rect = parent.getBoundingClientRect(); - const parentScaleX = rect.width / parent.offsetWidth, - parentScaleY = rect.height / parent.offsetHeight; - parentLeft = rect.left - parent.scrollLeft * parentScaleX; - parentTop = rect.top - parent.scrollTop * parentScaleY; - } - this.element.style.left = (rect.left - parentLeft) / scaleX + "px"; - this.element.style.top = (rect.top - parentTop) / scaleY + "px"; - this.element.style.width = (rect.right - rect.left) / scaleX + "px"; - this.element.style.height = (rect.bottom - rect.top) / scaleY + "px"; - } - - scheduleRemoval(timeout: number) { - if (this.timeout) clearTimeout(this.timeout); - this.timeout = setTimeout(() => this.setCursor(null), timeout); - } - - dragover(event: DragEvent) { - if (!this.editorView.editable) return; - const pos = this.editorView.posAtCoords({ left: event.clientX, top: event.clientY }); - - if (pos) { - const $pos = this.editorView.state.doc.resolve(pos.pos); - - const node = pos.inside >= 0 && this.editorView.state.doc.nodeAt(pos.inside); - const disableDropCursor = node && node.type.spec.disableDropCursor; - const disabled = - typeof disableDropCursor == "function" ? disableDropCursor(this.editorView, pos, event) : disableDropCursor; - - if (!disabled) { - const { isBetweenNodesOfType: isBetweenNodesOfTypeLists, position } = isBetweenNodesOfType($pos, "list"); - - if (isBetweenNodesOfTypeLists && position !== undefined) { - this.lastValidPosition = position; - this.setCursor(position); - console.log("cursor set at ", position); - return; - } - - let target = pos.pos; - if (this.editorView.dragging && this.editorView.dragging.slice) { - const point = dropPoint(this.editorView.state.doc, target, this.editorView.dragging.slice); - if (point != null) target = point; - } - this.setCursor(target); - this.scheduleRemoval(5000); - } - } - } - - dragend() { - this.scheduleRemoval(20); - } - - drop(event: DragEvent) { - const pos = this.editorView.posAtCoords({ left: event.clientX, top: event.clientY }); - if (pos) { - const $pos = this.editorView.state.doc.resolve(pos.pos); - if (isBetweenNodesOfType($pos, "list").isBetweenNodesOfType) { - event.preventDefault(); - event.stopPropagation(); - - let draggedContent: ProseMirrorNode | null = null; - let originalFrom: number | null = null; - let originalTo: number | null = null; - - if (this.lastValidPosition !== null) { - if (this.editorView.state.selection instanceof NodeSelection) { - draggedContent = this.editorView.state.selection.node; - originalFrom = this.editorView.state.selection.from; - originalTo = this.editorView.state.selection.to; - } - - if (draggedContent && originalFrom !== null && originalTo !== null) { - const tr = this.editorView.state.tr; - // Remove the node from its original position - tr.delete(originalFrom, originalTo); - // Insert the node at the new position - tr.insert(this.lastValidPosition, draggedContent); - // Apply the transaction - this.editorView.dispatch(tr); - } - this.lastValidPosition = null; - } - } - } - this.scheduleRemoval(20); - return true; - } - - dragleave(event: DragEvent) { - const relatedTarget = event.relatedTarget as Node | null; - if (relatedTarget && !this.editorView.dom.contains(relatedTarget)) { - this.setCursor(null); - } - } -} - -export const DropCursorExtension = Extension.create({ - name: "dropCursor", - addProseMirrorPlugins(this) { - return [ - dropCursor( - { - width: 2, - class: "transition-all duration-200 ease-[cubic-bezier(0.165, 0.84, 0.44, 1)] text-custom-text-300", - }, - this - ), - ]; - }, -}); diff --git a/packages/editor/src/core/extensions/drop-cursor-weird.ts b/packages/editor/src/core/extensions/drop-cursor-weird.ts deleted file mode 100644 index cf6f52d492c..00000000000 --- a/packages/editor/src/core/extensions/drop-cursor-weird.ts +++ /dev/null @@ -1,374 +0,0 @@ -import { Plugin, EditorState, NodeSelection, PluginKey } from "@tiptap/pm/state"; -import { EditorView } from "@tiptap/pm/view"; -import { dropPoint } from "@tiptap/pm/transform"; -import { Editor, Extension } from "@tiptap/core"; -import { NodeType, Node as ProseMirrorNode, ResolvedPos } from "@tiptap/pm/model"; - -interface DropCursorOptions { - /// The color of the cursor. Defaults to `black`. Use `false` to apply no color and rely only on class. - color?: string | false; - - /// The precise width of the cursor in pixels. Defaults to 1. - width?: number; - - /// A CSS class name to add to the cursor element. - class?: string; -} - -/// Create a plugin that, when added to a ProseMirror instance, -/// causes a decoration to show up at the drop position when something -/// is dragged over the editor. -/// -/// Nodes may add a `disableDropCursor` property to their spec to -/// control the showing of a drop cursor inside them. This may be a -/// boolean or a function, which will be called with a view and a -/// position, and should return a boolean. -export function dropCursor(options: DropCursorOptions = {}, tiptapEditorOptions: any): Plugin { - const pluginKey = new PluginKey("dropCursor"); - return new Plugin({ - key: pluginKey, - state: { - init() { - return { dropPosByDropCursorPos: null }; - }, - apply(tr, state) { - // Get the new state from meta - const meta = tr.getMeta(pluginKey); - if (meta) { - return { dropPosByDropCursorPos: meta.dropPosByDropCursorPos }; - } - return state; - }, - }, - view(editorView) { - return new DropCursorView(editorView, options, tiptapEditorOptions.editor, pluginKey); - }, - props: { - handleDrop(view, event, slice, moved) { - console.log("aaya"); - const coordinates = { left: event.clientX, top: event.clientY }; - const pos = view.posAtCoords(coordinates); - - // if (!pos) return false; - - const $pos = view.state.doc.resolve(pos.pos); - const { isBetweenNodesOfType: isBetweenLists } = isBetweenNodesOfType($pos, "list"); - - if (isBetweenLists) { - console.log("asdff"); - const state = pluginKey.getState(view.state); - const dropPosByDropCursorPos = state?.dropPosByDropCursorPos; - // __AUTO_GENERATED_PRINT_VAR_START__ - console.log("dropCursor#handleDrop#if dropPosByDropCursorPos: %s", dropPosByDropCursorPos + 1); // __AUTO_GENERATED_PRINT_VAR_END__ - if (dropPosByDropCursorPos != null) { - const tr = view.state.tr; - if (moved) { - tr.deleteSelection(); - } - tr.insert(dropPosByDropCursorPos + 1, slice.content); - view.dispatch(tr); - } - return true; - } - return false; - }, - }, - }); -} - -// Add disableDropCursor to NodeSpec -declare module "prosemirror-model" { - interface NodeSpec { - disableDropCursor?: - | boolean - | ((view: EditorView, pos: { pos: number; inside: number }, event: DragEvent) => boolean); - } -} - -class DropCursorView { - private width: number; - private color: string | undefined; - private class: string | undefined; - private cursorPos: number | null = null; - private element: HTMLElement | null = null; - private timeout: ReturnType | null = null; - private handlers: { name: string; handler: (event: Event) => void }[]; - private editor: Editor; - - constructor( - private readonly editorView: EditorView, - options: DropCursorOptions, - editor: Editor, - private readonly pluginKey: PluginKey - ) { - this.width = options.width ?? 1; - this.color = options.color === false ? undefined : options.color || "red"; - this.class = options.class; - this.editor = editor; - - this.handlers = ["dragover", "dragend", "drop", "dragleave"].map((name) => { - const handler = (e: Event) => { - (this as any)[name](e); - }; - editorView.dom.addEventListener(name, handler); - return { name, handler }; - }); - } - - destroy() { - this.handlers.forEach(({ name, handler }) => this.editorView.dom.removeEventListener(name, handler)); - } - - update(editorView: EditorView, prevState: EditorState) { - if (this.cursorPos != null && prevState.doc != editorView.state.doc) { - if (this.cursorPos > editorView.state.doc.content.size) this.setCursor(null); - else this.updateOverlay(); - } - } - - setCursor(pos: number | null) { - if (pos == this.cursorPos) return; - this.cursorPos = pos; - if (pos == null) { - this.element!.parentNode!.removeChild(this.element!); - this.element = null; - } else { - this.updateOverlay(); - } - } - - updateOverlay() { - const $pos = this.editorView.state.doc.resolve(this.cursorPos!); - const isBlock = !$pos.parent.inlineContent; - let rect: Partial; - const editorDOM = this.editorView.dom; - const editorRect = editorDOM.getBoundingClientRect(); - const scaleX = editorRect.width / editorDOM.offsetWidth; - const scaleY = editorRect.height / editorDOM.offsetHeight; - - if (isBlock) { - const before = $pos.nodeBefore; - const after = $pos.nodeAfter; - if (before || after) { - const node = this.editorView.nodeDOM(this.cursorPos! - (before ? before.nodeSize : 0)); - if (node) { - const nodeRect = (node as HTMLElement).getBoundingClientRect(); - let top = before ? nodeRect.bottom : nodeRect.top; - if (before && after) { - top = (top + (this.editorView.nodeDOM(this.cursorPos!) as HTMLElement).getBoundingClientRect().top) / 2; - } - const halfWidth = (this.width / 2) * scaleY; - rect = { left: nodeRect.left, right: nodeRect.right, top: top - halfWidth, bottom: top + halfWidth }; - } - } - } - if (!rect) { - const coords = this.editorView.coordsAtPos(this.cursorPos!); - const halfWidth = (this.width / 2) * scaleX; - rect = { left: coords.left - halfWidth, right: coords.left + halfWidth, top: coords.top, bottom: coords.bottom }; - } - - const parent = this.editorView.dom.offsetParent as HTMLElement; - if (!this.element) { - this.element = parent.appendChild(document.createElement("div")); - if (this.class) this.element.className = this.class; - this.element.style.cssText = "position: absolute; z-index: 50; pointer-events: none;"; - if (this.color) { - this.element.style.backgroundColor = this.color; - } - } - this.element.classList.toggle("prosemirror-dropcursor-block", isBlock); - this.element.classList.toggle("prosemirror-dropcursor-inline", !isBlock); - let parentLeft: number, parentTop: number; - if (!parent || (parent == document.body && getComputedStyle(parent).position == "static")) { - parentLeft = -window.scrollX; - parentTop = -window.scrollY; - } else { - const rect = parent.getBoundingClientRect(); - const parentScaleX = rect.width / parent.offsetWidth, - parentScaleY = rect.height / parent.offsetHeight; - parentLeft = rect.left - parent.scrollLeft * parentScaleX; - parentTop = rect.top - parent.scrollTop * parentScaleY; - } - this.element.style.left = (rect.left - parentLeft) / scaleX + "px"; - this.element.style.top = (rect.top - parentTop) / scaleY + "px"; - this.element.style.width = (rect.right - rect.left) / scaleX + "px"; - this.element.style.height = (rect.bottom - rect.top) / scaleY + "px"; - } - - scheduleRemoval(timeout: number) { - if (this.timeout) clearTimeout(this.timeout); - this.timeout = setTimeout(() => this.setCursor(null), timeout); - } - - dragover(event: DragEvent) { - if (!this.editorView.editable) return; - const pos = this.editorView.posAtCoords({ left: event.clientX, top: event.clientY }); - - if (pos) { - const $pos = this.editorView.state.doc.resolve(pos.pos); - - const node = pos.inside >= 0 && this.editorView.state.doc.nodeAt(pos.inside); - const disableDropCursor = node && node.type.spec.disableDropCursor; - const disabled = - typeof disableDropCursor == "function" ? disableDropCursor(this.editorView, pos, event) : disableDropCursor; - - if (!disabled) { - const { isBetweenNodesOfType: isBetweenNodesOfTypeLists, position } = isBetweenNodesOfType($pos, "list"); - - if (isBetweenNodesOfTypeLists && position !== undefined) { - this.dropPosByDropCursorPos = position; - this.setCursor(position); - return; - } - - let target = pos.pos; - if (this.editorView.dragging && this.editorView.dragging.slice) { - const point = dropPoint(this.editorView.state.doc, target, this.editorView.dragging.slice); - if (point != null) target = point; - } - this.setCursor(target); - this.scheduleRemoval(5000); - } - } - } - - dragend() { - this.scheduleRemoval(20); - } - - drop() { - this.scheduleRemoval(20); - } - - dragleave(event: DragEvent) { - const relatedTarget = event.relatedTarget as Node | null; - if (relatedTarget && !this.editorView.dom.contains(relatedTarget)) { - this.setCursor(null); - } - } - - set dropPosByDropCursorPos(pos: number | null) { - const tr = this.editorView.state.tr; - tr.setMeta(this.pluginKey, { dropPosByDropCursorPos: pos }); - this.editorView.dispatch(tr); - } - - get dropPosByDropCursorPos(): number | null { - return this.pluginKey.getState(this.editorView.state)?.dropPosByDropCursorPos; - } -} - -export const DropCursorExtension = Extension.create({ - name: "dropCursor", - addProseMirrorPlugins(this) { - return [ - dropCursor( - { - width: 2, - class: "transition-all duration-200 ease-[cubic-bezier(0.165, 0.84, 0.44, 1)] text-custom-text-300", - }, - this - ), - ]; - }, -}); - -function isBetweenNodesOfType($pos: ResolvedPos, nodeTypeName: string) { - const { doc } = $pos; - const nodeType = doc.type.schema.nodes[nodeTypeName]; - const listItemType = doc.type.schema.nodes["list_item"]; // Replace with your list item node type name - - function isNodeType(node: ProseMirrorNode | null, type: NodeType) { - return node && node.type === type; - } - - let isBetweenNodesOfType = false; - let isDirectlyBetweenLists = false; - let position: number | null = null; - - // Check if we are inside a list item - let foundListItem = false; - - for (let depth = $pos.depth; depth >= 0; depth--) { - const node = $pos.node(depth); - if (isNodeType(node, listItemType)) { - foundListItem = true; - - const listItemPos = $pos.before(depth); - const parent = $pos.node(depth - 1); // This should be the list node - - if (parent && isNodeType(parent, nodeType)) { - const index = findChildIndex(parent, $pos.before(depth + 1)); - const nextIndex = index + 1; - - if (nextIndex < parent.childCount) { - // There is a next sibling list item - position = listItemPos + node.nodeSize; - } else { - // No siblings, insert at the end of the list - position = $pos.end(depth - 1); - } - } - break; - } - } - - if (foundListItem) { - isBetweenNodesOfType = true; - isDirectlyBetweenLists = false; - } - - // If not found inside a list item, check if we are directly between list nodes - const nodeBefore = $pos.nodeBefore; - const nodeAfter = $pos.nodeAfter; - const nodeBeforeIsType = isNodeType(nodeBefore, nodeType); - const nodeAfterIsType = isNodeType(nodeAfter, nodeType); - - if (nodeBeforeIsType && nodeAfterIsType) { - // Cursor is directly between two list nodes - isBetweenNodesOfType = true; - isDirectlyBetweenLists = true; - position = $pos.pos; - } else if (nodeBeforeIsType || nodeAfterIsType) { - isBetweenNodesOfType = true; - isDirectlyBetweenLists = false; - position = $pos.pos; - } else if (!foundListItem) { - // If not between lists or inside a list item, look ahead for the next list - const nextListPos = findNextNodeOfType($pos, nodeType); - if (nextListPos != null) { - isBetweenNodesOfType = true; - isDirectlyBetweenLists = false; - position = nextListPos; - } - } - - return { - isBetweenNodesOfType, - isDirectlyBetweenLists, - position, - }; -} - -function findChildIndex(parent: ProseMirrorNode, pos: number): number { - let offset = 0; - for (let i = 0; i < parent.childCount; i++) { - const child = parent.child(i); - if (offset + child.nodeSize > pos) { - return i; - } - offset += child.nodeSize; - } - return -1; // Return -1 if not found -} - -function findNextNodeOfType($pos: ResolvedPos, nodeType: NodeType): number | null { - for (let i = $pos.pos; i < $pos.doc.content.size; i++) { - const node = $pos.doc.nodeAt(i); - if (node && node.type === nodeType) { - return i; - } - } - return null; -} diff --git a/packages/editor/src/core/extensions/drop-cursor-working.ts b/packages/editor/src/core/extensions/drop-cursor-working.ts deleted file mode 100644 index 74f727f39a9..00000000000 --- a/packages/editor/src/core/extensions/drop-cursor-working.ts +++ /dev/null @@ -1,359 +0,0 @@ -import { Plugin, EditorState, NodeSelection, PluginKey } from "@tiptap/pm/state"; -import { EditorView } from "@tiptap/pm/view"; -import { dropPoint } from "@tiptap/pm/transform"; -import { Editor, Extension } from "@tiptap/core"; -import { NodeType, Node as ProseMirrorNode, ResolvedPos } from "@tiptap/pm/model"; - -interface DropCursorOptions { - /// The color of the cursor. Defaults to `black`. Use `false` to apply no color and rely only on class. - color?: string | false; - - /// The precise width of the cursor in pixels. Defaults to 1. - width?: number; - - /// A CSS class name to add to the cursor element. - class?: string; -} - -/// Create a plugin that, when added to a ProseMirror instance, -/// causes a decoration to show up at the drop position when something -/// is dragged over the editor. -/// -/// Nodes may add a `disableDropCursor` property to their spec to -/// control the showing of a drop cursor inside them. This may be a -/// boolean or a function, which will be called with a view and a -/// position, and should return a boolean. -export function dropCursor(options: DropCursorOptions = {}, tiptapEditorOptions: any): Plugin { - const pluginKey = new PluginKey("dropCursor"); - return new Plugin({ - key: pluginKey, - state: { - init() { - return { dropPosByDropCursorPos: null }; - }, - apply(tr, state) { - // Get the new state from meta - const meta = tr.getMeta(pluginKey); - if (meta) { - return { dropPosByDropCursorPos: meta.dropPosByDropCursorPos }; - } - return state; - }, - }, - view(editorView) { - return new DropCursorView(editorView, options, tiptapEditorOptions.editor, pluginKey); - }, - props: { - handleDrop(view, event, slice, moved) { - const coordinates = { left: event.clientX, top: event.clientY }; - const pos = view.posAtCoords(coordinates); - - if (!pos) return false; - - const $pos = view.state.doc.resolve(pos.pos); - const { isBetweenNodesOfType: isBetweenLists, position } = isBetweenNodesOfType($pos, "list"); - - if (isBetweenLists && position !== null) { - const state = pluginKey.getState(view.state); - let dropPosByDropCursorPos = state?.dropPosByDropCursorPos; - - if (dropPosByDropCursorPos != null) { - const tr = view.state.tr; - - if (moved) { - // Get the size of content to be deleted - const selection = tr.selection; - const deleteSize = selection.to - selection.from; - - // Adjust drop position if it's after the deletion point - if (dropPosByDropCursorPos > selection.from) { - dropPosByDropCursorPos -= deleteSize; - } - - tr.deleteSelection(); - } - - tr.insert(dropPosByDropCursorPos, slice.content); - view.dispatch(tr); - return true; - } - } - return false; - }, - }, - }); -} - -// Add disableDropCursor to NodeSpec -declare module "prosemirror-model" { - interface NodeSpec { - disableDropCursor?: - | boolean - | ((view: EditorView, pos: { pos: number; inside: number }, event: DragEvent) => boolean); - } -} - -class DropCursorView { - private width: number; - private color: string | undefined; - private class: string | undefined; - private cursorPos: number | null = null; - private element: HTMLElement | null = null; - private timeout: ReturnType | null = null; - private handlers: { name: string; handler: (event: Event) => void }[]; - private editor: Editor; - - constructor( - private readonly editorView: EditorView, - options: DropCursorOptions, - editor: Editor, - private readonly pluginKey: PluginKey - ) { - this.width = options.width ?? 1; - this.color = options.color === false ? undefined : options.color || "red"; - this.class = options.class; - this.editor = editor; - - this.handlers = ["dragover", "dragend", "drop", "dragleave"].map((name) => { - const handler = (e: Event) => { - (this as any)[name](e); - }; - editorView.dom.addEventListener(name, handler); - return { name, handler }; - }); - } - - destroy() { - this.handlers.forEach(({ name, handler }) => this.editorView.dom.removeEventListener(name, handler)); - } - - update(editorView: EditorView, prevState: EditorState) { - if (this.cursorPos != null && prevState.doc != editorView.state.doc) { - if (this.cursorPos > editorView.state.doc.content.size) this.setCursor(null); - else this.updateOverlay(); - } - } - - setCursor(pos: number | null) { - if (pos == this.cursorPos) return; - this.cursorPos = pos; - if (pos == null) { - this.element!.parentNode!.removeChild(this.element!); - this.element = null; - } else { - this.updateOverlay(); - } - } - - updateOverlay() { - const $pos = this.editorView.state.doc.resolve(this.cursorPos!); - const isBlock = !$pos.parent.inlineContent; - const isSpecialCase = isNodeAtDepthAndItsParentIsParagraphWhoseParentIsList($pos); - let rect: Partial; - const editorDOM = this.editorView.dom; - const editorRect = editorDOM.getBoundingClientRect(); - const scaleX = editorRect.width / editorDOM.offsetWidth; - const scaleY = editorRect.height / editorDOM.offsetHeight; - - if (isBlock) { - const before = $pos.nodeBefore; - const after = $pos.nodeAfter; - if (before || after) { - const node = this.editorView.nodeDOM(this.cursorPos! - (before ? before.nodeSize : 0)); - if (node) { - const nodeRect = (node as HTMLElement).getBoundingClientRect(); - let top = before ? nodeRect.bottom : nodeRect.top; - if (before && after) { - top = (top + (this.editorView.nodeDOM(this.cursorPos!) as HTMLElement).getBoundingClientRect().top) / 2; - } - const halfWidth = (this.width / 2) * scaleY; - rect = { left: nodeRect.left, right: nodeRect.right, top: top - halfWidth, bottom: top + halfWidth }; - } - } - } - if (!rect) { - const coords = this.editorView.coordsAtPos(this.cursorPos!); - const halfWidth = (this.width / 2) * scaleX; - rect = { left: coords.left - halfWidth, right: coords.left + halfWidth, top: coords.top, bottom: coords.bottom }; - } - - const parent = this.editorView.dom.offsetParent as HTMLElement; - if (!this.element) { - this.element = parent.appendChild(document.createElement("div")); - if (this.class) this.element.className = this.class; - this.element.style.cssText = "position: absolute; z-index: 50; pointer-events: none;"; - if (this.color) { - this.element.style.backgroundColor = this.color; - } - } - this.element.classList.toggle("prosemirror-dropcursor-block", isBlock); - this.element.classList.toggle("prosemirror-dropcursor-inline", !isBlock); - - let parentLeft: number, parentTop: number; - if (!parent || (parent == document.body && getComputedStyle(parent).position == "static")) { - parentLeft = -window.scrollX; - parentTop = -window.scrollY; - } else { - const rect = parent.getBoundingClientRect(); - const parentScaleX = rect.width / parent.offsetWidth, - parentScaleY = rect.height / parent.offsetHeight; - parentLeft = rect.left - parent.scrollLeft * parentScaleX; - parentTop = rect.top - parent.scrollTop * parentScaleY; - } - this.element.style.left = (rect.left - parentLeft) / scaleX + "px"; - this.element.style.top = (rect.top - parentTop) / scaleY + "px"; - this.element.style.width = (rect.right - rect.left) / scaleX + "px"; - this.element.style.height = (rect.bottom - rect.top) / scaleY + "px"; - } - - scheduleRemoval(timeout: number) { - if (this.timeout) clearTimeout(this.timeout); - this.timeout = setTimeout(() => this.setCursor(null), timeout); - } - - dragover(event: DragEvent) { - if (!this.editorView.editable) return; - const pos = this.editorView.posAtCoords({ left: event.clientX, top: event.clientY }); - - if (pos) { - const $pos = this.editorView.state.doc.resolve(pos.pos); - - const node = pos.inside >= 0 && this.editorView.state.doc.nodeAt(pos.inside); - const disableDropCursor = node && node.type.spec.disableDropCursor; - const disabled = - typeof disableDropCursor == "function" ? disableDropCursor(this.editorView, pos, event) : disableDropCursor; - - let finalPos: number | null = null; - if (!disabled) { - const { - isBetweenNodesOfType: isBetweenNodesOfTypeLists, - position, - isSpecialCase, - } = isBetweenNodesOfType($pos, "list"); - - const node = this.editorView.nodeDOM(position); - const listElement = (node as HTMLElement).closest(".prosemirror-flat-list"); - finalPos = this.editorView.posAtDOM(listElement, 0); - const $pos1 = this.editorView.state.doc.resolve(finalPos); - console.log("asfd", $pos1); - if (isBetweenNodesOfTypeLists && position !== undefined) { - this.dropPosByDropCursorPos = finalPos - 1; - this.setCursor(finalPos - 1); - return; - } - - let target = pos.pos; - if (this.editorView.dragging && this.editorView.dragging.slice) { - const point = dropPoint(this.editorView.state.doc, target, this.editorView.dragging.slice); - if (point != null) target = point; - } - this.setCursor(target); - this.scheduleRemoval(5000); - } - } - } - - dragend() { - this.scheduleRemoval(20); - } - - drop() { - this.scheduleRemoval(20); - } - - dragleave(event: DragEvent) { - const relatedTarget = event.relatedTarget as Node | null; - if (relatedTarget && !this.editorView.dom.contains(relatedTarget)) { - this.setCursor(null); - } - } - - set dropPosByDropCursorPos(pos: number | null) { - const tr = this.editorView.state.tr; - tr.setMeta(this.pluginKey, { dropPosByDropCursorPos: pos }); - this.editorView.dispatch(tr); - } - - get dropPosByDropCursorPos(): number | null { - return this.pluginKey.getState(this.editorView.state)?.dropPosByDropCursorPos; - } -} - -export const DropCursorExtension = Extension.create({ - name: "dropCursor", - addProseMirrorPlugins(this) { - return [ - dropCursor( - { - width: 2, - class: "transition-all duration-200 ease-[cubic-bezier(0.165, 0.84, 0.44, 1)] text-custom-text-300", - }, - this - ), - ]; - }, -}); - -function isBetweenNodesOfType($pos: ResolvedPos, nodeTypeName: string) { - const { doc } = $pos; - const nodeType = doc.type.schema.nodes[nodeTypeName]; - - function isNodeType(node: ProseMirrorNode | null, type: NodeType) { - return node && node.type === type; - } - - let isBetweenNodesOfType = false; - let isDirectlyBetweenNodes = false; - let positionToShowAndDrop: number | null = null; - - const isSpecialCase = isNodeAtDepthAndItsParentIsParagraphWhoseParentIsList($pos); - - const nodeBefore = $pos.nodeBefore; - const nodeAfter = $pos.nodeAfter; - const nodeBeforeIsType = isNodeType(nodeBefore, nodeType); - const nodeAfterIsType = isNodeType(nodeAfter, nodeType); - - if (nodeBeforeIsType && nodeAfterIsType) { - isBetweenNodesOfType = true; - isDirectlyBetweenNodes = true; - positionToShowAndDrop = $pos.pos; - } else if (nodeBeforeIsType || nodeAfterIsType) { - isBetweenNodesOfType = true; - isDirectlyBetweenNodes = false; - positionToShowAndDrop = $pos.pos; - } else { - const nextListPos = findNextNodeOfType($pos, nodeType); - if (nextListPos != null) { - isBetweenNodesOfType = true; - isDirectlyBetweenNodes = false; - positionToShowAndDrop = nextListPos; - } - } - - return { - isBetweenNodesOfType, - isDirectlyBetweenLists: isDirectlyBetweenNodes, - position: positionToShowAndDrop, - isSpecialCase, - }; -} - -function findNextNodeOfType($pos: ResolvedPos, nodeType: NodeType): number | null { - for (let i = $pos.pos; i < $pos.doc.content.size; i++) { - const node = $pos.doc.nodeAt(i); - if (node && node.type === nodeType) { - return i; - } - } - return null; -} - -function isNodeAtDepthAndItsParentIsParagraphWhoseParentIsList($pos: ResolvedPos): boolean { - const depth = $pos.depth; - if (depth >= 0) { - const parent = $pos.node(depth); - const grandParent = $pos.node(depth - 1); - return parent.type.name === "paragraph" && grandParent.type.name === "list"; - } - return false; -} diff --git a/packages/editor/src/core/extensions/drop-cursor.optimized.ts b/packages/editor/src/core/extensions/drop-cursor.optimized.ts deleted file mode 100644 index e7a3b3493bc..00000000000 --- a/packages/editor/src/core/extensions/drop-cursor.optimized.ts +++ /dev/null @@ -1,557 +0,0 @@ -import { Plugin, EditorState, PluginKey, NodeSelection } from "@tiptap/pm/state"; -import { EditorView } from "@tiptap/pm/view"; -import { dropPoint } from "@tiptap/pm/transform"; -import { Editor, Extension } from "@tiptap/core"; - -interface DropCursorOptions { - /// The color of the cursor. Defaults to `black`. Use `false` to apply no color and rely only on class. - color?: string | false; - - /// The precise width of the cursor in pixels. Defaults to 1. - width?: number; - - /// A CSS class name to add to the cursor element. - class?: string; -} - -/// Create a plugin that, when added to a ProseMirror instance, -/// causes a decoration to show up at the drop position when something -/// is dragged over the editor. -/// -/// Nodes may add a `disableDropCursor` property to their spec to -/// control the showing of a drop cursor inside them. This may be a -/// boolean or a function, which will be called with a view and a -/// position, and should return a boolean. -export function dropCursor(options: DropCursorOptions = {}, tiptapEditorOptions: { editor: Editor }): Plugin { - const pluginKey = new PluginKey("dropCursor"); - - return new Plugin({ - key: pluginKey, - state: { - init() { - return { dropPosByDropCursorPos: null }; - }, - apply(tr, state) { - // Get the new state from meta - const meta = tr.getMeta(pluginKey); - if (meta) { - return { dropPosByDropCursorPos: meta.dropPosByDropCursorPos }; - } - return state; - }, - }, - view(editorView) { - return new DropCursorView(editorView, options, tiptapEditorOptions.editor, pluginKey); - }, - props: { - handleDrop(view, event, slice, moved) { - const { isBetweenFlatLists, isNestedList, hasNestedLists, pos, isHoveringOverListContent } = - // Instead of calling rawIsBetweenFlatListsFn directly, we rely on the - // Plugin's stored value or re-check if needed. But here, you can - // directly call rawIsBetweenFlatListsFn if you wish. - rawIsBetweenFlatListsFn(event, tiptapEditorOptions.editor) || {}; - - const state = pluginKey.getState(view.state); - let dropPosByDropCursorPos = state?.dropPosByDropCursorPos; - if (isHoveringOverListContent) { - dropPosByDropCursorPos -= 1; - } - - if (isBetweenFlatLists && dropPosByDropCursorPos) { - const tr = view.state.tr; - - if (moved) { - // Get the size of content to be deleted - const selection = tr.selection; - const deleteSize = selection.to - selection.from; - - // Adjust drop position if it's after the deletion point - if (dropPosByDropCursorPos > selection.from) { - dropPosByDropCursorPos -= deleteSize; - } - - tr.deleteSelection(); - } - - const finalDropPos = dropPosByDropCursorPos - 2; - // Insert the content - tr.insert(finalDropPos, slice.content); - - // Create a NodeSelection on the newly inserted content - const $pos = tr.doc.resolve(finalDropPos); - const node = $pos.nodeAfter; - - if (node) { - const nodeSelection = NodeSelection.create(tr.doc, dropPosByDropCursorPos); - tr.setSelection(nodeSelection); - } - - view.dispatch(tr); - return true; - } - return false; - }, - }, - }); -} - -// Add disableDropCursor to NodeSpec -declare module "prosemirror-model" { - interface NodeSpec { - disableDropCursor?: - | boolean - | ((view: EditorView, pos: { pos: number; inside: number }, event: DragEvent) => boolean); - } -} - -class DropCursorView { - private width: number; - private color: string | undefined; - private class: string | undefined; - private cursorPos: number | null = null; - private element: HTMLElement | null = null; - private timeout: ReturnType | null = null; - private handlers: { name: string; handler: (event: Event) => void }[]; - private editor: Editor; - - // Throttled version of our isBetweenFlatListsFn - private isBetweenFlatListsFn: (event: DragEvent) => ReturnType; - - constructor( - private readonly editorView: EditorView, - options: DropCursorOptions, - editor: Editor, - private readonly pluginKey: PluginKey - ) { - this.width = options.width ?? 1; - this.color = options.color === false ? undefined : options.color || `rgb(115, 115, 115)`; - this.class = options.class; - this.editor = editor; - - // Create the throttled function and store for use in dragover - this.isBetweenFlatListsFn = createThrottledIsBetweenFlatListsFn(editor); - - this.handlers = ["dragover", "dragend", "drop", "dragleave"].map((name) => { - const handler = (e: Event) => { - (this as any)[name](e); - }; - editorView.dom.addEventListener(name, handler); - return { name, handler }; - }); - } - - destroy() { - this.handlers.forEach(({ name, handler }) => this.editorView.dom.removeEventListener(name, handler)); - } - - update(editorView: EditorView, prevState: EditorState) { - if (this.cursorPos != null && prevState.doc != editorView.state.doc) { - if (this.cursorPos > editorView.state.doc.content.size) this.setCursor(null); - else this.updateOverlay(); - } - } - - setCursor(pos: number | null, isBetweenFlatLists?: boolean) { - this.cursorPos = pos; - if (pos == null) { - if (this.element?.parentNode) { - this.element.parentNode.removeChild(this.element); - } - this.element = null; - } else { - this.updateOverlay(isBetweenFlatLists); - } - } - - updateOverlay(isBetweenFlatLists?: boolean) { - const isBetweenFlatList = isBetweenFlatLists ?? false; - const $pos = this.editorView.state.doc.resolve(this.cursorPos!); - const isBlock = !$pos.parent.inlineContent; - let rect: Partial; - const editorDOM = this.editorView.dom; - const editorRect = editorDOM.getBoundingClientRect(); - const scaleX = editorRect.width / editorDOM.offsetWidth; - const scaleY = editorRect.height / editorDOM.offsetHeight; - - if (isBlock) { - const before = $pos.nodeBefore; - const after = $pos.nodeAfter; - if (before || after) { - const node = this.editorView.nodeDOM(this.cursorPos! - (before ? before.nodeSize : 0)); - if (node) { - const nodeRect = (node as HTMLElement).getBoundingClientRect(); - let top = before ? nodeRect.bottom : nodeRect.top; - if (before && after) { - top = (top + (this.editorView.nodeDOM(this.cursorPos!) as HTMLElement).getBoundingClientRect().top) / 2; - } - const halfWidth = (this.width / 2) * scaleY; - rect = { - left: nodeRect.left, - right: nodeRect.right, - top: top - halfWidth, - bottom: top + halfWidth, - }; - } - } - } - if (!rect) { - const coords = this.editorView.coordsAtPos(this.cursorPos!); - const halfWidth = (this.width / 2) * scaleX; - rect = { - left: coords.left - halfWidth, - right: coords.left + halfWidth, - top: coords.top, - bottom: coords.bottom, - }; - } - - const parent = this.editorView.dom.offsetParent as HTMLElement; - if (!this.element) { - this.element = parent.appendChild(document.createElement("div")); - if (this.class) this.element.className = this.class; - this.element.style.cssText = "position: absolute; z-index: 50; pointer-events: none;"; - if (this.color) { - this.element.style.backgroundColor = this.color; - } - } - this.element.classList.toggle("prosemirror-dropcursor-block", isBlock); - this.element.classList.toggle("prosemirror-dropcursor-inline", !isBlock); - - let parentLeft: number, parentTop: number; - if (!parent || (parent == document.body && getComputedStyle(parent).position == "static")) { - parentLeft = -window.scrollX; - parentTop = -window.scrollY; - } else { - const parentRect = parent.getBoundingClientRect(); - const parentScaleX = parentRect.width / parent.offsetWidth; - const parentScaleY = parentRect.height / parent.offsetHeight; - parentLeft = parentRect.left - parent.scrollLeft * parentScaleX; - parentTop = parentRect.top - parent.scrollTop * parentScaleY; - } - - // Adjust left if we're between flat lists - const finalLeft = (rect.left! - parentLeft) / scaleX - (isBetweenFlatList ? 20 : 0); - const finalTop = (rect.top! - parentTop) / scaleY; - const finalWidth = (rect.right! - rect.left!) / scaleX; - const finalHeight = (rect.bottom! - rect.top!) / scaleY; - - this.element.style.left = finalLeft + "px"; - this.element.style.top = finalTop + "px"; - this.element.style.width = finalWidth + "px"; - this.element.style.height = finalHeight + "px"; - } - - scheduleRemoval(timeout: number) { - if (this.timeout) clearTimeout(this.timeout); - this.timeout = setTimeout(() => this.setCursor(null), timeout); - } - - dragover(event: DragEvent) { - if (!this.editorView.editable) return; - - // Throttled call to the function - const result = this.isBetweenFlatListsFn(event); - if (!result) return; - - const { isBetweenFlatLists, pos: posList, isHoveringOverListContent } = result; - - // If we’re between flat lists, override the usual pos with posList - const pos = this.editorView.posAtCoords({ - left: event.clientX, - top: event.clientY, - }); - if (!pos) return; - - if (isBetweenFlatLists && this.element) { - // Reassign to the pos we discovered in the function - pos.pos = posList; - } - - const node = pos.inside >= 0 && this.editorView.state.doc.nodeAt(pos.inside); - const disableDropCursor = node && node.type.spec.disableDropCursor; - const disabled = - typeof disableDropCursor == "function" ? disableDropCursor(this.editorView, pos, event) : disableDropCursor; - - if (!disabled && pos) { - let target = pos.pos; - if (this.editorView.dragging && this.editorView.dragging.slice) { - const point = dropPoint(this.editorView.state.doc, target, this.editorView.dragging.slice); - if (point != null) target = point; - } - this.dropPosByDropCursorPos = target; - this.setCursor(target, !!isBetweenFlatLists && !isHoveringOverListContent); - this.scheduleRemoval(5000); - } - } - - dragend() { - this.scheduleRemoval(20); - } - - drop() { - this.scheduleRemoval(20); - } - - dragleave(event: DragEvent) { - const relatedTarget = event.relatedTarget as Node | null; - if (relatedTarget && !this.editorView.dom.contains(relatedTarget)) { - this.setCursor(null); - } - } - - set dropPosByDropCursorPos(pos: number | null) { - const tr = this.editorView.state.tr; - tr.setMeta(this.pluginKey, { dropPosByDropCursorPos: pos }); - this.editorView.dispatch(tr); - } - - get dropPosByDropCursorPos(): number | null { - return this.pluginKey.getState(this.editorView.state)?.dropPosByDropCursorPos; - } -} - -export const DropCursorExtension = Extension.create({ - name: "dropCursor", - addProseMirrorPlugins() { - return [ - dropCursor( - { - width: 2, - class: "transition-all duration-200 ease-[cubic-bezier(0.165, 0.84, 0.44, 1)]", - }, - this - ), - ]; - }, -}); - -function rawIsBetweenFlatListsFn(event: DragEvent, editor: Editor) { - // Cache coordinates and use a single object for multiple coordinate lookups - const coords = { - left: event.clientX, - top: event.clientY, - }; - - // Use WeakMap to cache element positions and calculations - const positionCache = new WeakMap(); - - // Get element under drag with a more efficient selector strategy - const elementUnderDrag = document.elementFromPoint(coords.left, coords.top); - if (!elementUnderDrag) return null; - - // Find closest flat list using a cached selector - const currentFlatList = elementUnderDrag.closest(".prosemirror-flat-list"); - if (!currentFlatList) return null; - - // Use a single getBoundingClientRect call and cache the result - const currentFlatListRect = currentFlatList.getBoundingClientRect(); - - // Initialize state object once - const state = { - isHoveringOverListContent: !elementUnderDrag.classList.contains("prosemirror-flat-list"), - isBetweenFlatLists: true, - hasNestedLists: false, - pos: null as number | null, - listLevel: 0, - isNestedList: false, - }; - - // Efficient position calculation with caching - const getPositionFromElement = (element: Element): number | null => { - if (positionCache.has(element)) { - return positionCache.get(element); - } - - const rect = element.getBoundingClientRect(); - const pos = editor.view.posAtCoords({ - left: rect.left, - top: rect.top, - }); - - const result = pos?.pos ?? null; - positionCache.set(element, result); - return result; - }; - - // Batch DOM operations - const sibling = currentFlatList.nextElementSibling; - const firstNestedList = currentFlatList.querySelector(":scope > .prosemirror-flat-list"); - - // Calculate list level efficiently using a direct parent check - const level = getListLevelOptimized(currentFlatList); - state.listLevel = level; - state.isNestedList = level >= 1; - - // Determine position with minimal DOM operations - if (sibling) { - state.pos = getPositionFromElement(sibling); - } else if (firstNestedList) { - state.pos = getPositionFromElement(firstNestedList); - state.hasNestedLists = true; - } else if (level >= 1 && !sibling) { - const parent = currentFlatList.parentElement; - if (parent) { - state.pos = getPositionFromElement(parent); - } - } - - if (!state.pos) return null; - - return { - ...state, - pos: state.pos - 1, - }; -} - -// Optimized list level calculation -function getListLevelOptimized(element: Element): number { - let level = 0; - let current = element.parentElement; - - // Use a more efficient selector matching - while (current && !current.classList.contains("ProseMirror")) { - if (current.classList.contains("prosemirror-flat-list")) { - level++; - } - current = current.parentElement; - } - - return level; -} - -function getListLevel(element: Element): number { - let level = 0; - let current = element.parentElement; - - while (current && current !== document.body) { - if (current.matches(".prosemirror-flat-list")) { - level++; - } - current = current.parentElement; - } - - return level; -} - -/** - * The original (unthrottled) version of the function that inspects the DOM - * to figure out if we are between flat lists. - */ -// function rawIsBetweenFlatListsFn(event: DragEvent, editor: Editor) { -// const elementUnderDrag = document.elementFromPoint(event.clientX, event.clientY); -// if (!elementUnderDrag) { -// return null; -// } -// -// let editorPos = editor.view.posAtCoords({ left: event.clientX, top: event.clientY }); -// let pos = null; -// const currentFlatList = elementUnderDrag.closest(".prosemirror-flat-list"); -// -// if (!currentFlatList) { -// return null; -// } -// -// let hasNestedLists = false; -// let firstChild = null; -// let isHoveringOverListContent = false; -// -// // If the element under drag is not the flat list itself but a child of it -// if (currentFlatList && !elementUnderDrag.classList.contains("prosemirror-flat-list")) { -// isHoveringOverListContent = true; -// } -// -// if (currentFlatList) { -// const sibling = currentFlatList.nextElementSibling; -// if (sibling) { -// const rect = sibling.getBoundingClientRect(); -// pos = editor.view.posAtCoords({ -// left: rect.left, -// top: rect.top, -// }); -// } -// -// firstChild = currentFlatList.querySelector(".prosemirror-flat-list") as HTMLElement; -// if (firstChild) { -// const rect = firstChild.getBoundingClientRect(); -// pos = editor.view.posAtCoords({ -// left: rect.left, -// top: rect.top, -// }); -// hasNestedLists = true; -// } -// } -// -// const level = getListLevel(currentFlatList); -// if (level >= 1) { -// const sibling = currentFlatList.nextElementSibling; -// if (!sibling) { -// const currentFlatListParentSibling = currentFlatList.parentElement; -// if (currentFlatListParentSibling) { -// const rect = currentFlatListParentSibling.getBoundingClientRect(); -// pos = editor.view.posAtCoords({ -// left: rect.left, -// top: rect.top, -// }); -// } -// } -// } -// -// if (!pos) { -// return null; -// } -// -// return { -// isHoveringOverListContent, -// isBetweenFlatLists: !!currentFlatList, -// pos: pos.pos - 1, -// listLevel: level, -// isNestedList: level >= 1, -// hasNestedLists, -// }; -// } - -/** - * Throttler factory for rawIsBetweenFlatListsFn. - * You can tweak timeThreshold and moveThreshold for your needs. - */ -function createThrottledIsBetweenFlatListsFn( - editor: Editor, - timeThreshold = 300, // ms between new computations - moveThreshold = 5 // px of mouse movement before re-checking -) { - let lastCallTime = 0; - let lastX = 0; - let lastY = 0; - let lastResult: ReturnType | null = null; - - return function throttledIsBetweenFlatListsFn(event: DragEvent) { - const now = performance.now(); - const dx = Math.abs(event.clientX - lastX); - const dy = Math.abs(event.clientY - lastY); - - // Only recalc if we moved enough OR enough time passed - if (dx < moveThreshold && dy < moveThreshold && now - lastCallTime < timeThreshold) { - return lastResult; - } - - lastX = event.clientX; - lastY = event.clientY; - lastCallTime = now; - lastResult = rawIsBetweenFlatListsFn(event, editor); - return lastResult; - }; -} - -// function getListLevel(element: Element): number { -// let level = 0; -// let current = element.parentElement; -// -// while (current && current !== document.body) { -// if (current.matches(".prosemirror-flat-list")) { -// level++; -// } -// current = current.parentElement; -// } -// -// return level; -// } diff --git a/packages/editor/src/core/extensions/drop-cursor.ts b/packages/editor/src/core/extensions/drop-cursor.ts index 985c1aff5b4..98e04c5a7ae 100644 --- a/packages/editor/src/core/extensions/drop-cursor.ts +++ b/packages/editor/src/core/extensions/drop-cursor.ts @@ -223,7 +223,6 @@ class DropCursorView { const finalTop = (rect.top! - parentTop) / scaleY; const finalWidth = (rect.right! - rect.left!) / scaleX; const finalHeight = (rect.bottom! - rect.top!) / scaleY; - console.log("isBetweenFlatList", isBetweenFlatList); this.element.style.transform = isBetweenFlatList ? `translateX(${-20}px` : `translateX(0px)`; this.element.style.left = finalLeft + "px"; this.element.style.top = finalTop + "px"; @@ -334,7 +333,6 @@ function rawIsBetweenFlatListsFn(event: DragEvent, editor: Editor) { const currentFlatList = elementUnderDrag.closest(".prosemirror-flat-list"); if (!currentFlatList) return null; - console.log(currentFlatList); let isInsideToggleOrTask = false; if ( currentFlatList.getAttribute("data-list-kind") === "toggle" || @@ -343,7 +341,6 @@ function rawIsBetweenFlatListsFn(event: DragEvent, editor: Editor) { isInsideToggleOrTask = true; } - console.log("isInsideToggleOrTask", isInsideToggleOrTask); const state = { isHoveringOverListContent: !elementUnderDrag.classList.contains("prosemirror-flat-list"), isBetweenFlatLists: true, @@ -357,7 +354,6 @@ function rawIsBetweenFlatListsFn(event: DragEvent, editor: Editor) { state.isHoveringOverListContent = firstChildListMarker?.classList.contains("list-marker"); } - console.log("isHoveringOverListContent", state.isHoveringOverListContent); const getPositionFromElement = (element: Element, some?: boolean): number | null => { if (positionCache.has(element)) { return positionCache.get(element); @@ -454,17 +450,3 @@ function createThrottledIsBetweenFlatListsFn( return lastResult; }; } - -// function getListLevel(element: Element): number { -// let level = 0; -// let current = element.parentElement; -// -// while (current && current !== document.body) { -// if (current.matches(".prosemirror-flat-list")) { -// level++; -// } -// current = current.parentElement; -// } -// -// return level; -// }