UNPKG

@lexical/markdown

Version:

This package contains Markdown helpers and functionality for Lexical.

158 lines (147 loc) 5.02 kB
/** * 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); }