Skip to content

Commit

Permalink
feat: support MDX flow expressions (#396)
Browse files Browse the repository at this point in the history
* Add handlers for mdxFlowExpressions

* chore: adjust isInline

I'm not certain on its purpose right now, but I think lexical calls it
at some point.

---------

Co-authored-by: Stanislav Kubik <[email protected]>
Co-authored-by: Petyo Ivanov <[email protected]>
  • Loading branch information
3 people authored Mar 25, 2024
1 parent 87adbef commit b6e2a19
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 53 deletions.
2 changes: 1 addition & 1 deletion src/examples/jsx.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export const JsxExpression = () => {
<div>
<MDXEditor
onChange={(e) => console.log(e)}
markdown={`Hello {1+1} after the expression`}
markdown={`Hello {1+1} after the expression \n\n{2+2}\n`}
plugins={[headingsPlugin(), jsxPlugin({ jsxComponentDescriptors: [] })]}
/>
</div>
Expand Down
5 changes: 3 additions & 2 deletions src/plugins/core/NestedLexicalEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ import { mergeRegister } from '@lexical/utils'
import { VoidEmitter } from '../../utils/voidEmitter'
import { isPartOftheEditorUI } from '../../utils/isPartOftheEditorUI'
import { useCellValues, usePublisher } from '@mdxeditor/gurx'
import { DirectiveNode } from '../directives'

/**
* The value of the {@link NestedEditorsContext} React context.
Expand Down Expand Up @@ -321,7 +320,9 @@ export const NestedLexicalEditor = function <T extends Mdast.RootContent>(props:
return (
<LexicalNestedComposer initialEditor={editor}>
<RichTextPlugin
contentEditable={<ContentEditable {...contentEditableProps} className={classNames(styles.nestedEditor, contentEditableProps?.className)} />}
contentEditable={
<ContentEditable {...contentEditableProps} className={classNames(styles.nestedEditor, contentEditableProps?.className)} />
}
placeholder={null}
ErrorBoundary={LexicalErrorBoundary}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,25 @@ import {
SerializedLexicalNode,
Spread
} from 'lexical'
import { MdxFlowExpression, MdxTextExpression } from 'mdast-util-mdx'

import lexicalThemeStyles from '../../styles/lexical-theme.module.css'
import styles from '../../styles/ui.module.css'

/**
* The type of MDX expression.
* @internal
*/
type MdxExpressionType = MdxTextExpression['type'] | MdxFlowExpression['type']

/**
* A serialized representation of a {@link GenericHTMLNode}.
* @group HTML
*/
export type SerializedLexicalMdxTextExpressionNode = Spread<
export type SerializedLexicalMdxExpressionNode = Spread<
{
type: 'mdx-text-expression'
type: 'mdx-expression'
mdastType: MdxExpressionType
value: string
version: 1
},
Expand All @@ -32,37 +40,45 @@ export type SerializedLexicalMdxTextExpressionNode = Spread<
* The generic HTML node is used as a "fallback" for HTML elements that are not explicitly supported by the editor.
* @group HTML
*/
export class LexicalMdxTextExpressionNode extends DecoratorNode<JSX.Element> {
export class LexicalMdxExpressionNode extends DecoratorNode<JSX.Element> {
/** @internal */
__value: string

/** @internal */
__mdastType: MdxExpressionType

/** @internal */
static getType(): string {
return 'mdx-text-expression'
return 'mdx-expression'
}

/** @internal */
static clone(node: LexicalMdxTextExpressionNode): LexicalMdxTextExpressionNode {
return new LexicalMdxTextExpressionNode(node.__value, node.__key)
static clone(node: LexicalMdxExpressionNode): LexicalMdxExpressionNode {
return new LexicalMdxExpressionNode(node.__value, node.__mdastType, node.__key)
}

/**
* Constructs a new {@link GenericHTMLNode} with the specified MDAST HTML node as the object to edit.
*/
constructor(value: string, key?: NodeKey) {
constructor(value: string, mdastType: MdxExpressionType, key?: NodeKey) {
super(key)
this.__value = value
this.__mdastType = mdastType
}

getValue(): string {
return this.__value
}

getMdastType(): MdxExpressionType {
return this.__mdastType
}

// View

createDOM(): HTMLElement {
const element = document.createElement('span')
element.classList.add(lexicalThemeStyles.mdxTextExpression)
element.classList.add(lexicalThemeStyles.mdxExpression)
return element
}

Expand Down Expand Up @@ -98,15 +114,16 @@ export class LexicalMdxTextExpressionNode extends DecoratorNode<JSX.Element> {
}
}

static importJSON(serializedNode: SerializedLexicalMdxTextExpressionNode): LexicalMdxTextExpressionNode {
return $createLexicalMdxTextExpressionNode(serializedNode.value)
static importJSON(serializedNode: SerializedLexicalMdxExpressionNode): LexicalMdxExpressionNode {
return $createLexicalMdxExpressionNode(serializedNode.value, serializedNode.mdastType)
}

exportJSON(): SerializedLexicalMdxTextExpressionNode {
exportJSON(): SerializedLexicalMdxExpressionNode {
return {
...super.exportJSON(),
value: this.getValue(),
type: 'mdx-text-expression',
mdastType: this.getMdastType(),
type: 'mdx-expression',
version: 1
}
}
Expand Down Expand Up @@ -136,7 +153,7 @@ export class LexicalMdxTextExpressionNode extends DecoratorNode<JSX.Element> {
}

isInline(): boolean {
return true
return this.__mdastType === 'mdxTextExpression'
}

decorate(editor: LexicalEditor) {
Expand Down Expand Up @@ -178,14 +195,14 @@ export class LexicalMdxTextExpressionNode extends DecoratorNode<JSX.Element> {
* Creates a new {@link GenericHTMLNode} with the specified MDAST HTML node as the object to edit.
* @group HTML
*/
export function $createLexicalMdxTextExpressionNode(value: string): LexicalMdxTextExpressionNode {
return $applyNodeReplacement(new LexicalMdxTextExpressionNode(value))
export function $createLexicalMdxExpressionNode(value: string, type: MdxExpressionType): LexicalMdxExpressionNode {
return $applyNodeReplacement(new LexicalMdxExpressionNode(value, type))
}

/**
* Determines if the specified node is a {@link GenericHTMLNode}.
* @group HTML
*/
export function $isLexicalMdxTextExpressionNode(node: LexicalNode | null | undefined): node is LexicalMdxTextExpressionNode {
return node instanceof LexicalMdxTextExpressionNode
export function $isLexicalMdxExpressionNode(node: LexicalNode | null | undefined): node is LexicalMdxExpressionNode {
return node instanceof LexicalMdxExpressionNode
}
15 changes: 15 additions & 0 deletions src/plugins/jsx/LexicalMdxExpressionVisitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { MdxFlowExpression, MdxTextExpression } from 'mdast-util-mdx'
import { LexicalExportVisitor } from '../../exportMarkdownFromLexical'
import { $isLexicalMdxExpressionNode, LexicalMdxExpressionNode } from './LexicalMdxExpressionNode'

export const LexicalMdxExpressionVisitor: LexicalExportVisitor<LexicalMdxExpressionNode, MdxTextExpression> = {
testLexicalNode: $isLexicalMdxExpressionNode,
visitLexicalNode({ actions, mdastParent, lexicalNode }) {
const mdastNode = {
type: lexicalNode.getMdastType(),
value: lexicalNode.getValue()
} as const satisfies MdxTextExpression | MdxFlowExpression

actions.appendToParent(mdastParent, mdastNode)
}
}
14 changes: 0 additions & 14 deletions src/plugins/jsx/LexicalMdxTextExpressionVisitor.ts

This file was deleted.

12 changes: 12 additions & 0 deletions src/plugins/jsx/MdastMdxExpressionVisitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ElementNode } from 'lexical'
import { MdxFlowExpression, MdxTextExpression } from 'mdast-util-mdx'
import { MdastImportVisitor } from '../../importMarkdownToLexical'
import { $createLexicalMdxExpressionNode } from './LexicalMdxExpressionNode'

export const MdastMdxExpressionVisitor: MdastImportVisitor<MdxTextExpression | MdxFlowExpression> = {
testNode: (node) => node.type === 'mdxTextExpression' || node.type === 'mdxFlowExpression',
visitNode({ lexicalParent, mdastNode }) {
;(lexicalParent as ElementNode).append($createLexicalMdxExpressionNode(mdastNode.value, mdastNode.type))
},
priority: -200
}
12 changes: 0 additions & 12 deletions src/plugins/jsx/MdastMdxTextExpressionVisitor.ts

This file was deleted.

12 changes: 6 additions & 6 deletions src/plugins/jsx/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import { MdastMdxJsxElementVisitor } from './MdastMdxJsxElementVisitor'
import * as Mdast from 'mdast'
import { Signal, map } from '@mdxeditor/gurx'
import { realmPlugin } from '../../RealmWithPlugins'
import { MdastMdxTextExpressionVisitor } from './MdastMdxTextExpressionVisitor'
import { LexicalMdxTextExpressionNode } from './LexicalMdxTextExpressionNode'
import { LexicalMdxTextExpressionVisitor } from './LexicalMdxTextExpressionVisitor'
import { MdastMdxExpressionVisitor } from './MdastMdxExpressionVisitor'
import { LexicalMdxExpressionNode } from './LexicalMdxExpressionNode'
import { LexicalMdxExpressionVisitor } from './LexicalMdxExpressionVisitor'
import { GenericJsxEditor } from '../../jsx-editors/GenericJsxEditor'

/**
Expand Down Expand Up @@ -225,11 +225,11 @@ export const jsxPlugin = realmPlugin<JsxPluginParams>({
[jsxIsAvailable$]: true,
[addMdastExtension$]: mdxFromMarkdown(),
[addSyntaxExtension$]: mdxjs(),
[addImportVisitor$]: [MdastMdxJsxElementVisitor, MdastMdxJsEsmVisitor, MdastMdxTextExpressionVisitor],
[addImportVisitor$]: [MdastMdxJsxElementVisitor, MdastMdxJsEsmVisitor, MdastMdxExpressionVisitor],

// export
[addLexicalNode$]: [LexicalJsxNode, LexicalMdxTextExpressionNode],
[addExportVisitor$]: [LexicalJsxVisitor, LexicalMdxTextExpressionVisitor],
[addLexicalNode$]: [LexicalJsxNode, LexicalMdxExpressionNode],
[addExportVisitor$]: [LexicalJsxVisitor, LexicalMdxExpressionVisitor],
[addToMarkdownExtension$]: mdxToMarkdown(),
[jsxComponentDescriptors$]: getDescriptors(params)
})
Expand Down
2 changes: 1 addition & 1 deletion src/styles/lexical-theme.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@
--admonitionBg: var(--admonitionNoteBg);
}

.mdxTextExpression {
.mdxExpression {
font-family: var(--font-mono);
font-size: 84%;
color: var(--accentText);
Expand Down

0 comments on commit b6e2a19

Please sign in to comment.