@mkljczk/lexical-remark
Version:
This package contains Markdown helpers and functionality for Lexical using remark-parse.
116 lines (115 loc) • 4.76 kB
JavaScript
/* eslint-disable @typescript-eslint/no-use-before-define */
import { $createTextNode, $createParagraphNode } from 'lexical';
import { fromMarkdown } from 'mdast-util-from-markdown';
import { $createCollapsibleContainerNode, $isCollapsibleContainerNode, } from '../../extensions/collapsible/container/node.js';
import { $createCollapsibleContentNode, $isCollapsibleContentNode } from '../../extensions/collapsible/content/node.js';
import { $createCollapsibleTitleNode } from '../../extensions/collapsible/title/node.js';
import { Parser } from '../parser.js';
import { dummyRoot } from './root.js';
const detailsAloneRegex = /^\s*<details\s*(?:open(?:=['"](?:true|false)['"])?)?>\s*$/s;
const restAloneRegex = /^\s*<summary>(?<title>.*?)<\/summary>\s*(?<content>.*?)\n*?(?:(?<closingTag><\/details>)(?<rest>.*)|$)/s;
const detailsRegex = /^\s*<details\s*(?:open(?:=['"](?:true|false)['"])?)?>\s*<summary>(?<title>.*?)<\/summary>\s*(?<content>.*?)\n*?(?:(?<closingTag><\/details>)(?<rest>.*)|$)/s;
const closingTagRegex = /^\n*<\/details>(?<rest>.*)/s;
const processDetailsAlone = (parser, match) => {
const lexicalNode = $createCollapsibleContainerNode(false);
parser.push(lexicalNode);
};
const processRestAlone = (parser, match) => {
// istanbul ignore if: This is a TS guard
if (!match?.groups) {
return;
}
// title can be const, but the other variables need to be let
// eslint-disable-next-line prefer-const
let { closingTag, content, rest, title } = match.groups;
const contentMatch = content.match(detailsRegex);
const restMatch = rest?.match(closingTagRegex);
if (contentMatch && !contentMatch?.groups?.closingTag && closingTag) {
// Nested content has no closing tag, we need to grab the one from the parent content.
// The parent content is now no longer self closing
content = content + '\n\n' + closingTag;
closingTag = '';
if (restMatch) {
closingTag = restMatch[0];
rest = restMatch.groups?.rest ?? '';
}
}
const summaryText = title;
const titleNode = $createCollapsibleTitleNode();
titleNode.append($createTextNode(summaryText));
parser.append(titleNode);
const contentNode = $createCollapsibleContentNode();
if (closingTag) {
// Nested content has a closing tag or no details tag at all
const contentTree = fromMarkdown(content.trim());
const nestedParser = new Parser();
const nestedContent = dummyRoot(contentTree, nestedParser).getChildren();
if (nestedContent && Array.isArray(nestedContent)) {
contentNode.append(...nestedContent);
}
parser.append(contentNode);
const containerNode = parser.pop();
parser.append(containerNode);
if (rest) {
html({
type: 'html',
value: rest,
}, parser);
}
}
else {
parser.push(contentNode);
if (content) {
const contentTree = fromMarkdown(content.trim());
const nestedParser = new Parser();
const nestedRoot = dummyRoot(contentTree, nestedParser);
const nestedContent = nestedRoot.getChildren();
if (nestedRoot.getStack()) {
parser.stack.push(...nestedRoot.getStack());
}
if (nestedContent && Array.isArray(nestedContent)) {
contentNode.append(...nestedContent);
}
}
}
};
const processAll = (parser, match) => {
processDetailsAlone(parser, match);
processRestAlone(parser, match);
};
const processClose = (parser, match) => {
const contentNode = parser.pop($isCollapsibleContentNode);
parser.append(contentNode);
const detailsNode = parser.pop($isCollapsibleContainerNode);
parser.append(detailsNode);
if (match.groups?.rest) {
html({
type: 'html',
value: match.groups.rest,
}, parser);
}
return;
};
export const html = (node, parser) => {
const allMatch = node.value.match(detailsRegex);
if (allMatch?.groups) {
processAll(parser, allMatch);
return;
}
const detailsAloneMatch = node.value.match(detailsAloneRegex);
if (detailsAloneMatch) {
processDetailsAlone(parser, detailsAloneMatch);
return;
}
const restAloneMatch = node.value.match(restAloneRegex);
if (restAloneMatch?.groups) {
processRestAlone(parser, restAloneMatch);
return;
}
const closingTagMatch = node.value.match(closingTagRegex);
if (closingTagMatch) {
processClose(parser, closingTagMatch);
return;
}
parser.append($createParagraphNode().append($createTextNode(node.value)));
};