Renato Atilio 381f6e64e8
FIX: click handler position on rich editor details node (#32268)
`pos` is the exact position of the click in the entire document

`nodePos` is the position of the clicked node itself

The idea is that we want the click to be the first position **within the
node**.

The previous code was checking for the first 2 positions of the entire
document.

I'd love to add a test for this, but it's very tricky.
2025-04-14 14:06:54 -03:00

90 lines
2.3 KiB
JavaScript

/** @type {RichEditorExtension} */
const extension = {
nodeSpec: {
details: {
allowGapCursor: true,
attrs: { open: { default: true } },
content: "summary block+",
group: "block",
draggable: true,
selectable: true,
defining: true,
isolating: true,
parseDOM: [{ tag: "details" }],
toDOM: (node) => ["details", { open: node.attrs.open || undefined }, 0],
},
summary: {
content: "inline*",
parseDOM: [{ tag: "summary" }],
toDOM: () => ["summary", 0],
},
},
parse: {
bbcode_open(state, token) {
if (token.tag === "details") {
state.openNode(state.schema.nodes.details, {
open: token.attrGet("open") !== null,
});
return true;
}
if (token.tag === "summary") {
state.openNode(state.schema.nodes.summary);
return true;
}
},
bbcode_close(state, token) {
if (token.tag === "details" || token.tag === "summary") {
state.closeNode();
return true;
}
},
},
serializeNode: {
details(state, node) {
state.renderContent(node);
state.write("[/details]\n\n");
},
summary(state, node, parent) {
let hasSummary = false;
// If the [details] tag has no summary.
if (node.content.childCount === 0) {
state.write("[details");
} else {
hasSummary = true;
state.write('[details="');
node.content.forEach(
(child) =>
child.text &&
state.text(child.text.replace(/"/g, "“"), state.inAutolink)
);
}
let finalState = `${parent.attrs.open ? " open" : ""}]\n`;
if (hasSummary) {
finalState = `"${finalState}`;
}
state.write(finalState);
},
},
plugins: {
props: {
handleClickOn(view, pos, node, nodePos) {
// if the click position in the document is not the first within the summary node
if (pos > nodePos + 1 || node.type.name !== "summary") {
return false;
}
const details = view.state.doc.nodeAt(nodePos - 1);
view.dispatch(
view.state.tr.setNodeMarkup(nodePos - 1, null, {
open: !details.attrs.open,
})
);
return true;
},
},
},
};
export default extension;