Skip to content

Commit 0252681

Browse files
authored
fix(richtext-lexical): combine 2 normalizeMarkdown implementations and fix code block regex (#10470)
This should fix it #10387 I don't know why we had 2 different copies of normalizeMarkdown. Also, the most up-to-date one still had a bug where lines were considered as if they were inside codeblocks when they weren't. How I tested that it works: 1. I copied the `normalizeMarkdown` implementation from this PR into the website repo, and made sure it is called before the conversion to editorState. 2. In the admin panel, sync docs. 3. In the admin panel, refresh mdx to lexical (new button, below sync docs). 4. Look for the examples from bug #10387 and verify that they have been resolved. An extra pair of eyes would be nice to make sure I'm not getting confused with the imports.
1 parent 690e99f commit 0252681

File tree

8 files changed

+44
-132
lines changed

8 files changed

+44
-132
lines changed

packages/richtext-lexical/src/features/blocks/client/markdownTransformer.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import { createHeadlessEditor } from '@lexical/headless'
66
import type { Transformer } from '../../../packages/@lexical/markdown/index.js'
77
import type { MultilineElementTransformer } from '../../../packages/@lexical/markdown/MarkdownTransformers.js'
88

9-
import { $convertToMarkdownString } from '../../../packages/@lexical/markdown/index.js'
9+
import {
10+
$convertFromMarkdownString,
11+
$convertToMarkdownString,
12+
} from '../../../packages/@lexical/markdown/index.js'
1013
import { extractPropsFromJSXPropsString } from '../../../utilities/jsx/extractPropsFromJSXPropsString.js'
1114
import { propsToJSXString } from '../../../utilities/jsx/jsx.js'
12-
import { $convertFromMarkdownString } from '../../../utilities/jsx/lexicalMarkdownCopy.js'
1315
import { $createBlockNode, $isBlockNode, BlockNode } from './nodes/BlocksNode.js'
1416

1517
function createTagRegexes(tagName: string) {

packages/richtext-lexical/src/features/blocks/server/markdownTransformer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ import type { NodeWithHooks } from '../../typesServer.js'
88

99
import { getEnabledNodesFromServerNodes } from '../../../lexical/nodes/index.js'
1010
import {
11+
$convertFromMarkdownString,
1112
$convertToMarkdownString,
1213
type MultilineElementTransformer,
1314
type TextMatchTransformer,
1415
type Transformer,
1516
} from '../../../packages/@lexical/markdown/index.js'
1617
import { extractPropsFromJSXPropsString } from '../../../utilities/jsx/extractPropsFromJSXPropsString.js'
1718
import { propsToJSXString } from '../../../utilities/jsx/jsx.js'
18-
import { $convertFromMarkdownString } from '../../../utilities/jsx/lexicalMarkdownCopy.js'
1919
import { linesFromStartToContentAndPropsString } from './linesFromMatchToContentAndPropsString.js'
2020
import { $createServerBlockNode, $isServerBlockNode, ServerBlockNode } from './nodes/BlocksNode.js'
2121
import {

packages/richtext-lexical/src/features/experimental_table/markdownTransformer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ import {
1515
import { $isParagraphNode, $isTextNode } from 'lexical'
1616

1717
import {
18+
$convertFromMarkdownString,
1819
$convertToMarkdownString,
1920
type ElementTransformer,
2021
type Transformer,
2122
} from '../../packages/@lexical/markdown/index.js'
22-
import { $convertFromMarkdownString } from '../../utilities/jsx/lexicalMarkdownCopy.js'
2323

2424
// Very primitive table setup
2525
const TABLE_ROW_REG_EXP = /^\|(.+)\|\s?$/

packages/richtext-lexical/src/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,20 +1010,20 @@ export { sanitizeUrl, validateUrl } from './lexical/utils/url.js'
10101010

10111011
export type * from './nodeTypes.js'
10121012

1013-
export { defaultRichTextValue } from './populateGraphQL/defaultValue.js'
1013+
export { $convertFromMarkdownString } from './packages/@lexical/markdown/index.js'
10141014

1015+
export { defaultRichTextValue } from './populateGraphQL/defaultValue.js'
10151016
export { populate } from './populateGraphQL/populate.js'
10161017
export type { LexicalEditorProps, LexicalRichTextAdapter } from './types.js'
1018+
10171019
export { createServerFeature } from './utilities/createServerFeature.js'
10181020

10191021
export type { FieldsDrawerProps } from './utilities/fieldsDrawer/Drawer.js'
1020-
10211022
export { extractPropsFromJSXPropsString } from './utilities/jsx/extractPropsFromJSXPropsString.js'
10221023
export {
10231024
extractFrontmatter,
10241025
frontmatterToObject,
10251026
objectToFrontmatter,
10261027
propsToJSXString,
10271028
} from './utilities/jsx/jsx.js'
1028-
export { $convertFromMarkdownString } from './utilities/jsx/lexicalMarkdownCopy.js'
10291029
export { upgradeLexicalData } from './utilities/upgradeLexicalData/index.js'

packages/richtext-lexical/src/packages/@lexical/markdown/MarkdownTransformers.ts

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -185,16 +185,19 @@ export type TextMatchTransformer = Readonly<{
185185
type: 'text-match'
186186
}>
187187

188+
const EMPTY_OR_WHITESPACE_ONLY = /^[\t ]*$/
188189
const ORDERED_LIST_REGEX = /^(\s*)(\d+)\.\s/
189190
const UNORDERED_LIST_REGEX = /^(\s*)[-*+]\s/
190191
const CHECK_LIST_REGEX = /^(\s*)(?:-\s)?\s?(\[(\s|x)?\])\s/i
191192
const HEADING_REGEX = /^(#{1,6})\s/
192193
const QUOTE_REGEX = /^>\s/
193-
const CODE_START_REGEX = /^[ \t]*```(\w+)?/
194-
const CODE_END_REGEX = /[ \t]*```$/
194+
const CODE_START_REGEX = /^[ \t]*(\\`\\`\\`|```)(\w+)?/
195+
const CODE_END_REGEX = /[ \t]*(\\`\\`\\`|```)$/
195196
const CODE_SINGLE_LINE_REGEX = /^[ \t]*```[^`]+(?:(?:`{1,2}|`{4,})[^`]+)*```(?:[^`]|$)/
196197
const TABLE_ROW_REG_EXP = /^\|(.+)\|\s?$/
197198
const TABLE_ROW_DIVIDER_REG_EXP = /^(\| ?:?-*:? ?)+\|\s?$/
199+
const TAG_START_REGEX = /^[ \t]*<[a-z_][\w-]*(?:\s[^<>]*)?\/?>/i
200+
const TAG_END_REGEX = /^[ \t]*<\/[a-z_][\w-]*\s*>/i
198201

199202
const createBlockNode = (
200203
createNode: (match: Array<string>) => ElementNode,
@@ -433,10 +436,11 @@ export const ITALIC_UNDERSCORE: TextFormatTransformer = {
433436
tag: '_',
434437
}
435438

436-
export function normalizeMarkdown(input: string, shouldMergeAdjacentLines = false): string {
439+
export function normalizeMarkdown(input: string, shouldMergeAdjacentLines: boolean): string {
437440
const lines = input.split('\n')
438441
let inCodeBlock = false
439442
const sanitizedLines: string[] = []
443+
let nestedDeepCodeBlock = 0
440444

441445
for (let i = 0; i < lines.length; i++) {
442446
const line = lines[i]
@@ -448,9 +452,24 @@ export function normalizeMarkdown(input: string, shouldMergeAdjacentLines = fals
448452
continue
449453
}
450454

451-
// Detect the start or end of a code block
452-
if (CODE_START_REGEX.test(line) || CODE_END_REGEX.test(line)) {
453-
inCodeBlock = !inCodeBlock
455+
if (CODE_END_REGEX.test(line)) {
456+
if (nestedDeepCodeBlock === 0) {
457+
inCodeBlock = true
458+
}
459+
if (nestedDeepCodeBlock === 1) {
460+
inCodeBlock = false
461+
}
462+
if (nestedDeepCodeBlock > 0) {
463+
nestedDeepCodeBlock--
464+
}
465+
sanitizedLines.push(line)
466+
continue
467+
}
468+
469+
// Toggle inCodeBlock state when encountering start or end of a code block
470+
if (CODE_START_REGEX.test(line)) {
471+
inCodeBlock = true
472+
nestedDeepCodeBlock++
454473
sanitizedLines.push(line)
455474
continue
456475
}
@@ -464,8 +483,8 @@ export function normalizeMarkdown(input: string, shouldMergeAdjacentLines = fals
464483
// In markdown the concept of "empty paragraphs" does not exist.
465484
// Blocks must be separated by an empty line. Non-empty adjacent lines must be merged.
466485
if (
467-
line === '' ||
468-
lastLine === '' ||
486+
EMPTY_OR_WHITESPACE_ONLY.test(line) ||
487+
EMPTY_OR_WHITESPACE_ONLY.test(lastLine) ||
469488
!lastLine ||
470489
HEADING_REGEX.test(lastLine) ||
471490
HEADING_REGEX.test(line) ||
@@ -475,11 +494,16 @@ export function normalizeMarkdown(input: string, shouldMergeAdjacentLines = fals
475494
CHECK_LIST_REGEX.test(line) ||
476495
TABLE_ROW_REG_EXP.test(line) ||
477496
TABLE_ROW_DIVIDER_REG_EXP.test(line) ||
478-
!shouldMergeAdjacentLines
497+
!shouldMergeAdjacentLines ||
498+
TAG_START_REGEX.test(line) ||
499+
TAG_END_REGEX.test(line) ||
500+
TAG_START_REGEX.test(lastLine) ||
501+
TAG_END_REGEX.test(lastLine) ||
502+
CODE_END_REGEX.test(lastLine)
479503
) {
480504
sanitizedLines.push(line)
481505
} else {
482-
sanitizedLines[sanitizedLines.length - 1] = lastLine + line
506+
sanitizedLines[sanitizedLines.length - 1] = lastLine + ' ' + line.trim()
483507
}
484508
}
485509

packages/richtext-lexical/src/packages/@lexical/markdown/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ function $convertFromMarkdownString(
8282
transformers: Array<Transformer> = TRANSFORMERS,
8383
node?: ElementNode,
8484
shouldPreserveNewLines = false,
85-
shouldMergeAdjacentLines = false,
85+
shouldMergeAdjacentLines = true,
8686
): void {
8787
const sanitizedMarkdown = shouldPreserveNewLines
8888
? markdown

packages/richtext-lexical/src/utilities/jsx/lexicalMarkdownCopy.ts

Lines changed: 0 additions & 112 deletions
This file was deleted.

test/lexical-mdx/int.spec.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,6 @@ describe('Lexical MDX', () => {
174174
? (sanitizedInputAfterConvertFromEditorJSON ?? sanitizedInput).replace(/\s/g, '')
175175
: (sanitizedInputAfterConvertFromEditorJSON ?? sanitizedInput)
176176

177-
console.log('resultNoSpace', resultNoSpace)
178-
console.log('inputNoSpace', inputNoSpace)
179177
expect(resultNoSpace).toBe(inputNoSpace)
180178
})
181179
}

0 commit comments

Comments
 (0)