Skip to content

Commit

Permalink
chore: decouple jsx node / component
Browse files Browse the repository at this point in the history
  • Loading branch information
petyosi committed Apr 12, 2023
1 parent 26f8213 commit b98fc47
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 141 deletions.
3 changes: 2 additions & 1 deletion src/content/theme.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EditorThemeClasses } from 'lexical'
import * as styles from './styles.css'

export const theme = {
export const theme: EditorThemeClasses = {
code: styles.codeBlock,
codeHighlight: {
atrule: styles.tokenAttr,
Expand Down
11 changes: 1 addition & 10 deletions src/nodes/Image/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,16 +130,7 @@ export class ImageNode extends DecoratorNode<JSX.Element> {
}

decorate(): JSX.Element {
return (
<img
title={this.__title}
src={this.__src}
alt={this.__altText}
onDoubleClick={() => {
this.setTitle('Hello')
}}
/>
)
return <img title={this.__title} src={this.__src} alt={this.__altText} />
}
}

Expand Down
144 changes: 15 additions & 129 deletions src/nodes/Jsx/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,10 @@ import {
SerializedRootNode,
Spread,
} from 'lexical'
import React from 'react'
import { MdxJsxAttribute } from 'mdast-util-mdx-jsx'
import { ReactComponent as SettingsIcon } from './icons/settings.svg'
// import { ReactComponent as ExtensionIcon } from './icons/extension.svg'
import * as styles from './styles.css'
import * as RadixPopover from '@radix-ui/react-popover'
import { PopoverContent, PopoverTrigger } from '../../ui/Popover/primitives'
import { LexicalNestedComposer } from '@lexical/react/LexicalNestedComposer'
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary'
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'
import { ContentEditable } from '@lexical/react/LexicalContentEditable'
import React from 'react'
import { contentTheme, useEmitterValues } from '../../'
import { useForm } from 'react-hook-form'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'

type JsxKind = 'text' | 'flow'
import { JsxEditorProps, JsxKind } from '../../types/NodeDecoratorsProps'

type updateFn = (node: LexicalNode) => void
export interface JsxPayload {
Expand Down Expand Up @@ -86,6 +74,11 @@ const EmptySerializedFlowEditorState = {
children: [],
} as SerializedRootNode

function InternalJsxEditor(props: JsxEditorProps) {
const [{ JsxEditor }] = useEmitterValues('nodeDecorators')
return <JsxEditor {...props} />
}

export class JsxNode extends DecoratorNode<JSX.Element> {
__kind: JsxKind
__name: string
Expand Down Expand Up @@ -205,125 +198,18 @@ export class JsxNode extends DecoratorNode<JSX.Element> {
}

decorate(): JSX.Element {
if (this.getKind() === 'flow') {
return (
<div className={styles.blockComponent}>
<div>{this.getName()}</div>
<LexicalNestedComposer initialEditor={this.__editor} initialTheme={contentTheme}>
<RichTextPlugin
contentEditable={<ContentEditable style={{ padding: 5, border: '1px solid red' }} />}
placeholder={<div>Type here..</div>}
ErrorBoundary={LexicalErrorBoundary}
/>
</LexicalNestedComposer>
</div>
)
}
return (
<span className={styles.inlineComponent}>
<span>
<InlineJsxComponent
attributes={this.getAttributes()}
componentName={this.getName()}
onSubmit={(attributeValues) => this.updateAttributes(attributeValues)}
/>
</span>
<span>
{this.getName()}

<LexicalNestedComposer initialEditor={this.__editor} initialTheme={contentTheme}>
<RichTextPlugin
contentEditable={<ContentEditable style={{ padding: 5, border: '1px solid red' }} />}
placeholder={<div>Type here..</div>}
ErrorBoundary={LexicalErrorBoundary}
/>
</LexicalNestedComposer>
</span>
</span>
<InternalJsxEditor
attributes={this.getAttributes()}
componentName={this.getName()}
kind={this.getKind()}
editor={this.__editor}
onSubmit={(attributeValues) => this.updateAttributes(attributeValues)}
theme={contentTheme}
/>
)
}
}
interface InlineJsxComponentProps {
attributes: MdxJsxAttribute[]
componentName: string
onSubmit: (values: Record<string, string>) => void
}

const InlineJsxComponent = ({ attributes, componentName, onSubmit }: InlineJsxComponentProps) => {
const [open, setOpen] = React.useState(false)

const decoratedOnSubmit = React.useCallback(
(values: Record<string, string>) => {
onSubmit(values)
setOpen(false)
},
[onSubmit]
)

return (
<RadixPopover.Root open={open} onOpenChange={(v) => setOpen(v)}>
<PopoverTrigger>
<SettingsIcon />
</PopoverTrigger>
<RadixPopover.Portal>
<PopoverContent>
<JsxPropertyPanel attributes={attributes} componentName={componentName} onSubmit={decoratedOnSubmit} />
</PopoverContent>
</RadixPopover.Portal>
</RadixPopover.Root>
)
}

interface JsxPropertyPanelProps {
componentName: string
attributes: Array<MdxJsxAttribute>
onSubmit: (values: Record<string, string>) => void
}

const JsxPropertyPanel: React.FC<JsxPropertyPanelProps> = ({ attributes, componentName, onSubmit }) => {
const [jsxComponentDescriptors] = useEmitterValues('jsxComponentDescriptors')
const descriptor = jsxComponentDescriptors.find((descriptor) => descriptor.name === componentName)!
const [editor] = useLexicalComposerContext()

const { register, handleSubmit } = useForm({
defaultValues: attributes.reduce((acc, attribute) => {
// TODO: handle mdxjs expressions
acc[attribute.name] = attribute.value as string
return acc
}, {} as Record<string, string>),
})

// iterate over the attributes and render a two column table with the name and value
return (
<form
onSubmit={handleSubmit((data) => {
editor.update(() => {
onSubmit(data)
})
})}
>
<table>
<thead>
<tr>
<th>Attribute</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{descriptor.props.map((propDescriptor) => (
<tr key={propDescriptor.name}>
<td> {propDescriptor.name} </td>
<td>
<input {...register(propDescriptor.name)} />
</td>
</tr>
))}
</tbody>
</table>
<button type="submit">Submit</button>
</form>
)
}

export function $createJsxNode(payload: JsxPayload): JsxNode {
return new JsxNode(payload)
Expand Down
4 changes: 3 additions & 1 deletion src/system/NodeDecorators.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { system } from '../gurx'
import React from 'react'
import { FrontmatterEditorProps } from '../types/NodeDecoratorsProps'
import { FrontmatterEditorProps, JsxEditorProps } from '../types/NodeDecoratorsProps'

function ComponentStub(componentName: string) {
return () => {
Expand All @@ -11,11 +11,13 @@ function ComponentStub(componentName: string) {

export interface NodeDecorators {
FrontmatterEditor: React.FC<FrontmatterEditorProps>
JsxEditor: React.FC<JsxEditorProps>
}

export const [NodeDecoratorsSystem, NodeDecoratorsSystemType] = system((r) => {
const nodeDecorators = r.node<NodeDecorators>({
FrontmatterEditor: ComponentStub('FrontmatterEditor'),
JsxEditor: ComponentStub('JsxEditor'),
})

return {
Expand Down
14 changes: 14 additions & 0 deletions src/types/NodeDecoratorsProps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
import { EditorThemeClasses, LexicalEditor } from 'lexical'
import { MdxJsxAttribute } from 'mdast-util-mdx-jsx'

export interface FrontmatterEditorProps {
yaml: string
onChange: (yaml: string) => void
}

export type JsxKind = 'text' | 'flow'

export interface JsxEditorProps {
kind: JsxKind
attributes: MdxJsxAttribute[]
componentName: string
onSubmit: (values: Record<string, string>) => void
theme: EditorThemeClasses
editor: LexicalEditor
}
127 changes: 127 additions & 0 deletions src/ui/NodeDecorators/JsxEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { MdxJsxAttribute } from 'mdast-util-mdx-jsx'
import React from 'react'
import { ReactComponent as SettingsIcon } from './icons/settings.svg'
// import { ReactComponent as ExtensionIcon } from './icons/extension.svg'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { ContentEditable } from '@lexical/react/LexicalContentEditable'
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary'
import { LexicalNestedComposer } from '@lexical/react/LexicalNestedComposer'
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'
import * as RadixPopover from '@radix-ui/react-popover'
import { useForm } from 'react-hook-form'
import { useEmitterValues } from '../../'
import { JsxEditorProps } from '../../types/NodeDecoratorsProps'
import { PopoverContent, PopoverTrigger } from '../../ui/Popover/primitives'

export function JsxEditor({ kind, attributes, componentName, editor, onSubmit, theme }: JsxEditorProps) {
if (kind === 'flow') {
return (
<div>
<div>{componentName}</div>
<LexicalNestedComposer initialEditor={editor} initialTheme={theme}>
<RichTextPlugin contentEditable={<ContentEditable />} placeholder={<div>Type here..</div>} ErrorBoundary={LexicalErrorBoundary} />
</LexicalNestedComposer>
</div>
)
}
return (
<span>
<span>
<InlineJsxComponent attributes={attributes} componentName={componentName} onSubmit={onSubmit} />
</span>
<span>
{componentName}
<LexicalNestedComposer initialEditor={editor} initialTheme={theme}>
<RichTextPlugin
contentEditable={<ContentEditable style={{ padding: 5, border: '1px solid red' }} />}
placeholder={<div>Type here..</div>}
ErrorBoundary={LexicalErrorBoundary}
/>
</LexicalNestedComposer>
</span>
</span>
)
}

interface InlineJsxComponentProps {
attributes: MdxJsxAttribute[]
componentName: string
onSubmit: (values: Record<string, string>) => void
}

const InlineJsxComponent = ({ attributes, componentName, onSubmit }: InlineJsxComponentProps) => {
const [open, setOpen] = React.useState(false)

const decoratedOnSubmit = React.useCallback(
(values: Record<string, string>) => {
onSubmit(values)
setOpen(false)
},
[onSubmit]
)

return (
<RadixPopover.Root open={open} onOpenChange={(v) => setOpen(v)}>
<PopoverTrigger>
<SettingsIcon />
</PopoverTrigger>
<RadixPopover.Portal>
<PopoverContent>
<JsxPropertyPanel attributes={attributes} componentName={componentName} onSubmit={decoratedOnSubmit} />
</PopoverContent>
</RadixPopover.Portal>
</RadixPopover.Root>
)
}

interface JsxPropertyPanelProps {
componentName: string
attributes: Array<MdxJsxAttribute>
onSubmit: (values: Record<string, string>) => void
}

const JsxPropertyPanel: React.FC<JsxPropertyPanelProps> = ({ attributes, componentName, onSubmit }) => {
const [jsxComponentDescriptors] = useEmitterValues('jsxComponentDescriptors')
const descriptor = jsxComponentDescriptors.find((descriptor) => descriptor.name === componentName)!
const [editor] = useLexicalComposerContext()

const { register, handleSubmit } = useForm({
defaultValues: attributes.reduce((acc, attribute) => {
// TODO: handle mdxjs expressions
acc[attribute.name] = attribute.value as string
return acc
}, {} as Record<string, string>),
})

// iterate over the attributes and render a two column table with the name and value
return (
<form
onSubmit={handleSubmit((data) => {
editor.update(() => {
onSubmit(data)
})
})}
>
<table>
<thead>
<tr>
<th>Attribute</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{descriptor.props.map((propDescriptor) => (
<tr key={propDescriptor.name}>
<td> {propDescriptor.name} </td>
<td>
<input {...register(propDescriptor.name)} />
</td>
</tr>
))}
</tbody>
</table>
<button type="submit">Submit</button>
</form>
)
}
File renamed without changes
File renamed without changes
2 changes: 2 additions & 0 deletions src/ui/Wrapper/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { SandpackConfigValue } from '../../system/Sandpack'
import * as styles from './styles.css'
import { NodeDecorators } from '../../system/NodeDecorators'
import { FrontmatterEditor } from '../NodeDecorators/FrontmatterEditor'
import { JsxEditor } from '../NodeDecorators/JsxEditor'

export function standardConfig(markdown: string) {
return {
Expand All @@ -47,6 +48,7 @@ interface WrappedEditorProps {

const nodeDecorators: NodeDecorators = {
FrontmatterEditor: FrontmatterEditor,
JsxEditor: JsxEditor,
}

const Debugger = () => {
Expand Down

0 comments on commit b98fc47

Please sign in to comment.