/** * MDAST <-> Lexical visitors for the structural plan components. * * Children are visited normally in both directions, so their content is made of * ordinary Lexical nodes and ordinary MDAST nodes — nothing is snapshotted into * a property, which is what keeps concurrent editing inside these containers * working. */ import { $createCalloutNode, $createTabNode, $createTabsNode, $isCalloutNode, $isTabNode, $isTabsNode, } from "./containers"; import { attribute, identity, isFlow, PRIORITY } from "./shared"; import type { LexicalExportVisitor, MdastImportVisitor } from "@mdxeditor/editor"; import type { LexicalNode } from "lexical"; import type { MdxJsxFlowElement } from "mdast-util-mdx-jsx"; import type { CalloutNode, CalloutType, TabNode, TabsNode } from "./containers"; type Importer = MdastImportVisitor; type Exporter = LexicalExportVisitor; /** Import a flow component as an element node and visit its children into it. */ function importer(name: string, create: (node: MdxJsxFlowElement) => LexicalNode): Importer { return { testNode: isFlow(name), visitNode({ mdastNode, actions }) { actions.addAndStepInto(create(mdastNode)); }, priority: PRIORITY, }; } /** Export an element node as a flow component and visit its children into it. */ function exporter( name: string, test: (node: LexicalNode | null | undefined) => node is T, attrs: (node: T) => ReturnType, ): Exporter { return { testLexicalNode: (node): node is T => test(node), visitLexicalNode({ lexicalNode, actions }) { actions.addAndStepInto("mdxJsxFlowElement", { name, attributes: attrs(lexicalNode) }); }, priority: PRIORITY, }; } export const MdastTabsVisitor = importer( "Tabs", node => $createTabsNode(attribute(node, "id") ?? ""), ); export const MdastTabVisitor = importer( "Tab", node => $createTabNode(attribute(node, "id") ?? "", attribute(node, "label") ?? ""), ); export const MdastCalloutVisitor = importer("Callout", node => $createCalloutNode( attribute(node, "id") ?? "", (attribute(node, "type") ?? "note") as CalloutType, attribute(node, "title") ?? "", )); export const LexicalTabsVisitor: Exporter = exporter( "Tabs", $isTabsNode, node => identity(node.getId()), ); export const LexicalTabVisitor: Exporter = exporter( "Tab", $isTabNode, node => identity(node.getId(), { label: node.getLabel() }), ); export const LexicalCalloutVisitor: Exporter = exporter( "Callout", $isCalloutNode, node => identity(node.getId(), { type: node.getCalloutType(), title: node.getTitle() }), ); export const CONTAINER_IMPORT_VISITORS = [ MdastTabsVisitor, MdastTabVisitor, MdastCalloutVisitor, ]; export const CONTAINER_EXPORT_VISITORS = [ LexicalTabsVisitor, LexicalTabVisitor, LexicalCalloutVisitor, ];