JSX
The JSX plugin allows you to process and associate custom editors with the JSX components in your markdown source - a capability enabled by MDX. The package includes a generic editor component, but you can also create your custom editors. The next example includes three JSX descriptors and an example of a custom editor that uses the NestedLexicalEditor component to edit the markdown contents of a JSX component.
The JSX syntax also supports {} as a way to embed JavaScript expressions in your markdown. Out of the box, the plugin will enable a simple inline editor for the expressions, too.
const jsxComponentDescriptors: JsxComponentDescriptor[] = [
{
name: 'MyLeaf',
kind: 'text', // 'text' for inline, 'flow' for block
// the source field is used to construct the import statement at the top of the markdown document.
// it won't be actually sourced.
source: './external',
// Used to construct the property popover of the generic editor
props: [
{ name: 'foo', type: 'string' },
{ name: 'bar', type: 'string' },
{ name: 'onClick', type: 'expression' }
],
// whether the component has children or not
hasChildren: true,
Editor: GenericJsxEditor
},
{
name: 'Marker',
kind: 'text',
source: './external',
props: [{ name: 'type', type: 'string' }],
hasChildren: false,
Editor: () => {
return (
<div style={{ border: '1px solid red', padding: 8, margin: 8, display: 'inline-block' }}>
<NestedLexicalEditor<MdxJsxTextElement>
getContent={(node) => node.children}
getUpdatedMdastNode={(mdastNode, children: any) => {
return { ...mdastNode, children }
}}
/>
</div>
)
}
},
{
name: 'BlockNode',
kind: 'flow',
source: './external',
props: [],
hasChildren: true,
Editor: GenericJsxEditor
}
]
// a toolbar button that will insert a JSX element into the editor.
const InsertMyLeaf = () => {
const insertJsx = usePublisher(insertJsx$)
return (
<Button
onClick={() =>
insertJsx({
name: 'MyLeaf',
kind: 'text',
props: { foo: 'bar', bar: 'baz', onClick: { type: 'expression', value: '() => console.log("Clicked")' } }
})
}
>
Leaf
</Button>
)
}
export const Example = () => {
return (
<MDXEditor
markdown={jsxMarkdown} // the contents of the file below
onChange={console.log}
plugins={[
jsxPlugin({ jsxComponentDescriptors }),
toolbarPlugin({
toolbarContents: () => (
<>
<InsertMyLeaf />
</>
)
})
]}
/>
)
}
import { MyLeaf, BlockNode } from './external';
A paragraph with inline jsx component <MyLeaf foo="bar" bar="baz" onClick={() => console.log("Clicked")}>Nested _markdown_</MyLeaf> more <Marker type="warning" />.
<BlockNode foo="fooValue">
Content *foo*
more Content
</BlockNode>
JSX kind mismatches
The MDX parser determines whether JSX is a text or flow node from its Markdown context. A component descriptor declares how MDXEditor intends to edit that component. These values can differ. For example, this valid one-line MDX is parsed as a text node even when Card.Header has a flow descriptor:
<Card.Header><Icon /> Text</Card.Header>
Use kindMismatchPolicy to select how jsxPlugin handles the mismatch:
jsxPlugin({
jsxComponentDescriptors,
kindMismatchPolicy: 'normalize'
})
sourceis the default. The parsed node type controls the nested editor, and MDXEditor does not rewrite the JSX kind.normalizemakes the descriptor authoritative when conversion is lossless. A standalone one-line text element declared asflowis converted to flow content and can be serialized in multiline form.errorrejects every mismatch through the editor'sonErrorcallback.
The policy applies only to nodes handled as descriptor-based JSX components. Built-in HTML elements keep their existing handling, including when the descriptor list contains a wildcard entry.
Normalization does not split surrounding paragraphs or flatten multiple blocks. Such conversions could change or discard content, so they produce a recoverable error. Write block components on separate lines when they occur next to other text.
GenericJsxEditor follows these policies. A custom JSX editor receives both mdastNode and descriptor; in source mode, it should derive its content model from mdastNode.type.
Types of properties
There are two types of properties - "textual" and "expressions" in JSX. You can define type in JsxComponentDescriptor. jsxPlugin will treat the value based on this setting. For example, this code:
const jsxComponentDescriptors: JsxComponentDescriptor[] = [
{
name: 'MyLeaf',
kind: 'text',
props: [
{ name: 'foo', type: 'string' } // Textual property type
],
hasChildren: true,
Editor: GenericJsxEditor
}
]
will produce component like the following:
<MyLeaf foo="bar">Some text...</MyLeaf>
While this descriptor:
const jsxComponentDescriptors: JsxComponentDescriptor[] = [
{
name: 'MyLeaf',
kind: 'text',
props: [
{ name: 'foo', type: 'expression' } // Expression property type
],
hasChildren: true,
Editor: GenericJsxEditor
}
]
will produce:
<MyLeaf foo={bar}>Some text...</MyLeaf>