Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into react-19-unit-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
etrepum committed May 17, 2024
2 parents 492816e + c1f0416 commit 3541f2d
Show file tree
Hide file tree
Showing 26 changed files with 369 additions and 211 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
*/

/**
Expand Down
23 changes: 18 additions & 5 deletions packages/lexical-markdown/src/MarkdownExport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ import {
$isTextNode,
} from 'lexical';

import {transformersByType} from './utils';
import {isEmptyParagraph, transformersByType} from './utils';

export function createMarkdownExport(
transformers: Array<Transformer>,
shouldPreserveNewLines: boolean = false,
): (node?: ElementNode) => string {
const byType = transformersByType(transformers);
const isNewlineDelimited = !shouldPreserveNewLines;

// Export only uses text formats that are responsible for single format
// e.g. it will filter out *** (bold, italic) and instead use separate ** and *
Expand All @@ -39,7 +41,8 @@ export function createMarkdownExport(
const output = [];
const children = (node || $getRoot()).getChildren();

for (const child of children) {
for (let i = 0; i < children.length; i++) {
const child = children[i];
const result = exportTopLevelElements(
child,
byType.element,
Expand All @@ -48,11 +51,20 @@ export function createMarkdownExport(
);

if (result != null) {
output.push(result);
output.push(
// seperate consecutive group of texts with a line break: eg. ["hello", "world"] -> ["hello", "/nworld"]
isNewlineDelimited &&
i > 0 &&
!isEmptyParagraph(child) &&
!isEmptyParagraph(children[i - 1])
? '\n'.concat(result)
: result,
);
}
}

return output.join('\n\n');
// Ensure consecutive groups of texts are atleast \n\n apart while each empty paragraph render as a newline.
// Eg. ["hello", "", "", "hi", "\nworld"] -> "hello\n\n\nhi\n\nworld"
return output.join('\n');
};
}

Expand Down Expand Up @@ -116,6 +128,7 @@ function exportChildren(
exportTextFormat(child, child.getTextContent(), textTransformersIndex),
);
} else if ($isElementNode(child)) {
// empty paragraph returns ""
output.push(
exportChildren(child, textTransformersIndex, textMatchTransformers),
);
Expand Down
36 changes: 15 additions & 21 deletions packages/lexical-markdown/src/MarkdownImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type {
TextMatchTransformer,
Transformer,
} from '@lexical/markdown';
import type {LexicalNode, TextNode} from 'lexical';
import type {TextNode} from 'lexical';

import {$createCodeNode} from '@lexical/code';
import {$isListItemNode, $isListNode, ListItemNode} from '@lexical/list';
Expand All @@ -26,14 +26,16 @@ import {
$getRoot,
$getSelection,
$isParagraphNode,
$isTextNode,
ElementNode,
} from 'lexical';
import {IS_APPLE_WEBKIT, IS_IOS, IS_SAFARI} from 'shared/environment';

import {PUNCTUATION_OR_SPACE, transformersByType} from './utils';
import {
isEmptyParagraph,
PUNCTUATION_OR_SPACE,
transformersByType,
} from './utils';

const MARKDOWN_EMPTY_LINE_REG_EXP = /^\s{0,3}$/;
const CODE_BLOCK_REG_EXP = /^[ \t]*```(\w{1,10})?\s?$/;
type TextFormatTransformersIndex = Readonly<{
fullMatchRegExpByTag: Readonly<Record<string, RegExp>>;
Expand All @@ -43,6 +45,7 @@ type TextFormatTransformersIndex = Readonly<{

export function createMarkdownImport(
transformers: Array<Transformer>,
shouldPreserveNewLines = false,
): (markdownString: string, node?: ElementNode) => void {
const byType = transformersByType(transformers);
const textFormatTransformersIndex = createTextFormatTransformersIndex(
Expand Down Expand Up @@ -77,11 +80,16 @@ export function createMarkdownImport(
);
}

// Removing empty paragraphs as md does not really
// allow empty lines and uses them as delimiter
// By default, removing empty paragraphs as md does not really
// allow empty lines and uses them as delimiter.
// If you need empty lines set shouldPreserveNewLines = true.
const children = root.getChildren();
for (const child of children) {
if (isEmptyParagraph(child) && root.getChildrenSize() > 1) {
if (
!shouldPreserveNewLines &&
isEmptyParagraph(child) &&
root.getChildrenSize() > 1
) {
child.remove();
}
}
Expand All @@ -92,20 +100,6 @@ export function createMarkdownImport(
};
}

function isEmptyParagraph(node: LexicalNode): boolean {
if (!$isParagraphNode(node)) {
return false;
}

const firstChild = node.getFirstChild();
return (
firstChild == null ||
(node.getChildrenSize() === 1 &&
$isTextNode(firstChild) &&
MARKDOWN_EMPTY_LINE_REG_EXP.test(firstChild.getTextContent()))
);
}

function $importBlocks(
lineText: string,
rootNode: ElementNode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe('Markdown', () => {
md: string;
skipExport?: true;
skipImport?: true;
shouldPreserveNewLines?: true;
}>;

const URL = 'https://lexical.dev';
Expand Down Expand Up @@ -147,6 +148,16 @@ describe('Markdown', () => {
html: '<p><i><em style="white-space: pre-wrap;">Hello </em></i><i><b><strong style="white-space: pre-wrap;">world</strong></b></i><i><em style="white-space: pre-wrap;">!</em></i></p>',
md: '*Hello **world**!*',
},
{
html: '<h1><span style="white-space: pre-wrap;">Hello</span></h1><p><br></p><p><br></p><p><br></p><p><b><strong style="white-space: pre-wrap;">world</strong></b><span style="white-space: pre-wrap;">!</span></p>',
md: '# Hello\n\n\n\n**world**!',
shouldPreserveNewLines: true,
},
{
html: '<h1><span style="white-space: pre-wrap;">Hello</span></h1><p><span style="white-space: pre-wrap;">hi</span></p><p><br></p><p><b><strong style="white-space: pre-wrap;">world</strong></b></p><p><br></p><p><span style="white-space: pre-wrap;">hi</span></p><blockquote><span style="white-space: pre-wrap;">hello</span><br><span style="white-space: pre-wrap;">hello</span></blockquote><p><br></p><h1><span style="white-space: pre-wrap;">hi</span></h1><p><br></p><p><span style="white-space: pre-wrap;">hi</span></p>',
md: '# Hello\nhi\n\n**world**\n\nhi\n> hello\n> hello\n\n# hi\n\nhi',
shouldPreserveNewLines: true,
},
{
// Import only: export will use * instead of _ due to registered transformers order
html: '<p><i><em style="white-space: pre-wrap;">Hello</em></i><span style="white-space: pre-wrap;"> world</span></p>',
Expand Down Expand Up @@ -221,7 +232,12 @@ describe('Markdown', () => {
},
};

for (const {html, md, skipImport} of IMPORT_AND_EXPORT) {
for (const {
html,
md,
skipImport,
shouldPreserveNewLines,
} of IMPORT_AND_EXPORT) {
if (skipImport) {
continue;
}
Expand All @@ -240,10 +256,12 @@ describe('Markdown', () => {

editor.update(
() =>
$convertFromMarkdownString(md, [
...TRANSFORMERS,
HIGHLIGHT_TEXT_MATCH_IMPORT,
]),
$convertFromMarkdownString(
md,
[...TRANSFORMERS, HIGHLIGHT_TEXT_MATCH_IMPORT],
undefined,
shouldPreserveNewLines,
),
{
discrete: true,
},
Expand All @@ -255,7 +273,12 @@ describe('Markdown', () => {
});
}

for (const {html, md, skipExport} of IMPORT_AND_EXPORT) {
for (const {
html,
md,
skipExport,
shouldPreserveNewLines,
} of IMPORT_AND_EXPORT) {
if (skipExport) {
continue;
}
Expand Down Expand Up @@ -288,7 +311,13 @@ describe('Markdown', () => {
expect(
editor
.getEditorState()
.read(() => $convertToMarkdownString(TRANSFORMERS)),
.read(() =>
$convertToMarkdownString(
TRANSFORMERS,
undefined,
shouldPreserveNewLines,
),
),
).toBe(md);
});
}
Expand Down
12 changes: 10 additions & 2 deletions packages/lexical-markdown/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,24 @@ function $convertFromMarkdownString(
markdown: string,
transformers: Array<Transformer> = TRANSFORMERS,
node?: ElementNode,
shouldPreserveNewLines = false,
): void {
const importMarkdown = createMarkdownImport(transformers);
const importMarkdown = createMarkdownImport(
transformers,
shouldPreserveNewLines,
);
return importMarkdown(markdown, node);
}

function $convertToMarkdownString(
transformers: Array<Transformer> = TRANSFORMERS,
node?: ElementNode,
shouldPreserveNewLines: boolean = false,
): string {
const exportMarkdown = createMarkdownExport(transformers);
const exportMarkdown = createMarkdownExport(
transformers,
shouldPreserveNewLines,
);
return exportMarkdown(node);
}

Expand Down
24 changes: 23 additions & 1 deletion packages/lexical-markdown/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@ import type {
TextMatchTransformer,
Transformer,
} from '@lexical/markdown';
import type {ElementNode, LexicalNode, TextFormatType} from 'lexical';

import {$isCodeNode} from '@lexical/code';
import {$isListItemNode, $isListNode} from '@lexical/list';
import {$isHeadingNode, $isQuoteNode} from '@lexical/rich-text';
import {
$isParagraphNode,
$isTextNode,
type ElementNode,
type LexicalNode,
type TextFormatType,
} from 'lexical';

type MarkdownFormatKind =
| 'noTransformation'
Expand Down Expand Up @@ -429,3 +435,19 @@ export function transformersByType(transformers: Array<Transformer>): Readonly<{
}

export const PUNCTUATION_OR_SPACE = /[!-/:-@[-`{-~\s]/;

const MARKDOWN_EMPTY_LINE_REG_EXP = /^\s{0,3}$/;

export function isEmptyParagraph(node: LexicalNode): boolean {
if (!$isParagraphNode(node)) {
return false;
}

const firstChild = node.getFirstChild();
return (
firstChild == null ||
(node.getChildrenSize() === 1 &&
$isTextNode(firstChild) &&
MARKDOWN_EMPTY_LINE_REG_EXP.test(firstChild.getTextContent()))
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,14 @@ test.describe('HTML CopyAndPaste', () => {
await assertHTML(
page,
html`
<hr class="" contenteditable="false" data-lexical-decorator="true" />
<hr class="" contenteditable="false" data-lexical-decorator="true" />
<hr
class="PlaygroundEditorTheme__hr"
contenteditable="false"
data-lexical-decorator="true" />
<hr
class="PlaygroundEditorTheme__hr"
contenteditable="false"
data-lexical-decorator="true" />
<div
class="PlaygroundEditorTheme__blockCursor"
contenteditable="false"
Expand All @@ -228,13 +234,19 @@ test.describe('HTML CopyAndPaste', () => {
await assertHTML(
page,
html`
<hr class="" contenteditable="false" data-lexical-decorator="true" />
<hr
class="PlaygroundEditorTheme__hr"
contenteditable="false"
data-lexical-decorator="true" />
<p
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
dir="ltr">
<span data-lexical-text="true">Text between HRs</span>
</p>
<hr class="" contenteditable="false" data-lexical-decorator="true" />
<hr
class="PlaygroundEditorTheme__hr"
contenteditable="false"
data-lexical-decorator="true" />
`,
);
await assertSelection(page, {
Expand Down Expand Up @@ -266,7 +278,10 @@ test.describe('HTML CopyAndPaste', () => {
dir="ltr">
<span data-lexical-text="true">Hello</span>
</p>
<hr class="" contenteditable="false" data-lexical-decorator="true" />
<hr
class="PlaygroundEditorTheme__hr"
contenteditable="false"
data-lexical-decorator="true" />
<p
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
dir="ltr">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,10 @@ test.describe('HTML Lists CopyAndPaste', () => {
<span data-lexical-text="true">one</span>
</li>
</ul>
<hr class="" contenteditable="false" data-lexical-decorator="true" />
<hr
class="PlaygroundEditorTheme__hr"
contenteditable="false"
data-lexical-decorator="true" />
<ul class="PlaygroundEditorTheme__ul">
<li
class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__ltr"
Expand Down
Loading

0 comments on commit 3541f2d

Please sign in to comment.