Skip to content

Commit

Permalink
Add a MarkView
Browse files Browse the repository at this point in the history
  • Loading branch information
smoores-dev committed Aug 1, 2023
1 parent e94cdd4 commit 9787afb
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 31 deletions.
2 changes: 1 addition & 1 deletion demo/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const editorState = EditorState.create({
schema,
doc: schema.nodes.doc.create({}, [
schema.nodes.paragraph.create({}, [
schema.text("This", [schema.marks.em.create()]),
schema.text("This", [schema.marks.em.create(), schema.marks.em.create()]),
schema.text(" is the first paragraph"),
]),
schema.nodes.paragraph.create(
Expand Down
24 changes: 24 additions & 0 deletions src/components/MarkView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Mark } from "prosemirror-model";
import React, { ReactNode, forwardRef } from "react";

import { OutputSpec } from "./OutputSpec.js";

type Props = {
mark: Mark;
children: ReactNode;
};

export const MarkView = forwardRef(function MarkView(
{ mark, children }: Props,
ref
) {
const outputSpec = mark.type.spec.toDOM?.(mark, true);
if (!outputSpec)
throw new Error(`Mark spec for ${mark.type.name} is missing toDOM`);

return (
<OutputSpec ref={ref} outputSpec={outputSpec}>
{children}
</OutputSpec>
);
});
47 changes: 25 additions & 22 deletions src/components/NodeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js"
import { NodeViewContext } from "../contexts/NodeViewContext.js";
import { NodeViewDesc, ViewDesc } from "../descriptors/ViewDesc.js";

import { MarkView } from "./MarkView.js";
import { NodeViewComponentProps } from "./NodeViewComponentProps.js";
import { OutputSpec } from "./OutputSpec.js";
import { TextNodeView } from "./TextNodeView.js";
Expand All @@ -29,10 +30,10 @@ export function NodeView({ node, pos }: Props) {
useContext(NodeViewContext);
const siblingDescriptors = useContext(ChildDescriptorsContext);
const childDescriptors: ViewDesc[] = [];
const ref = useRef<HTMLElement | null>(null);
const domRef = useRef<HTMLElement | null>(null);

useLayoutEffect(() => {
if (!ref.current) return;
if (!domRef.current) return;

const firstChildDesc = childDescriptors[0];

Expand All @@ -41,28 +42,28 @@ export function NodeView({ node, pos }: Props) {
node,
[],
DecorationSet.empty,
ref.current,
domRef.current,
firstChildDesc?.dom.parentElement ?? null,
ref.current,
domRef.current,
posToDesc,
domToDesc
);
desc.children = childDescriptors;
posToDesc.set(pos, desc);
domToDesc.set(ref.current, desc);
domToDesc.set(domRef.current, desc);
siblingDescriptors.push(desc);

for (const childDesc of childDescriptors) {
childDesc.parent = desc;
}
});

const children: ReactNode[] = [];
const content: ReactNode[] = [];
const innerPos = pos + 1;
node.content.forEach((childNode, offset) => {
const childPos = innerPos + offset;
if (childNode.isText) {
children.push(
content.push(
<ChildDescriptorsContext.Consumer key={childPos}>
{(siblingDescriptors) => (
<TextNodeView
Expand All @@ -74,26 +75,31 @@ export function NodeView({ node, pos }: Props) {
</ChildDescriptorsContext.Consumer>
);
} else {
children.push(
<NodeView key={childPos} node={childNode} pos={childPos} />
);
content.push(<NodeView key={childPos} node={childNode} pos={childPos} />);
}
});

if (!children.length) {
children.push(<TrailingHackView pos={innerPos} />);
if (!content.length) {
content.push(<TrailingHackView key={innerPos} pos={innerPos} />);
}

const children = (
<ChildDescriptorsContext.Provider value={childDescriptors}>
{content}
</ChildDescriptorsContext.Provider>
);

const Component:
| ForwardRefExoticComponent<
NodeViewComponentProps & RefAttributes<HTMLElement>
>
| undefined = nodeViews[node.type.name];

if (Component) {
return (
return node.marks.reduce(
(element, mark) => <MarkView mark={mark}>{element}</MarkView>,
<Component
ref={ref}
ref={domRef}
node={node}
pos={pos}
decorations={[]}
Expand All @@ -102,21 +108,18 @@ export function NodeView({ node, pos }: Props) {
state.selection.node === node
}
>
<ChildDescriptorsContext.Provider value={childDescriptors}>
{children}
</ChildDescriptorsContext.Provider>
{children}
</Component>
);
}

const outputSpec: DOMOutputSpec | undefined = node.type.spec.toDOM?.(node);

if (outputSpec) {
return (
<OutputSpec ref={ref} outputSpec={outputSpec}>
<ChildDescriptorsContext.Provider value={childDescriptors}>
{children}
</ChildDescriptorsContext.Provider>
return node.marks.reduce(
(element, mark) => <MarkView mark={mark}>{element}</MarkView>,
<OutputSpec ref={domRef} outputSpec={outputSpec}>
{children}
</OutputSpec>
);
}
Expand Down
13 changes: 5 additions & 8 deletions src/components/TextNodeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from "../contexts/NodeViewContext.js";
import { TextViewDesc, ViewDesc } from "../descriptors/ViewDesc.js";

import { OutputSpec } from "./OutputSpec.js";
import { MarkView } from "./MarkView.js";

type Props = {
node: Node;
Expand Down Expand Up @@ -75,13 +75,10 @@ export class TextNodeView extends Component<Props> {
}

render() {
return this.props.node.marks.reduce<JSX.Element>((children, mark) => {
const outputSpec = mark.type.spec.toDOM?.(mark, true);
if (!outputSpec)
throw new Error(`Mark spec for ${mark.type.name} is missing toDOM`);

return <OutputSpec outputSpec={outputSpec}>{children}</OutputSpec>;
}, <>{this.props.node.text}</>);
return this.props.node.marks.reduce<JSX.Element>(
(children, mark) => <MarkView mark={mark}>{children}</MarkView>,
<>{this.props.node.text}</>
);
}
}

Expand Down

0 comments on commit 9787afb

Please sign in to comment.