forked from github/plane
[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:
parent
b9314889d7
commit
480aa906de
@ -236,7 +236,7 @@ div[data-type="horizontalRule"] {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
||||||
& > div {
|
& > div {
|
||||||
border-bottom: 1px solid rgb(var(--color-border-200));
|
border-bottom: 2px solid rgb(var(--color-border-200));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,11 +28,22 @@ export const CustomHorizontalRule = Node.create<HorizontalRuleOptions>({
|
|||||||
group: "block",
|
group: "block",
|
||||||
|
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [{ tag: "hr" }];
|
return [
|
||||||
|
{
|
||||||
|
tag: `div[data-type="${this.name}"]`,
|
||||||
|
},
|
||||||
|
{ tag: "hr" },
|
||||||
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
renderHTML({ HTMLAttributes }) {
|
renderHTML({ HTMLAttributes }) {
|
||||||
return ["hr", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
return [
|
||||||
|
"div",
|
||||||
|
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
||||||
|
"data-type": this.name,
|
||||||
|
}),
|
||||||
|
["div", {}],
|
||||||
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
addCommands() {
|
addCommands() {
|
||||||
|
@ -101,21 +101,44 @@ export default function BlockMenu(props: BlockMenuProps) {
|
|||||||
label: "Duplicate",
|
label: "Duplicate",
|
||||||
isDisabled: editor.state.selection.content().content.firstChild?.type.name === "image",
|
isDisabled: editor.state.selection.content().content.firstChild?.type.name === "image",
|
||||||
onClick: (e) => {
|
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.preventDefault();
|
||||||
e.stopPropagation();
|
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();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -58,10 +58,10 @@ function nodeDOMAtCoords(coords: { x: number; y: number }) {
|
|||||||
[
|
[
|
||||||
"li",
|
"li",
|
||||||
"p:not(:first-child)",
|
"p:not(:first-child)",
|
||||||
"pre",
|
".code-block",
|
||||||
"blockquote",
|
"blockquote",
|
||||||
"h1, h2, h3",
|
"h1, h2, h3",
|
||||||
".table-wrapper",
|
"table",
|
||||||
"[data-type=horizontalRule]",
|
"[data-type=horizontalRule]",
|
||||||
].join(", ")
|
].join(", ")
|
||||||
)
|
)
|
||||||
@ -77,10 +77,25 @@ function nodePosAtDOM(node: Element, view: EditorView, options: DragHandleOption
|
|||||||
})?.inside;
|
})?.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) {
|
function calcNodePos(pos: number, view: EditorView) {
|
||||||
const $pos = view.state.doc.resolve(pos);
|
const maxPos = view.state.doc.content.size;
|
||||||
if ($pos.depth > 1) return $pos.before($pos.depth);
|
const safePos = Math.max(0, Math.min(pos, maxPos));
|
||||||
return pos;
|
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) {
|
function DragHandle(options: DragHandleOptions) {
|
||||||
@ -156,6 +171,20 @@ function DragHandle(options: DragHandleOptions) {
|
|||||||
|
|
||||||
if (!(node instanceof Element)) return;
|
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);
|
let nodePos = nodePosAtDOM(node, view, options);
|
||||||
|
|
||||||
if (nodePos === null || nodePos === undefined) return;
|
if (nodePos === null || nodePos === undefined) return;
|
||||||
@ -244,11 +273,13 @@ function DragHandle(options: DragHandleOptions) {
|
|||||||
|
|
||||||
rect.top += (lineHeight - 20) / 2;
|
rect.top += (lineHeight - 20) / 2;
|
||||||
rect.top += paddingTop;
|
rect.top += paddingTop;
|
||||||
|
|
||||||
// Li markers
|
// Li markers
|
||||||
if (node.matches("ul:not([data-type=taskList]) li, ol li")) {
|
if (node.matches("ul:not([data-type=taskList]) li, ol li")) {
|
||||||
rect.top += 4;
|
rect.top += 4;
|
||||||
rect.left -= 18;
|
rect.left -= 18;
|
||||||
}
|
}
|
||||||
|
|
||||||
rect.width = options.dragHandleWidth;
|
rect.width = options.dragHandleWidth;
|
||||||
|
|
||||||
if (!dragHandleElement) return;
|
if (!dragHandleElement) return;
|
||||||
|
Loading…
Reference in New Issue
Block a user