Skip to content

Commit

Permalink
Implement view descriptors
Browse files Browse the repository at this point in the history
  • Loading branch information
smoores-dev committed Jul 28, 2023
1 parent fd47230 commit e5a820b
Show file tree
Hide file tree
Showing 8 changed files with 1,175 additions and 77 deletions.
35 changes: 23 additions & 12 deletions src/components/EditorView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ import React, {
useState,
} from "react";

import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js";
import { EditorViewContext } from "../contexts/EditorViewContext.js";
import { LayoutGroup } from "../contexts/LayoutGroup.js";
import {
NodeViewDescriptor,
NodeViewDescriptorsContext,
} from "../contexts/NodeViewPositionsContext.js";
import { NodeViewDescriptorsContext } from "../contexts/NodeViewPositionsContext.js";
import { ReactWidgetType } from "../decorations/ReactWidgetType.js";
import { ViewDesc } from "../descriptors/ViewDesc.js";
import { DOMNode } from "../dom.js";
import { useContentEditable } from "../hooks/useContentEditable.js";
import { useSyncSelection } from "../hooks/useSyncSelection.js";
Expand Down Expand Up @@ -106,9 +105,9 @@ export function EditorView(props: Props) {
defaultState ?? null
);

const posToDesc = useRef(new Map<number, NodeViewDescriptor>());
const posToDesc = useRef(new Map<number, ViewDesc>());
posToDesc.current = new Map();
const domToDesc = useRef(new Map<DOMNode, NodeViewDescriptor>());
const domToDesc = useRef(new Map<DOMNode, ViewDesc>());
domToDesc.current = new Map();

// We always set internalState above if there's no state prop
Expand Down Expand Up @@ -178,11 +177,19 @@ export function EditorView(props: Props) {
const textNodeStart = pos + offset + subOffset + 1;
const textNodeEnd = pos + offset + subOffset + 1 + textNode.nodeSize;
const marked = wrapInMarks(
<TextNodeWrapper pos={textNodeStart}>
{/* Text nodes always have text */}
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
{textNode.text!}
</TextNodeWrapper>,
<ChildDescriptorsContext.Consumer>
{(siblingDescriptors) => (
<TextNodeWrapper
siblingDescriptors={siblingDescriptors}
pos={textNodeStart}
node={textNode}
>
{/* Text nodes always have text */}
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
{textNode.text!}
</TextNodeWrapper>
)}
</ChildDescriptorsContext.Consumer>,
textNode.marks,
textNode.isInline
);
Expand Down Expand Up @@ -277,7 +284,11 @@ export function EditorView(props: Props) {
const marked = wrapInMarks(element, node.marks, node.isInline);

const decorated = wrapInDecorations(marked, nodeDecorations, false);
const wrapped = <NodeWrapper pos={pos}>{decorated}</NodeWrapper>;
const wrapped = (
<NodeWrapper pos={pos} node={node}>
{decorated}
</NodeWrapper>
);

const elements = [wrapped];

Expand Down
50 changes: 34 additions & 16 deletions src/components/NodeWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Node } from "prosemirror-model";
import { DecorationSet } from "prosemirror-view";
import React, {
Children,
ReactElement,
Expand All @@ -9,15 +11,11 @@ import React, {
useRef,
} from "react";

import {
NodeViewDescriptor,
NodeViewDescriptorsContext,
} from "../contexts/NodeViewPositionsContext.js";
import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js";
import { NodeViewDescriptorsContext } from "../contexts/NodeViewPositionsContext.js";
import { NodeViewDesc, ViewDesc } from "../descriptors/ViewDesc.js";

export function findChildDesc(
pos: number,
posToDesc: Map<number, NodeViewDescriptor>
) {
export function findChildDesc(pos: number, posToDesc: Map<number, ViewDesc>) {
const positions = Array.from(posToDesc.keys()).sort((a, b) => b - a);

let parentPos = null;
Expand All @@ -31,25 +29,41 @@ export function findChildDesc(
}

type NodeWrapperProps = {
node: Node;
children: ReactNode;
pos: number;
};
export function NodeWrapper({ children, pos }: NodeWrapperProps) {

export function NodeWrapper({ node, children, pos }: NodeWrapperProps) {
const { posToDesc, domToDesc } = useContext(NodeViewDescriptorsContext);
const siblingDescriptors = useContext(ChildDescriptorsContext);
const childDescriptors: ViewDesc[] = [];
const ref = useRef<Element | null>(null);

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

const childDesc = findChildDesc(pos, posToDesc);
const firstChildDesc = childDescriptors[0];

const desc: NodeViewDescriptor = {
pos,
dom: ref.current,
contentDOM: childDesc?.dom.parentNode ?? null,
};
const desc = new NodeViewDesc(
undefined,
node,
[],
DecorationSet.empty,
ref.current,
firstChildDesc?.dom.parentElement ?? null,
ref.current,
posToDesc,
domToDesc
);
desc.children = childDescriptors;
posToDesc.set(pos, desc);
domToDesc.set(ref.current, desc);
siblingDescriptors.push(desc);

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

const child = Children.only(children);
Expand All @@ -73,5 +87,9 @@ export function NodeWrapper({ children, pos }: NodeWrapperProps) {
},
});

return <>{clonedChild}</>;
return (
<ChildDescriptorsContext.Provider value={childDescriptors}>
{clonedChild}
</ChildDescriptorsContext.Provider>
);
}
38 changes: 27 additions & 11 deletions src/components/TextNodeWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { Node } from "prosemirror-model";
import { DecorationSet } from "prosemirror-view";
import { Component } from "react";
import { findDOMNode } from "react-dom";

import {
NodeViewDescriptor,
NodeViewDescriptorsContext,
NodeViewDescriptorsContextValue,
} from "../contexts/NodeViewPositionsContext.js";
import { TextViewDesc, ViewDesc } from "../descriptors/ViewDesc.js";

type TextNodeWrapperProps = {
node: Node;
children: string;
pos: number;
siblingDescriptors: ViewDesc[];
};
export class TextNodeWrapper extends Component<TextNodeWrapperProps> {
componentDidMount(): void {
Expand All @@ -21,13 +25,19 @@ export class TextNodeWrapper extends Component<TextNodeWrapperProps> {
const { posToDesc, domToDesc } = this
.context as NodeViewDescriptorsContextValue;

const desc: NodeViewDescriptor = {
pos: this.props.pos,
dom: textNode,
contentDOM: null,
};
const desc = new TextViewDesc(
undefined,
this.props.node,
[],
DecorationSet.empty,
textNode,
textNode,
posToDesc,
domToDesc
);
posToDesc.set(this.props.pos, desc);
domToDesc.set(textNode, desc);
this.props.siblingDescriptors.push(desc);
}

componentDidUpdate(): void {
Expand All @@ -39,13 +49,19 @@ export class TextNodeWrapper extends Component<TextNodeWrapperProps> {
const { posToDesc, domToDesc } = this
.context as NodeViewDescriptorsContextValue;

const desc: NodeViewDescriptor = {
pos: this.props.pos,
dom: textNode,
contentDOM: null,
};
const desc = new TextViewDesc(
undefined,
this.props.node,
[],
DecorationSet.empty,
textNode,
textNode,
posToDesc,
domToDesc
);
posToDesc.set(this.props.pos, desc);
domToDesc.set(textNode, desc);
this.props.siblingDescriptors.push(desc);
}

render() {
Expand Down
5 changes: 5 additions & 0 deletions src/contexts/ChildDescriptorsContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createContext } from "react";

import { ViewDesc } from "../descriptors/ViewDesc.js";

export const ChildDescriptorsContext = createContext<ViewDesc[]>([]);
11 changes: 3 additions & 8 deletions src/contexts/NodeViewPositionsContext.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import { createContext } from "react";

import { ViewDesc } from "../descriptors/ViewDesc.js";
import { DOMNode } from "../dom.js";

export type NodeViewDescriptor = {
pos: number;
dom: DOMNode;
contentDOM: DOMNode | null;
};

export type NodeViewDescriptorsContextValue = {
mount: HTMLDivElement | null;
domToDesc: Map<DOMNode, NodeViewDescriptor>;
posToDesc: Map<number, NodeViewDescriptor>;
domToDesc: Map<DOMNode, ViewDesc>;
posToDesc: Map<number, ViewDesc>;
};

export const NodeViewDescriptorsContext = createContext(
Expand Down
Loading

0 comments on commit e5a820b

Please sign in to comment.