Skip to content

Commit

Permalink
fix: correct nesting jsx behavior
Browse files Browse the repository at this point in the history
Fixes #203
  • Loading branch information
petyosi committed Nov 27, 2023
1 parent abb87e6 commit 9d33280
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 17 deletions.
116 changes: 116 additions & 0 deletions src/examples/nested-jsx-elements.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import React from 'react'
import { MdxJsxTextElement } from 'mdast-util-mdx'
import {
Button,
DiffSourceToggleWrapper,
JsxComponentDescriptor,
MDXEditor,
NestedLexicalEditor,
UndoRedo,
diffSourcePlugin,
jsxPlugin,
jsxPluginHooks,
toolbarPlugin
} from '../'
const jsxMarkdown = `<Grid foo="fooValue">
Content *foo*more Content
</Grid>`
const jsxComponentDescriptors: JsxComponentDescriptor[] = [
{
name: 'Card',
kind: 'text',
source: './external',
props: [],
hasChildren: true,
Editor: () => {
return <button>I am the card</button>
}
},
{
name: 'Grid',
kind: 'flow',
source: './external',
props: [],
hasChildren: true,
Editor: () => {
return (
<div style={{ border: '1px solid red' }}>
<NestedLexicalEditor<MdxJsxTextElement>
block
getContent={(node) => node.children}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getUpdatedMdastNode={(mdastNode, children: any) => {
return {
...mdastNode,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
children
}
}}
/>
</div>
)
}
}
]

const InsertCard = () => {
const insertJsx = jsxPluginHooks.usePublisher('insertJsx')
return (
<>
<Button
onClick={() =>
insertJsx({
name: 'Card',
kind: 'text',
props: {}
})
}
>
Card
</Button>
</>
)
}

const InsertGrid = () => {
const insertJsx = jsxPluginHooks.usePublisher('insertJsx')
return (
<>
<Button
onClick={() =>
insertJsx({
name: 'Grid',
kind: 'flow',
props: {}
})
}
>
Grid
</Button>
</>
)
}

export const Example = () => {
return (
<MDXEditor
markdown={jsxMarkdown}
onChange={console.log}
plugins={[
jsxPlugin({ jsxComponentDescriptors }),
diffSourcePlugin({ diffMarkdown: 'An older version', viewMode: 'rich-text' }),
toolbarPlugin({
toolbarContents: () => (
<>
<DiffSourceToggleWrapper>
<UndoRedo />
<InsertCard />
<InsertGrid />
</DiffSourceToggleWrapper>
</>
)
})
]}
/>
)
}
18 changes: 11 additions & 7 deletions src/exportMarkdownFromLexical.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export interface ExportLexicalTreeOptions {
visitors: LexicalVisitor[]
jsxComponentDescriptors: JsxComponentDescriptor[]
jsxIsAvailable: boolean
addImportStatements?: boolean
}

function isParent(node: unknown): node is Mdast.Parent {
Expand All @@ -116,7 +117,8 @@ export function exportLexicalTreeToMdast({
root,
visitors,
jsxComponentDescriptors,
jsxIsAvailable
jsxIsAvailable,
addImportStatements = true
}: ExportLexicalTreeOptions): Mdast.Root {
let unistRoot: Mdast.Root | null = null
const referredComponents = new Set<string>()
Expand Down Expand Up @@ -240,10 +242,12 @@ export function exportLexicalTreeToMdast({

const frontmatter = typedRoot.children.find((child) => child.type === 'yaml')

if (frontmatter) {
typedRoot.children.splice(typedRoot.children.indexOf(frontmatter) + 1, 0, ...imports)
} else {
typedRoot.children.unshift(...imports)
if (addImportStatements) {
if (frontmatter) {
typedRoot.children.splice(typedRoot.children.indexOf(frontmatter) + 1, 0, ...imports)
} else {
typedRoot.children.unshift(...imports)
}
}

fixWrappingWhitespace(typedRoot, [])
Expand All @@ -257,7 +261,7 @@ export function exportLexicalTreeToMdast({
}

function collapseNestedHtmlTags(node: Mdast.Parent | Mdast.Content) {
if ('children' in node) {
if ('children' in node && node.children) {
if (isMdastHTMLNode(node) && node.children.length === 1) {
const onlyChild = node.children[0]
if (onlyChild.type === 'mdxJsxTextElement' && onlyChild.name === 'span') {
Expand Down Expand Up @@ -339,7 +343,7 @@ function fixWrappingWhitespace(node: Mdast.Parent | Mdast.Content, parentChain:
}
}
}
if (Object.hasOwn(node, 'children')) {
if ('children' in node && node.children) {
const nodeAsParent = node as Mdast.Parent
nodeAsParent.children.forEach((child) => fixWrappingWhitespace(child, [...parentChain, nodeAsParent]))
}
Expand Down
8 changes: 7 additions & 1 deletion src/plugins/core/LexicalTextVisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ export function isMdastText(mdastNode: Mdast.Content): mdastNode is Mdast.Text {
return mdastNode.type === 'text'
}

const JOINABLE_TAGS = ['u', 'span']

export const LexicalTextVisitor: LexicalExportVisitor<TextNode, Mdast.Text> = {
shouldJoin: (prevNode, currentNode) => {
if (['text', 'emphasis', 'strong'].includes(prevNode.type)) {
return prevNode.type === currentNode.type
}

if (prevNode.type === 'mdxJsxTextElement' && (currentNode as unknown as MdxJsxTextElement).type === 'mdxJsxTextElement') {
if (
prevNode.type === 'mdxJsxTextElement' &&
(currentNode as unknown as MdxJsxTextElement).type === 'mdxJsxTextElement' &&
JOINABLE_TAGS.includes((currentNode as unknown as MdxJsxTextElement).name as string)
) {
const currentMdxNode: MdxJsxTextElement = currentNode as unknown as MdxJsxTextElement
return prevNode.name === currentMdxNode.name && JSON.stringify(prevNode.attributes) === JSON.stringify(currentMdxNode.attributes)
}
Expand Down
23 changes: 14 additions & 9 deletions src/plugins/core/NestedLexicalEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,18 @@ export function useMdastNodeUpdater<T extends Mdast.Content>() {
const { parentEditor, mdastNode, lexicalNode } = useNestedEditorContext<T>()

return function updateMdastNode(node: Partial<T>) {
parentEditor.update(() => {
$addUpdateTag('history-push')
const currentNode = $getNodeByKey(lexicalNode.getKey())
if (currentNode) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
currentNode.setMdastNode({ ...mdastNode, ...node })
}
})
parentEditor.update(
() => {
$addUpdateTag('history-push')
const currentNode = $getNodeByKey(lexicalNode.getKey())
if (currentNode) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
currentNode.setMdastNode({ ...mdastNode, ...node })
}
},
{ discrete: true }
)
parentEditor.dispatchCommand(NESTED_EDITOR_UPDATED_COMMAND, undefined)
}
}

Expand Down Expand Up @@ -219,7 +223,8 @@ export const NestedLexicalEditor = function <T extends Mdast.Content>(props: Nes
root: $getRoot(),
visitors: exportVisitors,
jsxComponentDescriptors,
jsxIsAvailable
jsxIsAvailable,
addImportStatements: false
})
const content: Mdast.Content[] = block ? mdast.children : (mdast.children[0] as Mdast.Paragraph)!.children
updateMdastNode(getUpdatedMdastNode(structuredClone(mdastNode) as any, content as any))
Expand Down
4 changes: 4 additions & 0 deletions src/plugins/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,10 @@ export const coreSystem = system((r) => {
setTimeout(() => node.select())
}
})

setTimeout(() => {
theEditor.dispatchCommand(NESTED_EDITOR_UPDATED_COMMAND, undefined)
})
}
}
})
Expand Down

0 comments on commit 9d33280

Please sign in to comment.