@lexical/markdown
Version:
This package contains Markdown helpers and functionality for Lexical.
158 lines (147 loc) • 5.02 kB
text/typescript
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import type {TextFormatTransformersIndex} from './MarkdownImport';
import type {TextMatchTransformer} from './MarkdownTransformers';
import {$isTextNode, type LexicalNode, type TextNode} from 'lexical';
import {
findOutermostTextFormatTransformer,
importTextFormatTransformer,
} from './importTextFormatTransformer';
import {
findOutermostTextMatchTransformer,
importFoundTextMatchTransformer,
} from './importTextMatchTransformer';
import {unescapeText} from './utils';
/**
* Returns true if the node can contain transformable markdown.
* Code nodes cannot contain transformable markdown.
* For example, `code **bold**` should not be transformed to
* <code>code <strong>bold</strong></code>.
*/
export function canContainTransformableMarkdown(
node: LexicalNode | undefined,
): node is TextNode {
return $isTextNode(node) && !node.hasFormat('code');
}
/**
* Handles applying both text format and text match transformers.
* It finds the outermost text format or text match and applies it,
* then recursively calls itself to apply the next outermost transformer,
* until there are no more transformers to apply.
*/
export function importTextTransformers(
textNode: TextNode,
textFormatTransformersIndex: TextFormatTransformersIndex,
textMatchTransformers: TextMatchTransformer[],
) {
let foundTextFormat = findOutermostTextFormatTransformer(
textNode,
textFormatTransformersIndex,
);
let foundTextMatch = findOutermostTextMatchTransformer(
textNode,
textMatchTransformers,
);
if (foundTextFormat && foundTextMatch) {
if (foundTextFormat.isCodeSpan) {
// Code spans bind tighter than text-match transformers (e.g. equations
// or links). A code span must never be partially consumed by a text
// match, so prefer the code format unless the text match fully wraps it
// (in which case the code span is part of the match's raw content).
// https://github.com/facebook/lexical/issues/8687
if (
foundTextMatch.startIndex <= foundTextFormat.startIndex &&
foundTextMatch.endIndex >= foundTextFormat.endIndex
) {
// foundTextMatch wraps the code span - apply foundTextMatch
foundTextFormat = null;
} else {
// Apply the code span
foundTextMatch = null;
}
} else if (
// Find the outermost transformer
(foundTextFormat.startIndex <= foundTextMatch.startIndex &&
foundTextFormat.endIndex >= foundTextMatch.endIndex) ||
// foundTextMatch is not contained within foundTextFormat
foundTextMatch.startIndex > foundTextFormat.endIndex
) {
// foundTextFormat wraps foundTextMatch - apply foundTextFormat by setting foundTextMatch to null
foundTextMatch = null;
} else {
// foundTextMatch wraps foundTextFormat - apply foundTextMatch by setting foundTextFormat to null
foundTextFormat = null;
}
}
if (foundTextFormat) {
const result = importTextFormatTransformer(
textNode,
foundTextFormat.startIndex,
foundTextFormat.endIndex,
foundTextFormat.transformer,
foundTextFormat.match,
);
if (canContainTransformableMarkdown(result.nodeAfter)) {
importTextTransformers(
result.nodeAfter,
textFormatTransformersIndex,
textMatchTransformers,
);
}
if (canContainTransformableMarkdown(result.nodeBefore)) {
importTextTransformers(
result.nodeBefore,
textFormatTransformersIndex,
textMatchTransformers,
);
}
if (canContainTransformableMarkdown(result.transformedNode)) {
importTextTransformers(
result.transformedNode,
textFormatTransformersIndex,
textMatchTransformers,
);
}
} else if (foundTextMatch) {
const result = importFoundTextMatchTransformer(
textNode,
foundTextMatch.startIndex,
foundTextMatch.endIndex,
foundTextMatch.transformer,
foundTextMatch.match,
);
if (!result) {
return;
}
if (canContainTransformableMarkdown(result.nodeAfter)) {
importTextTransformers(
result.nodeAfter,
textFormatTransformersIndex,
textMatchTransformers,
);
}
if (canContainTransformableMarkdown(result.nodeBefore)) {
importTextTransformers(
result.nodeBefore,
textFormatTransformersIndex,
textMatchTransformers,
);
}
if (canContainTransformableMarkdown(result.transformedNode)) {
importTextTransformers(
result.transformedNode,
textFormatTransformersIndex,
textMatchTransformers,
);
}
}
// Handle escape characters
const textContent = textNode.getTextContent();
const unescapedText = unescapeText(textContent);
textNode.setTextContent(unescapedText);
}