Skip to content

Commit

Permalink
[lexical-markdown] Feature: add ability to control finding the end of…
Browse files Browse the repository at this point in the history
… a node matched by TextMatchTransformer (#6681)
  • Loading branch information
AlessioGr authored Nov 5, 2024
1 parent f5ae852 commit 2c1a8f1
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 2 deletions.
9 changes: 8 additions & 1 deletion packages/lexical-markdown/src/MarkdownImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,14 @@ function importTextMatchTransformers(
}

const startIndex = match.index || 0;
const endIndex = startIndex + match[0].length;
const endIndex = transformer.getEndIndex
? transformer.getEndIndex(textNode, match)
: startIndex + match[0].length;

if (endIndex === false) {
continue;
}

let replaceNode, newTextNode;

if (startIndex === 0) {
Expand Down
9 changes: 9 additions & 0 deletions packages/lexical-markdown/src/MarkdownTransformers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,15 @@ export type TextMatchTransformer = Readonly<{
* Determines how the matched markdown text should be transformed into a node during the markdown import process
*/
replace?: (node: TextNode, match: RegExpMatchArray) => void;
/**
* For import operations, this function can be used to determine the end index of the match, after `importRegExp` has matched.
* Without this function, the end index will be determined by the length of the match from `importRegExp`. Manually determining the end index can be useful if
* the match from `importRegExp` is not the entire text content of the node. That way, `importRegExp` can be used to match only the start of the node, and `getEndIndex`
* can be used to match the end of the node.
*
* @returns The end index of the match, or false if the match was unsuccessful and a different transformer should be tried.
*/
getEndIndex?: (node: TextNode, match: RegExpMatchArray) => number | false;
/**
* Single character that allows the transformer to trigger when typed in the editor. This does not affect markdown imports outside of the markdown shortcut plugin.
* If the trigger is matched, the `regExp` will be used to match the text in the second step.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import {$createCodeNode, CodeNode} from '@lexical/code';
import {createHeadlessEditor} from '@lexical/headless';
import {$generateHtmlFromNodes, $generateNodesFromDOM} from '@lexical/html';
import {LinkNode} from '@lexical/link';
import {$createLinkNode, LinkNode} from '@lexical/link';
import {ListItemNode, ListNode} from '@lexical/list';
import {HeadingNode, QuoteNode} from '@lexical/rich-text';
import {$createTextNode, $getRoot, $insertNodes} from 'lexical';
Expand All @@ -28,6 +28,51 @@ import {
normalizeMarkdown,
} from '../../MarkdownTransformers';

const SIMPLE_INLINE_JSX_MATCHER: TextMatchTransformer = {
dependencies: [LinkNode],
getEndIndex(node, match) {
// Find the closing tag. Count the number of opening and closing tags to find the correct closing tag.
// For simplicity, this will only count the opening and closing tags without checking for "MyTag" specifically.
let openedSubStartMatches = 0;
const start = (match.index ?? 0) + match[0].length;
let endIndex = start;
const line = node.getTextContent();

for (let i = start; i < line.length; i++) {
const char = line[i];
if (char === '<') {
const nextChar = line[i + 1];
if (nextChar === '/') {
if (openedSubStartMatches === 0) {
endIndex = i + '</MyTag>'.length;
break;
}
openedSubStartMatches--;
} else {
openedSubStartMatches++;
}
}
}
return endIndex;
},
importRegExp: /<(MyTag)\s*>/,
regExp: /__ignore__/,
replace: (textNode, match) => {
const linkNode = $createLinkNode('simple-jsx');

const textStart = match[0].length + (match.index ?? 0);
const textEnd =
(match.index ?? 0) + textNode.getTextContent().length - '</MyTag>'.length;
const text = match.input?.slice(textStart, textEnd);

const linkTextNode = $createTextNode(text);
linkTextNode.setFormat(textNode.getFormat());
linkNode.append(linkTextNode);
textNode.replace(linkNode);
},
type: 'text-match',
};

// Matches html within a mdx file
const MDX_HTML_TRANSFORMER: MultilineElementTransformer = {
dependencies: [CodeNode],
Expand Down Expand Up @@ -461,6 +506,12 @@ describe('Markdown', () => {
md: '```ts\nCode\n```ts\nSub Code\n```\n```',
skipExport: true,
},
{
customTransformers: [SIMPLE_INLINE_JSX_MATCHER],
html: '<p><span style="white-space: pre-wrap;">Hello </span><a href="simple-jsx"><span style="white-space: pre-wrap;">One &lt;MyTag&gt;Two&lt;/MyTag&gt;</span></a><span style="white-space: pre-wrap;"> there</span></p>',
md: 'Hello <MyTag>One <MyTag>Two</MyTag></MyTag> there',
skipExport: true,
},
];

const HIGHLIGHT_TEXT_MATCH_IMPORT: TextMatchTransformer = {
Expand Down

0 comments on commit 2c1a8f1

Please sign in to comment.