From 49252b3a4f5b7ccf103aefc0f1ee91dea14afded Mon Sep 17 00:00:00 2001 From: Shane Friedman Date: Mon, 7 Aug 2023 13:57:26 -0400 Subject: [PATCH] Fix mark and inline decoration rendering --- demo/main.tsx | 22 ++++- src/hooks/useChildNodeViews.tsx | 169 ++++++++++++++++++-------------- 2 files changed, 112 insertions(+), 79 deletions(-) diff --git a/demo/main.tsx b/demo/main.tsx index 3cf44a63..8e110ecd 100644 --- a/demo/main.tsx +++ b/demo/main.tsx @@ -6,6 +6,7 @@ import { Decoration, DecorationSet } from "prosemirror-view"; import "prosemirror-view/style/prosemirror.css"; import React, { DetailedHTMLProps, + ForwardedRef, HTMLAttributes, Ref, forwardRef, @@ -51,6 +52,11 @@ const schema = new Schema({ return ["em", 0]; }, }, + strong: { + toDOM() { + return ["strong", 0]; + }, + }, }, }); @@ -58,8 +64,12 @@ const editorState = EditorState.create({ schema, doc: schema.nodes.doc.create({}, [ schema.nodes.paragraph.create({}, [ - schema.text("This", [schema.marks.em.create()]), - schema.text(" is the first paragraph"), + schema.text("This ", [schema.marks.em.create()]), + schema.text("is", [ + schema.marks.em.create(), + schema.marks.strong.create(), + ]), + schema.text(" the first paragraph"), ]), schema.nodes.paragraph.create( {}, @@ -97,9 +107,13 @@ const Paragraph = forwardRef(function Paragraph( ); }); -function TestWidget() { +const TestWidget = forwardRef(function TestWidget( + _props, + ref: ForwardedRef +) { return ( ); -} +}); const viewPlugin = new Plugin({ view(view) { diff --git a/src/hooks/useChildNodeViews.tsx b/src/hooks/useChildNodeViews.tsx index 73be709d..07b0b5d3 100644 --- a/src/hooks/useChildNodeViews.tsx +++ b/src/hooks/useChildNodeViews.tsx @@ -17,29 +17,27 @@ import { type ChildNode = { node: Node; + marks: readonly Mark[]; innerDeco: DecorationSourceInternal; offset: number; }; -type ChildrenNodeViewProps = { - outerDeco: readonly DecorationInternal[]; +type SharedMarksProps = { sharedMarks: readonly Mark[]; + outerDeco: readonly DecorationInternal[]; innerPos: number; nodes: ChildNode[]; }; -function ChildrenNodeView({ +function SharedMarks({ outerDeco, sharedMarks, innerPos, nodes, -}: ChildrenNodeViewProps) { - const childElements: JSX.Element[] = []; - let queuedSharedMarks: readonly Mark[] = []; - let queuedNodes: ChildNode[] = []; +}: SharedMarksProps) { if (nodes.length === 1) { - const { node, offset, innerDeco } = nodes[0]!; - + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const { node, marks, offset, innerDeco } = nodes[0]!; const childPos = innerPos + offset; const nodeElement = node.isText ? ( @@ -61,54 +59,78 @@ function ChildrenNodeView({ /> ); - const uniqueMarks: Mark[] = node.marks.filter( - (mark) => !mark.isInSet(sharedMarks) - ); - const markedElement = uniqueMarks.reduce( - (element, mark) => {element}, - nodeElement - ); - - childElements.push(cloneElement(markedElement, { key: childPos })); - } else { - nodes.forEach((childNode) => { - const uniqueMarks = childNode.node.marks.filter( - (mark) => !mark.isInSet(sharedMarks) - ); - const sharedUniqueMarks = uniqueMarks.filter((mark, index) => - queuedSharedMarks[index]?.eq(mark) + const markedElement = sharedMarks + .concat(marks) + .reduce( + (element, mark) => {element}, + nodeElement ); - if (sharedUniqueMarks.length) { - queuedSharedMarks = sharedUniqueMarks; - queuedNodes.push(childNode); - } else { - if (queuedNodes.length) { - childElements.push( - - ); - } - queuedNodes = [childNode]; - queuedSharedMarks = childNode.node.marks; + + return cloneElement(markedElement, { key: childPos }); + } + + const childElements: JSX.Element[] = []; + + let queuedSharedMarks: readonly Mark[] = []; + let queuedChildNodes: ChildNode[] = []; + + for (const childNode of nodes) { + const filteredMarks = childNode.marks.filter((mark, index) => + queuedSharedMarks[index]?.eq(mark) + ); + if (filteredMarks.length) { + queuedSharedMarks = filteredMarks; + queuedChildNodes.push(childNode); + } else { + if (queuedChildNodes.length) { + childElements.push( + ({ + ...childNode, + marks: childNode.marks.slice(queuedSharedMarks.length), + }))} + /> + ); } - }); + queuedSharedMarks = childNode.marks; + queuedChildNodes = [childNode]; + } } - if (queuedNodes.length) { + if (queuedChildNodes.length) { childElements.push( - ({ + ...childNode, + marks: childNode.marks.slice(queuedSharedMarks.length), + }))} /> ); } + return sharedMarks.reduce( + (element, mark) => {element}, + <>{childElements} + ); +} + +type OuterDecoViewProps = { + outerDeco: readonly DecorationInternal[]; + innerPos: number; + nodes: ChildNode[]; +}; + +function OuterDecoView({ outerDeco, innerPos, nodes }: OuterDecoViewProps) { return outerDeco.reduce( (element, deco) => { const { @@ -118,10 +140,9 @@ function ChildrenNodeView({ ...attrs } = (deco.type as NonWidgetType).attrs; - if (nodeName) { + if (nodeName || nodes[0]?.node.isText) { return createElement( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - nodeName!, + nodeName ?? "span", { className, ...attrs, @@ -129,21 +150,18 @@ function ChildrenNodeView({ element ); } - if (Array.isArray(element)) { - return ( - <>{element.map((el) => cloneElement(el, { className, ...attrs }))} - ); - } return cloneElement(element, { className, ...attrs, }); }, - sharedMarks.reduce( - (element, mark) => {element}, - <>{childElements} - ) + ); } @@ -156,7 +174,6 @@ export function useChildNodeViews( const innerPos = pos + 1; let queuedOuterDeco: readonly DecorationInternal[] = []; - let queuedSharedMarks: readonly Mark[] = []; let queuedChildNodes: ChildNode[] = []; iterDeco( @@ -165,10 +182,10 @@ export function useChildNodeViews( (widget, offset, index) => { if (queuedChildNodes.length) { children.push( - @@ -180,41 +197,43 @@ export function useChildNodeViews( widget={widget as ReactWidgetDecoration} /> ); + queuedChildNodes = []; + queuedOuterDeco = []; }, (childNode, outerDeco, innerDeco, offset) => { - const sharedMarks = childNode.marks.filter((mark, index) => - queuedSharedMarks[index]?.eq(mark) - ); if (!sameOuterDeco(queuedOuterDeco, outerDeco)) { if (queuedChildNodes.length) { children.push( - ); } queuedOuterDeco = outerDeco; - queuedChildNodes = [{ node: childNode, innerDeco, offset }]; - queuedSharedMarks = childNode.marks; + queuedChildNodes = [ + { node: childNode, marks: childNode.marks, innerDeco, offset }, + ]; } else { - queuedChildNodes.push({ node: childNode, innerDeco, offset }); - queuedSharedMarks = sharedMarks; + queuedChildNodes.push({ + node: childNode, + marks: childNode.marks, + innerDeco, + offset, + }); } } ); if (queuedChildNodes.length) { children.push( -