@mdxeditor/editor
Version:
React component for rich text markdown editing
243 lines (242 loc) • 8.27 kB
JavaScript
import { $isElementNode } from "lexical";
import { toMarkdown } from "mdast-util-to-markdown";
import { isMdastHTMLNode } from "./plugins/core/MdastHTMLNode.js";
import { mergeStyleAttributes } from "./utils/mergeStyleAttributes.js";
function isParent(node) {
return node.children instanceof Array;
}
function exportLexicalTreeToMdast({
root,
visitors,
jsxComponentDescriptors,
jsxIsAvailable,
addImportStatements = true
}) {
let unistRoot = null;
const referredComponents = /* @__PURE__ */ new Set();
visitors = visitors.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
visit(root, null);
function registerReferredComponent(componentName) {
referredComponents.add(componentName);
}
function appendToParent(parentNode, node) {
if (unistRoot === null) {
unistRoot = node;
return unistRoot;
}
if (!isParent(parentNode)) {
throw new Error("Attempting to append children to a non-parent");
}
const siblings = parentNode.children;
const prevSibling = siblings.at(-1);
if (prevSibling) {
const joinVisitor = visitors.find((visitor) => {
var _a;
return (_a = visitor.shouldJoin) == null ? void 0 : _a.call(visitor, prevSibling, node);
});
if (joinVisitor) {
const joinedNode = joinVisitor.join(prevSibling, node);
siblings.splice(siblings.length - 1, 1, joinedNode);
return joinedNode;
}
}
siblings.push(node);
return node;
}
function visitChildren(lexicalNode, parentNode) {
lexicalNode.getChildren().forEach((lexicalChild) => {
visit(lexicalChild, parentNode);
});
}
function visit(lexicalNode, mdastParent) {
var _a;
const visitor = visitors.find((visitor2) => {
var _a2;
return (_a2 = visitor2.testLexicalNode) == null ? void 0 : _a2.call(visitor2, lexicalNode);
});
if (!visitor) {
throw new Error(`no lexical visitor found for ${lexicalNode.getType()}`, {
cause: lexicalNode
});
}
(_a = visitor.visitLexicalNode) == null ? void 0 : _a.call(visitor, {
lexicalNode,
mdastParent,
actions: {
addAndStepInto(type, props = {}, hasChildren = true) {
const newNode = {
type,
...props,
...hasChildren ? { children: [] } : {}
};
appendToParent(mdastParent, newNode);
if ($isElementNode(lexicalNode) && hasChildren) {
visitChildren(lexicalNode, newNode);
}
},
appendToParent,
visitChildren,
registerReferredComponent
}
});
}
if (unistRoot === null) {
throw new Error("traversal ended with no root element");
}
const importsMap = /* @__PURE__ */ new Map();
const defaultImportsMap = /* @__PURE__ */ new Map();
for (const componentName of referredComponents) {
const descriptor = jsxComponentDescriptors.find((descriptor2) => descriptor2.name === componentName);
if (!descriptor) {
throw new Error(`Component ${componentName} is used but not imported`);
}
if (!descriptor.source) {
continue;
}
if (descriptor.defaultExport) {
defaultImportsMap.set(componentName, descriptor.source);
} else {
const { source } = descriptor;
const existing = importsMap.get(source);
if (existing) {
existing.push(componentName);
} else {
importsMap.set(source, [componentName]);
}
}
}
const imports = Array.from(importsMap).map(([source, componentNames]) => {
return {
type: "mdxjsEsm",
value: `import { ${componentNames.join(", ")} } from '${source}'`
};
});
imports.push(
...Array.from(defaultImportsMap).map(([componentName, source]) => {
return {
type: "mdxjsEsm",
value: `import ${componentName} from '${source}'`
};
})
);
const typedRoot = unistRoot;
const frontmatter = typedRoot.children.find((child) => child.type === "yaml");
if (addImportStatements) {
if (frontmatter) {
typedRoot.children.splice(typedRoot.children.indexOf(frontmatter) + 1, 0, ...imports);
} else {
typedRoot.children.unshift(...imports);
}
}
fixWrappingWhitespace(typedRoot, []);
collapseNestedHtmlTags(typedRoot);
if (!jsxIsAvailable) {
convertUnderlineJsxToHtml(typedRoot);
}
return typedRoot;
}
function collapseNestedHtmlTags(node) {
if ("children" in node && node.children.length > 0) {
if (isMdastHTMLNode(node) && node.children.length === 1) {
const onlyChild = node.children[0];
if (onlyChild.type === "mdxJsxTextElement" && onlyChild.name === "span") {
onlyChild.attributes.forEach((attribute) => {
if (attribute.type === "mdxJsxAttribute") {
const parentAttribute = node.attributes.find((attr) => attr.type === "mdxJsxAttribute" && attr.name === attribute.name);
if (parentAttribute) {
if (attribute.name === "className") {
const mergedClassesSet = /* @__PURE__ */ new Set([
...parentAttribute.value.split(" "),
...attribute.value.split(" ")
]);
parentAttribute.value = Array.from(mergedClassesSet).join(" ");
} else if (attribute.name === "style") {
parentAttribute.value = mergeStyleAttributes(parentAttribute.value, attribute.value);
}
} else {
node.attributes.push(attribute);
}
}
});
node.children = onlyChild.children;
}
}
node.children.forEach((child) => {
collapseNestedHtmlTags(child);
});
}
}
function convertUnderlineJsxToHtml(node) {
if (Object.hasOwn(node, "children")) {
const nodeAsParent = node;
const newChildren = [];
nodeAsParent.children.forEach((child) => {
if (child.type === "mdxJsxTextElement" && child.name === "u") {
newChildren.push(...[{ type: "html", value: "<u>" }, ...child.children, { type: "html", value: "</u>" }]);
} else {
newChildren.push(child);
convertUnderlineJsxToHtml(child);
}
});
nodeAsParent.children = newChildren;
}
}
const TRAILING_WHITESPACE_REGEXP = /\s+$/;
const LEADING_WHITESPACE_REGEXP = /^\s+/;
function fixWrappingWhitespace(node, parentChain) {
if (node.type === "strong" || node.type === "emphasis") {
const lastChild = node.children.at(-1);
if ((lastChild == null ? void 0 : lastChild.type) === "text") {
const trailingWhitespace = lastChild.value.match(TRAILING_WHITESPACE_REGEXP);
if (trailingWhitespace) {
lastChild.value = lastChild.value.replace(TRAILING_WHITESPACE_REGEXP, "");
const parent = parentChain.at(-1);
if (parent) {
parent.children.splice(parent.children.indexOf(node) + 1, 0, {
type: "text",
value: trailingWhitespace[0]
});
fixWrappingWhitespace(parent, parentChain.slice(0, -1));
}
}
}
const firstChild = node.children.at(0);
if ((firstChild == null ? void 0 : firstChild.type) === "text") {
const leadingWhitespace = firstChild.value.match(LEADING_WHITESPACE_REGEXP);
if (leadingWhitespace) {
firstChild.value = firstChild.value.replace(LEADING_WHITESPACE_REGEXP, "");
const parent = parentChain.at(-1);
if (parent) {
parent.children.splice(parent.children.indexOf(node), 0, {
type: "text",
value: leadingWhitespace[0]
});
fixWrappingWhitespace(parent, parentChain.slice(0, -1));
}
}
}
}
if ("children" in node && node.children.length > 0) {
const nodeAsParent = node;
nodeAsParent.children.forEach((child) => {
fixWrappingWhitespace(child, [...parentChain, nodeAsParent]);
});
}
}
function exportMarkdownFromLexical({
root,
toMarkdownOptions,
toMarkdownExtensions,
visitors,
jsxComponentDescriptors,
jsxIsAvailable
}) {
return toMarkdown(exportLexicalTreeToMdast({ root, visitors, jsxComponentDescriptors, jsxIsAvailable }), {
extensions: toMarkdownExtensions,
...toMarkdownOptions
}) + "\n";
}
export {
exportLexicalTreeToMdast,
exportMarkdownFromLexical
};