[WEB - 480] fix: horizontal rule drag and drop (#4200)

* fix: horizontal rule parsing (with backwards compatibility) and ensuring correct selections

* fix: table drag handle position

* fix: code block drag handle positioning fixes

* fix: blockquote duplication, selection and deletion fixed

* fix: possible range errors and type errors
This commit is contained in:
M. Palanikannan 2024-04-16 15:51:05 +05:30 committed by GitHub
parent b9314889d7
commit 480aa906de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 86 additions and 21 deletions

View File

@ -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));
}
}

View File

@ -28,11 +28,22 @@ export const CustomHorizontalRule = Node.create<HorizontalRuleOptions>({
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() {

View File

@ -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();
},
},
];

View File

@ -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;