diff --git a/packages/editor/core/src/styles/editor.css b/packages/editor/core/src/styles/editor.css index 4cb4e9772..5868fce91 100644 --- a/packages/editor/core/src/styles/editor.css +++ b/packages/editor/core/src/styles/editor.css @@ -236,7 +236,7 @@ div[data-type="horizontalRule"] { margin-bottom: 0; & > div { - border-bottom: 1px solid rgb(var(--color-border-200)); + border-bottom: 2px solid rgb(var(--color-border-200)); } } diff --git a/packages/editor/core/src/ui/extensions/horizontal-rule/horizontal-rule.ts b/packages/editor/core/src/ui/extensions/horizontal-rule/horizontal-rule.ts index 2af845b7a..b9be1a314 100644 --- a/packages/editor/core/src/ui/extensions/horizontal-rule/horizontal-rule.ts +++ b/packages/editor/core/src/ui/extensions/horizontal-rule/horizontal-rule.ts @@ -28,11 +28,22 @@ export const CustomHorizontalRule = Node.create({ group: "block", parseHTML() { - return [{ tag: "hr" }]; + return [ + { + tag: `div[data-type="${this.name}"]`, + }, + { tag: "hr" }, + ]; }, renderHTML({ HTMLAttributes }) { - return ["hr", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)]; + return [ + "div", + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { + "data-type": this.name, + }), + ["div", {}], + ]; }, addCommands() { diff --git a/packages/editor/document-editor/src/ui/menu/block-menu.tsx b/packages/editor/document-editor/src/ui/menu/block-menu.tsx index 9703113f7..6fc9a87fe 100644 --- a/packages/editor/document-editor/src/ui/menu/block-menu.tsx +++ b/packages/editor/document-editor/src/ui/menu/block-menu.tsx @@ -101,21 +101,44 @@ export default function BlockMenu(props: BlockMenuProps) { label: "Duplicate", isDisabled: editor.state.selection.content().content.firstChild?.type.name === "image", onClick: (e) => { - const { view } = editor; - const { state } = view; - const { selection } = state; - - editor - .chain() - .insertContentAt(selection.to, selection.content().content.firstChild!.toJSON(), { - updateSelection: true, - }) - .focus(selection.to + 1, { scrollIntoView: false }) - .run(); - - popup.current?.hide(); e.preventDefault(); e.stopPropagation(); + + try { + const { state } = editor; + const { selection } = state; + const firstChild = selection.content().content.firstChild; + const docSize = state.doc.content.size; + + if (!firstChild) { + throw new Error("No content selected or content is not duplicable."); + } + + // Directly use selection.to as the insertion position + const insertPos = selection.to; + + // Ensure the insertion position is within the document's bounds + if (insertPos < 0 || insertPos > docSize) { + throw new Error("The insertion position is invalid or outside the document."); + } + + const contentToInsert = firstChild.toJSON(); + + // Insert the content at the calculated position + editor + .chain() + .insertContentAt(insertPos, contentToInsert, { + updateSelection: true, + }) + .focus(Math.min(insertPos + 1, docSize), { scrollIntoView: false }) + .run(); + } catch (error) { + if (error instanceof Error) { + console.error(error.message); + } + } + + popup.current?.hide(); }, }, ]; diff --git a/packages/editor/extensions/src/extensions/drag-drop.tsx b/packages/editor/extensions/src/extensions/drag-drop.tsx index 1c2427418..e9ef9c06e 100644 --- a/packages/editor/extensions/src/extensions/drag-drop.tsx +++ b/packages/editor/extensions/src/extensions/drag-drop.tsx @@ -58,10 +58,10 @@ function nodeDOMAtCoords(coords: { x: number; y: number }) { [ "li", "p:not(:first-child)", - "pre", + ".code-block", "blockquote", "h1, h2, h3", - ".table-wrapper", + "table", "[data-type=horizontalRule]", ].join(", ") ) @@ -77,10 +77,25 @@ function nodePosAtDOM(node: Element, view: EditorView, options: DragHandleOption })?.inside; } +function nodePosAtDOMForBlockquotes(node: Element, view: EditorView) { + const boundingRect = node.getBoundingClientRect(); + + return view.posAtCoords({ + left: boundingRect.left + 1, + top: boundingRect.top + 1, + })?.inside; +} + function calcNodePos(pos: number, view: EditorView) { - const $pos = view.state.doc.resolve(pos); - if ($pos.depth > 1) return $pos.before($pos.depth); - return pos; + const maxPos = view.state.doc.content.size; + const safePos = Math.max(0, Math.min(pos, maxPos)); + const $pos = view.state.doc.resolve(safePos); + + if ($pos.depth > 1) { + const newPos = $pos.before($pos.depth); + return Math.max(0, Math.min(newPos, maxPos)); + } + return safePos; } function DragHandle(options: DragHandleOptions) { @@ -156,6 +171,20 @@ function DragHandle(options: DragHandleOptions) { if (!(node instanceof Element)) return; + if (node.matches("blockquote")) { + let nodePosForBlockquotes = nodePosAtDOMForBlockquotes(node, view); + if (nodePosForBlockquotes === null || nodePosForBlockquotes === undefined) return; + + const docSize = view.state.doc.content.size; + nodePosForBlockquotes = Math.max(0, Math.min(nodePosForBlockquotes, docSize)); + + if (nodePosForBlockquotes >= 0 && nodePosForBlockquotes <= docSize) { + const nodeSelection = NodeSelection.create(view.state.doc, nodePosForBlockquotes); + view.dispatch(view.state.tr.setSelection(nodeSelection)); + } + return; + } + let nodePos = nodePosAtDOM(node, view, options); if (nodePos === null || nodePos === undefined) return; @@ -244,11 +273,13 @@ function DragHandle(options: DragHandleOptions) { rect.top += (lineHeight - 20) / 2; rect.top += paddingTop; + // Li markers if (node.matches("ul:not([data-type=taskList]) li, ol li")) { rect.top += 4; rect.left -= 18; } + rect.width = options.dragHandleWidth; if (!dragHandleElement) return;