This repository has been archived by the owner on Nov 7, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(editor): adds react node view to replacing Tiptap node view
- Loading branch information
Showing
47 changed files
with
855 additions
and
346 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { createContext, FC, PropsWithChildren, RefObject, useContext, useEffect, useMemo, useRef } from 'react' | ||
|
||
export interface NodeViewContextProps { | ||
setContentDOM?: (contentDOM: HTMLElement | null) => void | ||
} | ||
|
||
export const NodeViewContext = createContext<NodeViewContextProps>({}) | ||
|
||
export function useNodeContent<T extends HTMLElement>(): RefObject<T> { | ||
const { setContentDOM } = useContext(NodeViewContext) | ||
const ref = useRef<T>(null) | ||
|
||
useEffect(() => { | ||
setContentDOM?.(ref.current) | ||
}, [setContentDOM]) | ||
|
||
return ref | ||
} | ||
|
||
export interface NodeViewContainerProps extends PropsWithChildren, NodeViewContextProps {} | ||
|
||
export const NodeViewContainer: FC<NodeViewContainerProps> = ({ setContentDOM, children }) => { | ||
const contextValue = useMemo<NodeViewContextProps>(() => ({ setContentDOM }), [setContentDOM]) | ||
return <NodeViewContext.Provider value={contextValue}>{children}</NodeViewContext.Provider> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import { uuid } from '@mashcard/active-support' | ||
import { EditorView, NodeView } from 'prosemirror-view' | ||
import { ComponentType } from 'react' | ||
import { NodePortal } from '../NodePortal' | ||
import { flushSync } from 'react-dom' | ||
import { NodeViewContainer } from './NodeViewContainer' | ||
|
||
export { useNodeContent } from './NodeViewContainer' | ||
export { NodeViewContainer } | ||
|
||
/** | ||
* The store for nodePortals mutation | ||
*/ | ||
export interface NodePortalStore { | ||
/** | ||
* Adds or update node portal. | ||
*/ | ||
set: (nodePortal: NodePortal) => void | ||
/** | ||
* Removes a node portal by id. | ||
*/ | ||
remove: (id: string) => void | ||
} | ||
|
||
/** | ||
* A Prosemirror NodeView rendering view by react component. | ||
* @param component The react component for NodeView rendering. | ||
* @param editorView The editor view of current Prosemirror editor state | ||
* @param nodePortalStore The store for nodePortals mutation | ||
* @param asContainer Define the tag of react portal container, optional argument. | ||
*/ | ||
export class ReactNodeView<ComponentProps = {}> implements NodeView { | ||
/** | ||
* Unique id | ||
*/ | ||
public readonly id: string | ||
/** | ||
* The store for nodePortals mutation | ||
*/ | ||
public readonly nodePortalStore: NodePortalStore | ||
/** | ||
* The react component for NodeView rendering. | ||
*/ | ||
public readonly component: ComponentType<ComponentProps> | ||
/** | ||
* The dom container for NodeView | ||
*/ | ||
public readonly container: HTMLElement | ||
/** | ||
* The editor view of current Prosemirror editor state | ||
*/ | ||
public readonly editorView: EditorView | ||
|
||
public contentDOM: HTMLElement | null | ||
public dom: Element | ||
|
||
constructor({ | ||
component, | ||
componentProps, | ||
editorView, | ||
nodePortalStore, | ||
asContainer | ||
}: { | ||
component: ComponentType<ComponentProps> | ||
componentProps: ComponentProps | ||
editorView: EditorView | ||
nodePortalStore: NodePortalStore | ||
asContainer?: keyof HTMLElementTagNameMap | ||
}) { | ||
this.editorView = editorView | ||
this.container = document.createElement(asContainer ?? 'div') | ||
this.contentDOM = document.createElement('div') | ||
|
||
this.nodePortalStore = nodePortalStore | ||
this.id = uuid() | ||
this.component = component | ||
|
||
// renders react component synchronously when NodeView created. | ||
flushSync(() => { | ||
this.renderComponent(componentProps) | ||
}) | ||
|
||
this.dom = this.container | ||
} | ||
|
||
public destroy(): void { | ||
this.nodePortalStore.remove(this.id) | ||
this.contentDOM = null | ||
} | ||
|
||
/** | ||
* Used to set current contentDOM. Set it via useRef in react component for example. | ||
* @param contentDOM The DOM node that should hold the node's content. | ||
*/ | ||
protected readonly setContentDOM = (contentDOM: HTMLElement | null): void => { | ||
if (contentDOM) { | ||
contentDOM.append(...Array.from(this.contentDOM?.childNodes ?? [])) | ||
} | ||
this.contentDOM = contentDOM | ||
} | ||
|
||
/** | ||
* Renders the component for updating NodeView's view. | ||
*/ | ||
protected renderComponent(props: ComponentProps): void { | ||
this.nodePortalStore.set({ | ||
id: this.id, | ||
container: this.container, | ||
child: ( | ||
<NodeViewContainer setContentDOM={this.setContentDOM}> | ||
<this.component {...props} /> | ||
</NodeViewContainer> | ||
) | ||
}) | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
packages/editor/src/ReactNodeVew/__tests__/NodeViewContainer.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { render } from '@testing-library/react' | ||
import { FC } from 'react' | ||
import { NodeViewContainer, useNodeContent } from '../NodeViewContainer' | ||
|
||
describe('NodeViewContainer', () => { | ||
it('sets contentDOM correctly', () => { | ||
const setContentDOM = jest.fn() | ||
|
||
const TestComponent: FC = () => { | ||
const ref = useNodeContent<HTMLDivElement>() | ||
|
||
return <div ref={ref} /> | ||
} | ||
|
||
render( | ||
<NodeViewContainer setContentDOM={setContentDOM}> | ||
<TestComponent /> | ||
</NodeViewContainer> | ||
) | ||
|
||
expect(setContentDOM).toBeCalled() | ||
}) | ||
}) |
86 changes: 86 additions & 0 deletions
86
packages/editor/src/ReactNodeVew/__tests__/ReactNodeView.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import { render, screen } from '@testing-library/react' | ||
import { FC, ReactElement, ReactNode } from 'react' | ||
import { NodePortalStore, ReactNodeView, useNodeContent } from '../ReactNodeView' | ||
|
||
describe('ReactNodeView', () => { | ||
it('renders NodeView component correctly', () => { | ||
const componentProps = { | ||
text: 'text' | ||
} | ||
const component: FC<typeof componentProps> = props => <div>{props.text}</div> | ||
|
||
let child: ReactNode | ||
|
||
const nodePortalStore: NodePortalStore = { | ||
set(portal) { | ||
child = portal.child | ||
}, | ||
remove: jest.fn() | ||
} | ||
|
||
// eslint-disable-next-line no-new | ||
new ReactNodeView({ | ||
component, | ||
componentProps, | ||
editorView: {} as any, | ||
nodePortalStore | ||
}) | ||
|
||
render(child as ReactElement) | ||
|
||
expect(screen.getByText(componentProps.text)).toBeInTheDocument() | ||
}) | ||
|
||
it('renders NodeView component with NodeContent correctly', () => { | ||
const Component: FC = () => { | ||
const ref = useNodeContent<HTMLDivElement>() | ||
return <div ref={ref} /> | ||
} | ||
|
||
const text = 'text' | ||
|
||
let child: ReactNode | ||
|
||
const nodePortalStore: NodePortalStore = { | ||
set(portal) { | ||
child = portal.child | ||
}, | ||
remove: jest.fn() | ||
} | ||
|
||
const nodeView = new ReactNodeView({ | ||
component: Component, | ||
componentProps: {}, | ||
editorView: {} as any, | ||
nodePortalStore | ||
}) | ||
|
||
const textElement = document.createElement('span') | ||
textElement.innerHTML = text | ||
nodeView.contentDOM?.append(textElement) | ||
|
||
render(child as ReactElement) | ||
|
||
expect(screen.getByText(text)).toBeInTheDocument() | ||
}) | ||
|
||
it('destroys NodeView correctly', () => { | ||
const component: FC = () => <div /> | ||
|
||
const nodePortalStore: NodePortalStore = { | ||
set: jest.fn(), | ||
remove: jest.fn() | ||
} | ||
|
||
const nodeView = new ReactNodeView({ | ||
component, | ||
componentProps: {}, | ||
editorView: {} as any, | ||
nodePortalStore | ||
}) | ||
|
||
nodeView.destroy() | ||
|
||
expect(nodePortalStore.remove).toBeCalled() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './ReactNodeView' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from './prosemirror' | ||
export * from './NodePortal' | ||
export * from './ReactNodeVew' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.