From 10027d5704995511c429b8a0a7d0dcd58aa16d64 Mon Sep 17 00:00:00 2001 From: Simon Bauchet Date: Mon, 22 Sep 2025 15:59:47 +0200 Subject: [PATCH 1/5] Add Indent Transformer This transformer allows to keep indentation when switching with markdown --- .../src/MarkdownTransformers.ts | 34 +++++++++++++++++++ .../__tests__/unit/LexicalMarkdown.test.ts | 16 +++++++++ packages/lexical-markdown/src/index.ts | 3 +- 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/packages/lexical-markdown/src/MarkdownTransformers.ts b/packages/lexical-markdown/src/MarkdownTransformers.ts index cf9c5eca8c9..1d90723219a 100644 --- a/packages/lexical-markdown/src/MarkdownTransformers.ts +++ b/packages/lexical-markdown/src/MarkdownTransformers.ts @@ -35,6 +35,8 @@ import { import { $createLineBreakNode, $createTextNode, + $isParagraphNode, + $isTextNode, ElementNode, Klass, LexicalNode, @@ -209,6 +211,7 @@ const CODE_SINGLE_LINE_REGEX = /^[ \t]*```[^`]+(?:(?:`{1,2}|`{4,})[^`]+)*```(?:[^`]|$)/; const TABLE_ROW_REG_EXP = /^(?:\|)(.+)(?:\|)\s?$/; const TABLE_ROW_DIVIDER_REG_EXP = /^(\| ?:?-*:? ?)+\|\s?$/; +const INDENT_REGEX = /^\t+/; const createBlockNode = ( createNode: (match: Array) => ElementNode, @@ -595,6 +598,37 @@ export const LINK: TextMatchTransformer = { type: 'text-match', }; +export const INDENT: TextMatchTransformer = { + dependencies: [], + export: (node, exportChildren, exportFormat) => { + const parentNode = node.getParent(); + const textContent = node.getTextContent(); + if ( + !$isParagraphNode(parentNode) || + !$isTextNode(node) || + parentNode.getFirstChild() !== node + ) { + return null; + } + const indent = parentNode.getIndent(); + const textWithFormat = exportFormat(node, textContent); + return textWithFormat ? '\t'.repeat(indent) + textWithFormat : null; + }, + importRegExp: INDENT_REGEX, + regExp: INDENT_REGEX, + replace: (textNode, match) => { + const [indents] = match; + const parentNode = textNode.getParent(); + if (!parentNode || !$isParagraphNode(parentNode)) { + return; + } + parentNode.setIndent(indents.length); + textNode.setTextContent(textNode.getTextContent().replace(/^\t+/, '')); + return textNode; + }, + type: 'text-match', +}; + export function normalizeMarkdown( input: string, shouldMergeAdjacentLines = false, diff --git a/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts b/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts index ca1b5a9175d..f364bc9db67 100644 --- a/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts +++ b/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts @@ -669,6 +669,22 @@ describe('Markdown', () => { html: '

[helloworld

', md: '[h[ello](https://lexical.dev)[world](https://lexical.dev)', }, + { + html: '

Hello Word

', + md: '\tHello Word', + }, + { + html: '

Hello Word

', + md: '\t\tHello Word', + }, + { + html: '

Hello\t Word

', + md: 'Hello\t Word', + }, + { + html: '

Hello Word

', + md: '\t*Hello* Word', + }, ]; const HIGHLIGHT_TEXT_MATCH_IMPORT: TextMatchTransformer = { diff --git a/packages/lexical-markdown/src/index.ts b/packages/lexical-markdown/src/index.ts index 06bd85dd492..9437a32c02d 100644 --- a/packages/lexical-markdown/src/index.ts +++ b/packages/lexical-markdown/src/index.ts @@ -27,6 +27,7 @@ import { CODE, HEADING, HIGHLIGHT, + INDENT, INLINE_CODE, ITALIC_STAR, ITALIC_UNDERSCORE, @@ -65,7 +66,7 @@ const TEXT_FORMAT_TRANSFORMERS: Array = [ STRIKETHROUGH, ]; -const TEXT_MATCH_TRANSFORMERS: Array = [LINK]; +const TEXT_MATCH_TRANSFORMERS: Array = [INDENT, LINK]; const TRANSFORMERS: Array = [ ...ELEMENT_TRANSFORMERS, From c02ef17ad51c2a1297b4342e106a3749f07421ae Mon Sep 17 00:00:00 2001 From: Simon Bauchet Date: Mon, 22 Sep 2025 17:12:42 +0200 Subject: [PATCH 2/5] Remove unused import --- packages/lexical-markdown/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/lexical-markdown/src/index.ts b/packages/lexical-markdown/src/index.ts index 0725c8765a4..3b9270d0e22 100644 --- a/packages/lexical-markdown/src/index.ts +++ b/packages/lexical-markdown/src/index.ts @@ -28,7 +28,6 @@ import { ELEMENT_TRANSFORMERS, HEADING, HIGHLIGHT, - INDENT, INLINE_CODE, ITALIC_STAR, ITALIC_UNDERSCORE, From e2a67120c6b8521841c85ad29a1c72bf389fb784 Mon Sep 17 00:00:00 2001 From: Simon Bauchet Date: Tue, 23 Sep 2025 09:29:16 +0200 Subject: [PATCH 3/5] Fix prettier error --- packages/lexical-markdown/src/MarkdownTransformers.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/lexical-markdown/src/MarkdownTransformers.ts b/packages/lexical-markdown/src/MarkdownTransformers.ts index 3633a547b66..29b2af7e536 100644 --- a/packages/lexical-markdown/src/MarkdownTransformers.ts +++ b/packages/lexical-markdown/src/MarkdownTransformers.ts @@ -655,7 +655,10 @@ export const TEXT_FORMAT_TRANSFORMERS: Array = [ STRIKETHROUGH, ]; -export const TEXT_MATCH_TRANSFORMERS: Array = [INDENT, LINK]; +export const TEXT_MATCH_TRANSFORMERS: Array = [ + INDENT, + LINK, +]; export const TRANSFORMERS: Array = [ ...ELEMENT_TRANSFORMERS, From 8cec7f1910546bca0e5b66805eaf280aff0afbb9 Mon Sep 17 00:00:00 2001 From: Simon Bauchet Date: Tue, 4 Nov 2025 10:54:52 +0100 Subject: [PATCH 4/5] Separate INDENT transformer from default one - Remove INDENT from TEXT_MATCH_TRANSFORMERS - Export INDENT and use it in playground Signed-off-by: Simon Bauchet --- packages/lexical-markdown/flow/LexicalMarkdown.js.flow | 1 + packages/lexical-markdown/src/MarkdownTransformers.ts | 5 +---- .../src/__tests__/unit/LexicalMarkdown.test.ts | 5 +++++ packages/lexical-markdown/src/index.ts | 2 ++ .../src/plugins/MarkdownTransformers/index.ts | 2 ++ 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/lexical-markdown/flow/LexicalMarkdown.js.flow b/packages/lexical-markdown/flow/LexicalMarkdown.js.flow index 973fab93726..63627c9b4f7 100644 --- a/packages/lexical-markdown/flow/LexicalMarkdown.js.flow +++ b/packages/lexical-markdown/flow/LexicalMarkdown.js.flow @@ -127,6 +127,7 @@ declare export var CHECK_LIST: ElementTransformer; declare export var ORDERED_LIST: ElementTransformer; declare export var LINK: TextMatchTransformer; +declare export var INDENT: TextMatchTransformer; declare export var TRANSFORMERS: Array; declare export var ELEMENT_TRANSFORMERS: Array; diff --git a/packages/lexical-markdown/src/MarkdownTransformers.ts b/packages/lexical-markdown/src/MarkdownTransformers.ts index 8ce13e6022f..2cd44792cd0 100644 --- a/packages/lexical-markdown/src/MarkdownTransformers.ts +++ b/packages/lexical-markdown/src/MarkdownTransformers.ts @@ -676,10 +676,7 @@ export const TEXT_FORMAT_TRANSFORMERS: Array = [ STRIKETHROUGH, ]; -export const TEXT_MATCH_TRANSFORMERS: Array = [ - INDENT, - LINK, -]; +export const TEXT_MATCH_TRANSFORMERS: Array = [LINK]; export const TRANSFORMERS: Array = [ ...ELEMENT_TRANSFORMERS, diff --git a/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts b/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts index a27c51fa982..d2947730505 100644 --- a/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts +++ b/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts @@ -32,6 +32,7 @@ import {describe, expect, it} from 'vitest'; import { $convertFromMarkdownString, $convertToMarkdownString, + INDENT, LINK, registerMarkdownShortcuts, TextMatchTransformer, @@ -694,18 +695,22 @@ describe('Markdown', () => { md: '[h[ello](https://lexical.dev)[world](https://lexical.dev)', }, { + customTransformers: [INDENT], html: '

Hello Word

', md: '\tHello Word', }, { + customTransformers: [INDENT], html: '

Hello Word

', md: '\t\tHello Word', }, { + customTransformers: [INDENT], html: '

Hello\t Word

', md: 'Hello\t Word', }, { + customTransformers: [INDENT], html: '

Hello Word

', md: '\t*Hello* Word', }, diff --git a/packages/lexical-markdown/src/index.ts b/packages/lexical-markdown/src/index.ts index 3b9270d0e22..f0e16aa0c31 100644 --- a/packages/lexical-markdown/src/index.ts +++ b/packages/lexical-markdown/src/index.ts @@ -28,6 +28,7 @@ import { ELEMENT_TRANSFORMERS, HEADING, HIGHLIGHT, + INDENT, INLINE_CODE, ITALIC_STAR, ITALIC_UNDERSCORE, @@ -94,6 +95,7 @@ export { type ElementTransformer, HEADING, HIGHLIGHT, + INDENT, INLINE_CODE, ITALIC_STAR, ITALIC_UNDERSCORE, diff --git a/packages/lexical-playground/src/plugins/MarkdownTransformers/index.ts b/packages/lexical-playground/src/plugins/MarkdownTransformers/index.ts index 7498485217a..9f17da36919 100644 --- a/packages/lexical-playground/src/plugins/MarkdownTransformers/index.ts +++ b/packages/lexical-playground/src/plugins/MarkdownTransformers/index.ts @@ -12,6 +12,7 @@ import { CHECK_LIST, ELEMENT_TRANSFORMERS, ElementTransformer, + INDENT, MULTILINE_ELEMENT_TRANSFORMERS, TEXT_FORMAT_TRANSFORMERS, TEXT_MATCH_TRANSFORMERS, @@ -315,6 +316,7 @@ export const PLAYGROUND_TRANSFORMERS: Array = [ EQUATION, TWEET, CHECK_LIST, + INDENT, ...ELEMENT_TRANSFORMERS, ...MULTILINE_ELEMENT_TRANSFORMERS, ...TEXT_FORMAT_TRANSFORMERS, From 6bec51ffcee89c3f698149a7da0e811d0f87c41d Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 21 Nov 2025 15:59:49 +0100 Subject: [PATCH 5/5] Refactor key comparison Co-authored-by: Bob Ippolito --- packages/lexical-markdown/src/MarkdownTransformers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lexical-markdown/src/MarkdownTransformers.ts b/packages/lexical-markdown/src/MarkdownTransformers.ts index b9b5a8ef000..3ca35990c1b 100644 --- a/packages/lexical-markdown/src/MarkdownTransformers.ts +++ b/packages/lexical-markdown/src/MarkdownTransformers.ts @@ -633,7 +633,7 @@ export const INDENT: TextMatchTransformer = { if ( !$isParagraphNode(parentNode) || !$isTextNode(node) || - parentNode.getFirstChild() !== node + !node.is(parentNode.getFirstChild()) ) { return null; }