@payloadcms/richtext-lexical 3.85.1 → 3.85.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/exports/client/{Field-KR4W3EZZ.js → Field-J6MIUIWP.js} +2 -2
- package/dist/exports/client/chunk-QAW5QKEM.js +12 -0
- package/dist/exports/client/chunk-QAW5QKEM.js.map +7 -0
- package/dist/exports/client/index.js +2 -2
- package/dist/packages/@lexical/markdown/LexicalMarkdownHardLineBreak.spec.js +160 -0
- package/dist/packages/@lexical/markdown/LexicalMarkdownHardLineBreak.spec.js.map +1 -0
- package/dist/packages/@lexical/markdown/MarkdownExport.d.ts.map +1 -1
- package/dist/packages/@lexical/markdown/MarkdownExport.js +9 -2
- package/dist/packages/@lexical/markdown/MarkdownExport.js.map +1 -1
- package/dist/packages/@lexical/markdown/MarkdownImport.d.ts.map +1 -1
- package/dist/packages/@lexical/markdown/MarkdownImport.js +3 -2
- package/dist/packages/@lexical/markdown/MarkdownImport.js.map +1 -1
- package/dist/packages/@lexical/markdown/MarkdownTransformers.d.ts +27 -1
- package/dist/packages/@lexical/markdown/MarkdownTransformers.d.ts.map +1 -1
- package/dist/packages/@lexical/markdown/MarkdownTransformers.js +88 -4
- package/dist/packages/@lexical/markdown/MarkdownTransformers.js.map +1 -1
- package/package.json +6 -6
- package/dist/exports/client/chunk-FTT5KJ6W.js +0 -12
- package/dist/exports/client/chunk-FTT5KJ6W.js.map +0 -7
- /package/dist/exports/client/{Field-KR4W3EZZ.js.map → Field-J6MIUIWP.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LexicalMarkdownHardLineBreak.spec.js","names":["createHeadlessEditor","LinkNode","ListItemNode","ListNode","HeadingNode","QuoteNode","$getRoot","$getState","$isElementNode","$isLineBreakNode","$isTextNode","describe","expect","it","$convertFromMarkdownString","$convertToMarkdownString","hardLineBreakState","normalizeMarkdown","createEditor","nodes","importMarkdown","markdown","editor","update","discrete","result","hasLineBreak","textContent","getEditorState","read","firstChild","getFirstChild","Error","getChildren","some","child","getTextContent","getStoredHardLineBreak","marker","textNodeTexts","block","lineBreakNode","find","undefined","filter","map","roundTrip","output","md","join","toBe","not","toContain"],"sources":["../../../../src/packages/@lexical/markdown/LexicalMarkdownHardLineBreak.spec.ts"],"sourcesContent":["import { createHeadlessEditor } from '@lexical/headless'\nimport { LinkNode } from '@lexical/link'\nimport { ListItemNode, ListNode } from '@lexical/list'\nimport { HeadingNode, QuoteNode } from '@lexical/rich-text'\nimport { $getRoot, $getState, $isElementNode, $isLineBreakNode, $isTextNode } from 'lexical'\nimport { describe, expect, it } from 'vitest'\n\nimport { $convertFromMarkdownString, $convertToMarkdownString } from './index.js'\nimport { hardLineBreakState, normalizeMarkdown } from './MarkdownTransformers.js'\n\nfunction createEditor() {\n return createHeadlessEditor({\n nodes: [HeadingNode, QuoteNode, ListNode, ListItemNode, LinkNode],\n })\n}\n\nfunction importMarkdown(markdown: string): { hasLineBreak: boolean; textContent: string } {\n const editor = createEditor()\n editor.update(() => $convertFromMarkdownString(markdown), { discrete: true })\n let result = { hasLineBreak: false, textContent: '' }\n editor.getEditorState().read(() => {\n const firstChild = $getRoot().getFirstChild()\n if (!$isElementNode(firstChild)) {\n throw new Error('Expected an element block')\n }\n result = {\n hasLineBreak: firstChild.getChildren().some((child) => $isLineBreakNode(child)),\n textContent: firstChild.getTextContent(),\n }\n })\n return result\n}\n\n// Returns the hard line break marker stored on the first LineBreakNode of the\n// first block, plus the text content of every text node in that block. Used to\n// assert the marker was moved off the text and onto the line break node, even\n// when the trailing whitespace was split into its own text node by an inline\n// transformer (e.g. after bold or a link).\nfunction getStoredHardLineBreak(markdown: string): {\n marker: string\n textNodeTexts: string[]\n} {\n const editor = createEditor()\n editor.update(() => $convertFromMarkdownString(markdown), { discrete: true })\n let result = { marker: '', textNodeTexts: [] as string[] }\n editor.getEditorState().read(() => {\n const block = $getRoot().getFirstChild()\n if (!$isElementNode(block)) {\n throw new Error('Expected an element block')\n }\n const lineBreakNode = block.getChildren().find((child) => $isLineBreakNode(child))\n if (lineBreakNode === undefined) {\n throw new Error('Expected a line break node')\n }\n result = {\n marker: $getState(lineBreakNode, hardLineBreakState),\n textNodeTexts: block\n .getChildren()\n .filter($isTextNode)\n .map((child) => child.getTextContent()),\n }\n })\n return result\n}\n\nfunction roundTrip(markdown: string): string {\n const editor = createEditor()\n editor.update(() => $convertFromMarkdownString(markdown), { discrete: true })\n let output = ''\n editor.getEditorState().read(() => {\n output = $convertToMarkdownString()\n })\n return output\n}\n\ndescribe('markdown hard line break normalization', () => {\n it('should preserve trailing-space hard breaks when merging adjacent lines', () => {\n const md = ['foo ', 'bar'].join('\\n')\n expect(normalizeMarkdown(md, true)).toBe(md)\n })\n\n it('should preserve the exact number of trailing spaces', () => {\n const md = ['foo ', 'bar'].join('\\n')\n expect(normalizeMarkdown(md, true)).toBe(md)\n })\n\n it('should preserve backslash hard breaks when merging adjacent lines', () => {\n const md = 'foo\\\\\\nbar'\n expect(normalizeMarkdown(md, true)).toBe(md)\n })\n\n it('should still merge a soft break into the previous line', () => {\n const md = ['foo', 'bar'].join('\\n')\n expect(normalizeMarkdown(md, true)).toBe('foo bar')\n })\n\n it('should merge a soft break before a hard-breaking line', () => {\n const md = ['foo', 'bar ', 'baz'].join('\\n')\n expect(normalizeMarkdown(md, true)).toBe(['foo bar ', 'baz'].join('\\n'))\n })\n})\n\ndescribe('markdown hard line break import', () => {\n it('should import a trailing-space hard break as a line break node with the marker stripped', () => {\n const { hasLineBreak, textContent } = importMarkdown(['foo ', 'bar'].join('\\n'))\n\n expect(hasLineBreak).toBe(true)\n // The marker is stripped from the text and represented by the line break node.\n expect(textContent).toBe('foo\\nbar')\n })\n\n it('should import a backslash hard break as a line break node with the marker stripped', () => {\n const { hasLineBreak, textContent } = importMarkdown('foo\\\\\\nbar')\n\n expect(hasLineBreak).toBe(true)\n expect(textContent).toBe('foo\\nbar')\n })\n\n it('should store the marker on the line break when the hard break follows formatted text', () => {\n const { marker, textNodeTexts } = getStoredHardLineBreak('**foo** \\nbar')\n\n expect(marker).toBe(' ')\n // The trailing-space marker is split into its own text node by the bold\n // transformer; it must be moved onto the line break node, not left as text.\n expect(textNodeTexts).not.toContain(' ')\n })\n\n it('should store the exact number of trailing spaces when the hard break follows formatted text', () => {\n const { marker } = getStoredHardLineBreak('**foo** \\nbar')\n\n expect(marker).toBe(' ')\n })\n\n it('should store the marker on the line break when the hard break follows a link', () => {\n const { marker } = getStoredHardLineBreak('[foo](https://payloadcms.com) \\nbar')\n\n expect(marker).toBe(' ')\n })\n})\n\ndescribe('markdown hard line break round trip', () => {\n it('should round trip a trailing-space hard break', () => {\n const md = ['foo ', 'bar'].join('\\n')\n expect(roundTrip(md)).toBe(md)\n })\n\n it('should round trip a backslash hard break', () => {\n const md = 'foo\\\\\\nbar'\n expect(roundTrip(md)).toBe(md)\n })\n\n it('should not introduce a hard break for a soft wrap', () => {\n expect(roundTrip(['foo', 'bar'].join('\\n'))).toBe('foo bar')\n })\n\n it('should round trip a hard break after formatted text', () => {\n const md = '**foo** \\nbar'\n expect(roundTrip(md)).toBe(md)\n })\n\n it('should round trip a hard break after a link', () => {\n const md = '[foo](https://payloadcms.com) \\nbar'\n expect(roundTrip(md)).toBe(md)\n })\n})\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarkdownExport.d.ts","sourceRoot":"","sources":["../../../../src/packages/@lexical/markdown/MarkdownExport.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"MarkdownExport.d.ts","sourceRoot":"","sources":["../../../../src/packages/@lexical/markdown/MarkdownExport.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAwD,MAAM,SAAS,CAAA;AAWhG,OAAO,KAAK,EAKV,WAAW,EACZ,MAAM,2BAA2B,CAAA;AAKlC;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,KAAK,CAAC,WAAW,CAAC,EAChC,sBAAsB,GAAE,OAAe,GACtC,CAAC,IAAI,CAAC,EAAE,WAAW,KAAK,MAAM,CAkDhC"}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*
|
|
7
|
-
*/import { $getRoot, $isDecoratorNode, $isElementNode, $isLineBreakNode, $isTextNode } from 'lexical';
|
|
7
|
+
*/import { $getRoot, $getState, $isDecoratorNode, $isElementNode, $isLineBreakNode, $isTextNode } from 'lexical';
|
|
8
|
+
import { hardLineBreakState } from './MarkdownTransformers.js';
|
|
8
9
|
import { isEmptyParagraph, transformersByType } from './utils.js';
|
|
9
10
|
/**
|
|
10
11
|
* Renders string from markdown. The selection is moved to the start after the operation.
|
|
@@ -89,7 +90,7 @@ function exportChildren(node, textTransformersIndex, textMatchTransformers, uncl
|
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
if ($isLineBreakNode(child)) {
|
|
92
|
-
output.push(
|
|
93
|
+
output.push($exportLineBreak(child));
|
|
93
94
|
} else if ($isTextNode(child)) {
|
|
94
95
|
output.push(exportTextFormat(child, child.getTextContent(), textTransformersIndex, unclosedTags, unclosableTags));
|
|
95
96
|
} else if ($isElementNode(child)) {
|
|
@@ -101,6 +102,12 @@ function exportChildren(node, textTransformersIndex, textMatchTransformers, uncl
|
|
|
101
102
|
}
|
|
102
103
|
return output.join('');
|
|
103
104
|
}
|
|
105
|
+
// Prefixes the newline with the original CommonMark hard line break marker
|
|
106
|
+
// (`\` or trailing spaces) when one was preserved on import, otherwise exports a
|
|
107
|
+
// plain soft line break.
|
|
108
|
+
function $exportLineBreak(node) {
|
|
109
|
+
return $getState(node, hardLineBreakState) + '\n';
|
|
110
|
+
}
|
|
104
111
|
function exportTextFormat(node, textContent, textTransformers,
|
|
105
112
|
// unclosed tags include the markdown tags that haven't been closed yet, and their associated formats
|
|
106
113
|
unclosedTags, unclosableTags) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarkdownExport.js","names":["$getRoot","$isDecoratorNode","$isElementNode","$isLineBreakNode","$isTextNode","isEmptyParagraph","transformersByType","createMarkdownExport","transformers","shouldPreserveNewLines","byType","elementTransformers","multilineElement","element","isNewlineDelimited","textFormatTransformers","textFormat","filter","transformer","format","length","sort","a","b","includes","node","output","children","getChildren","forEach","child","i","result","exportTopLevelElements","textMatch","push","concat","join","textTransformersIndex","textMatchTransformers","export","_node","exportChildren","getTextContent","unclosedTags","unclosableTags","mainLoop","parentNode","textNode","textContent","exportTextFormat","textTransformers","frozenString","trim","hasFormat","replace","openingTags","closingTagsBefore","closingTagsAfter","prevNode","getTextSibling","nextNode","applied","Set","tag","has","add","find","unclosedTag","nodeHasFormat","nextNodeHasFormat","unhandledUnclosedTags","pop","backward","sibling","getPreviousSibling","getNextSibling","parent","getParentOrThrow","isInline","descendant","getLastDescendant","getFirstDescendant"],"sources":["../../../../src/packages/@lexical/markdown/MarkdownExport.ts"],"sourcesContent":["/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport type { ElementNode, LexicalNode, TextFormatType, TextNode } from 'lexical'\n\nimport { $getRoot, $isDecoratorNode, $isElementNode, $isLineBreakNode, $isTextNode } from 'lexical'\n\nimport type {\n ElementTransformer,\n MultilineElementTransformer,\n TextFormatTransformer,\n TextMatchTransformer,\n Transformer,\n} from './MarkdownTransformers.js'\n\nimport { isEmptyParagraph, transformersByType } from './utils.js'\n\n/**\n * Renders string from markdown. The selection is moved to the start after the operation.\n */\nexport function createMarkdownExport(\n transformers: Array<Transformer>,\n shouldPreserveNewLines: boolean = false,\n): (node?: ElementNode) => string {\n const byType = transformersByType(transformers)\n const elementTransformers = [...byType.multilineElement, ...byType.element]\n const isNewlineDelimited = !shouldPreserveNewLines\n\n // Export only uses text formats that are responsible for single format\n // e.g. it will filter out *** (bold, italic) and instead use separate ** and *\n const textFormatTransformers = byType.textFormat\n .filter((transformer) => transformer.format.length === 1)\n // Make sure all text transformers that contain 'code' in their format are at the end of the array. Otherwise, formatted code like\n // <strong><code>code</code></strong> will be exported as `**Bold Code**`, as the code format will be applied first, and the bold format\n // will be applied second and thus skipped entirely, as the code format will prevent any further formatting.\n .sort((a, b) => {\n if (a.format.includes('code') && !b.format.includes('code')) {\n return 1\n } else if (!a.format.includes('code') && b.format.includes('code')) {\n return -1\n } else {\n return 0\n }\n })\n\n return (node) => {\n const output: string[] = []\n const children = (node || $getRoot()).getChildren()\n\n children.forEach((child, i) => {\n const result = exportTopLevelElements(\n child,\n elementTransformers,\n textFormatTransformers,\n byType.textMatch,\n )\n\n if (result != null) {\n output.push(\n // separate consecutive group of texts with a line break: eg. [\"hello\", \"world\"] -> [\"hello\", \"/nworld\"]\n isNewlineDelimited &&\n i > 0 &&\n !isEmptyParagraph(child) &&\n !isEmptyParagraph(children[i - 1]!)\n ? '\\n'.concat(result)\n : result,\n )\n }\n })\n // Ensure consecutive groups of texts are at least \\n\\n apart while each empty paragraph render as a newline.\n // Eg. [\"hello\", \"\", \"\", \"hi\", \"\\nworld\"] -> \"hello\\n\\n\\nhi\\n\\nworld\"\n return output.join('\\n')\n }\n}\n\nfunction exportTopLevelElements(\n node: LexicalNode,\n elementTransformers: Array<ElementTransformer | MultilineElementTransformer>,\n textTransformersIndex: Array<TextFormatTransformer>,\n textMatchTransformers: Array<TextMatchTransformer>,\n): null | string {\n for (const transformer of elementTransformers) {\n if (!transformer.export) {\n continue\n }\n const result = transformer.export(node, (_node) =>\n exportChildren(_node, textTransformersIndex, textMatchTransformers),\n )\n\n if (result != null) {\n return result\n }\n }\n\n if ($isElementNode(node)) {\n return exportChildren(node, textTransformersIndex, textMatchTransformers)\n } else if ($isDecoratorNode(node)) {\n return node.getTextContent()\n } else {\n return null\n }\n}\n\nfunction exportChildren(\n node: ElementNode,\n textTransformersIndex: Array<TextFormatTransformer>,\n textMatchTransformers: Array<TextMatchTransformer>,\n unclosedTags?: Array<{ format: TextFormatType; tag: string }>,\n unclosableTags?: Array<{ format: TextFormatType; tag: string }>,\n): string {\n const output = []\n const children = node.getChildren()\n // keep track of unclosed tags from the very beginning\n if (!unclosedTags) {\n unclosedTags = []\n }\n if (!unclosableTags) {\n unclosableTags = []\n }\n\n mainLoop: for (const child of children) {\n for (const transformer of textMatchTransformers) {\n if (!transformer.export) {\n continue\n }\n\n const result = transformer.export(\n child,\n (parentNode) =>\n exportChildren(\n parentNode,\n textTransformersIndex,\n textMatchTransformers,\n unclosedTags,\n // Add current unclosed tags to the list of unclosable tags - we don't want nested tags from\n // textmatch transformers to close the outer ones, as that may result in invalid markdown.\n // E.g. **text [text**](https://lexical.io)\n // is invalid markdown, as the closing ** is inside the link.\n //\n [...unclosableTags, ...unclosedTags],\n ),\n (textNode, textContent) =>\n exportTextFormat(\n textNode,\n textContent,\n textTransformersIndex,\n unclosedTags,\n unclosableTags,\n ),\n )\n\n if (result != null) {\n output.push(result)\n continue mainLoop\n }\n }\n\n if ($isLineBreakNode(child)) {\n output.push('\\n')\n } else if ($isTextNode(child)) {\n output.push(\n exportTextFormat(\n child,\n child.getTextContent(),\n textTransformersIndex,\n unclosedTags,\n unclosableTags,\n ),\n )\n } else if ($isElementNode(child)) {\n // empty paragraph returns \"\"\n output.push(\n exportChildren(\n child,\n textTransformersIndex,\n textMatchTransformers,\n unclosedTags,\n unclosableTags,\n ),\n )\n } else if ($isDecoratorNode(child)) {\n output.push(child.getTextContent())\n }\n }\n\n return output.join('')\n}\n\nfunction exportTextFormat(\n node: TextNode,\n textContent: string,\n textTransformers: Array<TextFormatTransformer>,\n // unclosed tags include the markdown tags that haven't been closed yet, and their associated formats\n unclosedTags: Array<{ format: TextFormatType; tag: string }>,\n unclosableTags?: Array<{ format: TextFormatType; tag: string }>,\n): string {\n // This function handles the case of a string looking like this: \" foo \"\n // Where it would be invalid markdown to generate: \"** foo **\"\n // We instead want to trim the whitespace out, apply formatting, and then\n // bring the whitespace back. So our returned string looks like this: \" **foo** \"\n const frozenString = textContent.trim()\n let output = frozenString\n\n if (!node.hasFormat('code')) {\n // Escape any markdown characters in the text content\n output = output.replace(/([*_`~\\\\])/g, '\\\\$1')\n }\n\n // the opening tags to be added to the result\n let openingTags = ''\n // the closing tags to be added to the result\n let closingTagsBefore = ''\n let closingTagsAfter = ''\n\n const prevNode = getTextSibling(node, true)\n const nextNode = getTextSibling(node, false)\n\n const applied = new Set()\n\n for (const transformer of textTransformers) {\n const format = transformer.format[0]!\n const tag = transformer.tag\n\n // dedup applied formats\n if (hasFormat(node, format) && !applied.has(format)) {\n // Multiple tags might be used for the same format (*, _)\n applied.add(format)\n\n // append the tag to openningTags, if it's not applied to the previous nodes,\n // or the nodes before that (which would result in an unclosed tag)\n if (!hasFormat(prevNode, format) || !unclosedTags.find((element) => element.tag === tag)) {\n unclosedTags.push({ format, tag })\n openingTags += tag\n }\n }\n }\n\n // close any tags in the same order they were applied, if necessary\n for (let i = 0; i < unclosedTags.length; i++) {\n const unclosedTag = unclosedTags[i]!\n const nodeHasFormat = hasFormat(node, unclosedTag.format)\n const nextNodeHasFormat = hasFormat(nextNode, unclosedTag.format)\n\n // prevent adding closing tag if next sibling will do it\n if (nodeHasFormat && nextNodeHasFormat) {\n continue\n }\n\n const unhandledUnclosedTags = [...unclosedTags] // Shallow copy to avoid modifying the original array\n\n while (unhandledUnclosedTags.length > i) {\n const unclosedTag = unhandledUnclosedTags.pop()\n\n // If tag is unclosable, don't close it and leave it in the original array,\n // So that it can be closed when it's no longer unclosable\n if (\n unclosableTags &&\n unclosedTag &&\n unclosableTags.find((element) => element.tag === unclosedTag.tag)\n ) {\n continue\n }\n\n if (unclosedTag && typeof unclosedTag.tag === 'string') {\n if (!nodeHasFormat) {\n // Handles cases where the tag has not been closed before, e.g. if the previous node\n // was a text match transformer that did not account for closing tags of the next node (e.g. a link)\n closingTagsBefore += unclosedTag.tag\n } else if (!nextNodeHasFormat) {\n closingTagsAfter += unclosedTag.tag\n }\n }\n // Mutate the original array to remove the closed tag\n unclosedTags.pop()\n }\n break\n }\n\n output = openingTags + output + closingTagsAfter\n // Replace trimmed version of textContent ensuring surrounding whitespace is not modified\n return closingTagsBefore + textContent.replace(frozenString, () => output)\n}\n\n// Get next or previous text sibling a text node, including cases\n// when it's a child of inline element (e.g. link)\nfunction getTextSibling(node: TextNode, backward: boolean): null | TextNode {\n let sibling = backward ? node.getPreviousSibling() : node.getNextSibling()\n\n if (!sibling) {\n const parent = node.getParentOrThrow()\n\n if (parent.isInline()) {\n sibling = backward ? parent.getPreviousSibling() : parent.getNextSibling()\n }\n }\n\n while (sibling) {\n if ($isElementNode(sibling)) {\n if (!sibling.isInline()) {\n break\n }\n\n const descendant = backward ? sibling.getLastDescendant() : sibling.getFirstDescendant()\n\n if ($isTextNode(descendant)) {\n return descendant\n } else {\n sibling = backward ? sibling.getPreviousSibling() : sibling.getNextSibling()\n }\n }\n\n if ($isTextNode(sibling)) {\n return sibling\n }\n\n if (!$isElementNode(sibling)) {\n return null\n }\n }\n\n return null\n}\n\nfunction hasFormat(node: LexicalNode | null | undefined, format: TextFormatType): boolean {\n return $isTextNode(node) && node.hasFormat(format)\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"MarkdownExport.js","names":["$getRoot","$getState","$isDecoratorNode","$isElementNode","$isLineBreakNode","$isTextNode","hardLineBreakState","isEmptyParagraph","transformersByType","createMarkdownExport","transformers","shouldPreserveNewLines","byType","elementTransformers","multilineElement","element","isNewlineDelimited","textFormatTransformers","textFormat","filter","transformer","format","length","sort","a","b","includes","node","output","children","getChildren","forEach","child","i","result","exportTopLevelElements","textMatch","push","concat","join","textTransformersIndex","textMatchTransformers","export","_node","exportChildren","getTextContent","unclosedTags","unclosableTags","mainLoop","parentNode","textNode","textContent","exportTextFormat","$exportLineBreak","textTransformers","frozenString","trim","hasFormat","replace","openingTags","closingTagsBefore","closingTagsAfter","prevNode","getTextSibling","nextNode","applied","Set","tag","has","add","find","unclosedTag","nodeHasFormat","nextNodeHasFormat","unhandledUnclosedTags","pop","backward","sibling","getPreviousSibling","getNextSibling","parent","getParentOrThrow","isInline","descendant","getLastDescendant","getFirstDescendant"],"sources":["../../../../src/packages/@lexical/markdown/MarkdownExport.ts"],"sourcesContent":["/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport type { ElementNode, LexicalNode, LineBreakNode, TextFormatType, TextNode } from 'lexical'\n\nimport {\n $getRoot,\n $getState,\n $isDecoratorNode,\n $isElementNode,\n $isLineBreakNode,\n $isTextNode,\n} from 'lexical'\n\nimport type {\n ElementTransformer,\n MultilineElementTransformer,\n TextFormatTransformer,\n TextMatchTransformer,\n Transformer,\n} from './MarkdownTransformers.js'\n\nimport { hardLineBreakState } from './MarkdownTransformers.js'\nimport { isEmptyParagraph, transformersByType } from './utils.js'\n\n/**\n * Renders string from markdown. The selection is moved to the start after the operation.\n */\nexport function createMarkdownExport(\n transformers: Array<Transformer>,\n shouldPreserveNewLines: boolean = false,\n): (node?: ElementNode) => string {\n const byType = transformersByType(transformers)\n const elementTransformers = [...byType.multilineElement, ...byType.element]\n const isNewlineDelimited = !shouldPreserveNewLines\n\n // Export only uses text formats that are responsible for single format\n // e.g. it will filter out *** (bold, italic) and instead use separate ** and *\n const textFormatTransformers = byType.textFormat\n .filter((transformer) => transformer.format.length === 1)\n // Make sure all text transformers that contain 'code' in their format are at the end of the array. Otherwise, formatted code like\n // <strong><code>code</code></strong> will be exported as `**Bold Code**`, as the code format will be applied first, and the bold format\n // will be applied second and thus skipped entirely, as the code format will prevent any further formatting.\n .sort((a, b) => {\n if (a.format.includes('code') && !b.format.includes('code')) {\n return 1\n } else if (!a.format.includes('code') && b.format.includes('code')) {\n return -1\n } else {\n return 0\n }\n })\n\n return (node) => {\n const output: string[] = []\n const children = (node || $getRoot()).getChildren()\n\n children.forEach((child, i) => {\n const result = exportTopLevelElements(\n child,\n elementTransformers,\n textFormatTransformers,\n byType.textMatch,\n )\n\n if (result != null) {\n output.push(\n // separate consecutive group of texts with a line break: eg. [\"hello\", \"world\"] -> [\"hello\", \"/nworld\"]\n isNewlineDelimited &&\n i > 0 &&\n !isEmptyParagraph(child) &&\n !isEmptyParagraph(children[i - 1]!)\n ? '\\n'.concat(result)\n : result,\n )\n }\n })\n // Ensure consecutive groups of texts are at least \\n\\n apart while each empty paragraph render as a newline.\n // Eg. [\"hello\", \"\", \"\", \"hi\", \"\\nworld\"] -> \"hello\\n\\n\\nhi\\n\\nworld\"\n return output.join('\\n')\n }\n}\n\nfunction exportTopLevelElements(\n node: LexicalNode,\n elementTransformers: Array<ElementTransformer | MultilineElementTransformer>,\n textTransformersIndex: Array<TextFormatTransformer>,\n textMatchTransformers: Array<TextMatchTransformer>,\n): null | string {\n for (const transformer of elementTransformers) {\n if (!transformer.export) {\n continue\n }\n const result = transformer.export(node, (_node) =>\n exportChildren(_node, textTransformersIndex, textMatchTransformers),\n )\n\n if (result != null) {\n return result\n }\n }\n\n if ($isElementNode(node)) {\n return exportChildren(node, textTransformersIndex, textMatchTransformers)\n } else if ($isDecoratorNode(node)) {\n return node.getTextContent()\n } else {\n return null\n }\n}\n\nfunction exportChildren(\n node: ElementNode,\n textTransformersIndex: Array<TextFormatTransformer>,\n textMatchTransformers: Array<TextMatchTransformer>,\n unclosedTags?: Array<{ format: TextFormatType; tag: string }>,\n unclosableTags?: Array<{ format: TextFormatType; tag: string }>,\n): string {\n const output = []\n const children = node.getChildren()\n // keep track of unclosed tags from the very beginning\n if (!unclosedTags) {\n unclosedTags = []\n }\n if (!unclosableTags) {\n unclosableTags = []\n }\n\n mainLoop: for (const child of children) {\n for (const transformer of textMatchTransformers) {\n if (!transformer.export) {\n continue\n }\n\n const result = transformer.export(\n child,\n (parentNode) =>\n exportChildren(\n parentNode,\n textTransformersIndex,\n textMatchTransformers,\n unclosedTags,\n // Add current unclosed tags to the list of unclosable tags - we don't want nested tags from\n // textmatch transformers to close the outer ones, as that may result in invalid markdown.\n // E.g. **text [text**](https://lexical.io)\n // is invalid markdown, as the closing ** is inside the link.\n //\n [...unclosableTags, ...unclosedTags],\n ),\n (textNode, textContent) =>\n exportTextFormat(\n textNode,\n textContent,\n textTransformersIndex,\n unclosedTags,\n unclosableTags,\n ),\n )\n\n if (result != null) {\n output.push(result)\n continue mainLoop\n }\n }\n\n if ($isLineBreakNode(child)) {\n output.push($exportLineBreak(child))\n } else if ($isTextNode(child)) {\n output.push(\n exportTextFormat(\n child,\n child.getTextContent(),\n textTransformersIndex,\n unclosedTags,\n unclosableTags,\n ),\n )\n } else if ($isElementNode(child)) {\n // empty paragraph returns \"\"\n output.push(\n exportChildren(\n child,\n textTransformersIndex,\n textMatchTransformers,\n unclosedTags,\n unclosableTags,\n ),\n )\n } else if ($isDecoratorNode(child)) {\n output.push(child.getTextContent())\n }\n }\n\n return output.join('')\n}\n\n// Prefixes the newline with the original CommonMark hard line break marker\n// (`\\` or trailing spaces) when one was preserved on import, otherwise exports a\n// plain soft line break.\nfunction $exportLineBreak(node: LineBreakNode): string {\n return $getState(node, hardLineBreakState) + '\\n'\n}\n\nfunction exportTextFormat(\n node: TextNode,\n textContent: string,\n textTransformers: Array<TextFormatTransformer>,\n // unclosed tags include the markdown tags that haven't been closed yet, and their associated formats\n unclosedTags: Array<{ format: TextFormatType; tag: string }>,\n unclosableTags?: Array<{ format: TextFormatType; tag: string }>,\n): string {\n // This function handles the case of a string looking like this: \" foo \"\n // Where it would be invalid markdown to generate: \"** foo **\"\n // We instead want to trim the whitespace out, apply formatting, and then\n // bring the whitespace back. So our returned string looks like this: \" **foo** \"\n const frozenString = textContent.trim()\n let output = frozenString\n\n if (!node.hasFormat('code')) {\n // Escape any markdown characters in the text content\n output = output.replace(/([*_`~\\\\])/g, '\\\\$1')\n }\n\n // the opening tags to be added to the result\n let openingTags = ''\n // the closing tags to be added to the result\n let closingTagsBefore = ''\n let closingTagsAfter = ''\n\n const prevNode = getTextSibling(node, true)\n const nextNode = getTextSibling(node, false)\n\n const applied = new Set()\n\n for (const transformer of textTransformers) {\n const format = transformer.format[0]!\n const tag = transformer.tag\n\n // dedup applied formats\n if (hasFormat(node, format) && !applied.has(format)) {\n // Multiple tags might be used for the same format (*, _)\n applied.add(format)\n\n // append the tag to openningTags, if it's not applied to the previous nodes,\n // or the nodes before that (which would result in an unclosed tag)\n if (!hasFormat(prevNode, format) || !unclosedTags.find((element) => element.tag === tag)) {\n unclosedTags.push({ format, tag })\n openingTags += tag\n }\n }\n }\n\n // close any tags in the same order they were applied, if necessary\n for (let i = 0; i < unclosedTags.length; i++) {\n const unclosedTag = unclosedTags[i]!\n const nodeHasFormat = hasFormat(node, unclosedTag.format)\n const nextNodeHasFormat = hasFormat(nextNode, unclosedTag.format)\n\n // prevent adding closing tag if next sibling will do it\n if (nodeHasFormat && nextNodeHasFormat) {\n continue\n }\n\n const unhandledUnclosedTags = [...unclosedTags] // Shallow copy to avoid modifying the original array\n\n while (unhandledUnclosedTags.length > i) {\n const unclosedTag = unhandledUnclosedTags.pop()\n\n // If tag is unclosable, don't close it and leave it in the original array,\n // So that it can be closed when it's no longer unclosable\n if (\n unclosableTags &&\n unclosedTag &&\n unclosableTags.find((element) => element.tag === unclosedTag.tag)\n ) {\n continue\n }\n\n if (unclosedTag && typeof unclosedTag.tag === 'string') {\n if (!nodeHasFormat) {\n // Handles cases where the tag has not been closed before, e.g. if the previous node\n // was a text match transformer that did not account for closing tags of the next node (e.g. a link)\n closingTagsBefore += unclosedTag.tag\n } else if (!nextNodeHasFormat) {\n closingTagsAfter += unclosedTag.tag\n }\n }\n // Mutate the original array to remove the closed tag\n unclosedTags.pop()\n }\n break\n }\n\n output = openingTags + output + closingTagsAfter\n // Replace trimmed version of textContent ensuring surrounding whitespace is not modified\n return closingTagsBefore + textContent.replace(frozenString, () => output)\n}\n\n// Get next or previous text sibling a text node, including cases\n// when it's a child of inline element (e.g. link)\nfunction getTextSibling(node: TextNode, backward: boolean): null | TextNode {\n let sibling = backward ? node.getPreviousSibling() : node.getNextSibling()\n\n if (!sibling) {\n const parent = node.getParentOrThrow()\n\n if (parent.isInline()) {\n sibling = backward ? parent.getPreviousSibling() : parent.getNextSibling()\n }\n }\n\n while (sibling) {\n if ($isElementNode(sibling)) {\n if (!sibling.isInline()) {\n break\n }\n\n const descendant = backward ? sibling.getLastDescendant() : sibling.getFirstDescendant()\n\n if ($isTextNode(descendant)) {\n return descendant\n } else {\n sibling = backward ? sibling.getPreviousSibling() : sibling.getNextSibling()\n }\n }\n\n if ($isTextNode(sibling)) {\n return sibling\n }\n\n if (!$isElementNode(sibling)) {\n return null\n }\n }\n\n return null\n}\n\nfunction hasFormat(node: LexicalNode | null | undefined, format: TextFormatType): boolean {\n return $isTextNode(node) && node.hasFormat(format)\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarkdownImport.d.ts","sourceRoot":"","sources":["../../../../src/packages/@lexical/markdown/MarkdownImport.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"MarkdownImport.d.ts","sourceRoot":"","sources":["../../../../src/packages/@lexical/markdown/MarkdownImport.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAa1C,OAAO,KAAK,EAGV,qBAAqB,EAErB,WAAW,EACZ,MAAM,2BAA2B,CAAA;AAMlC,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IACjD,oBAAoB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACtD,cAAc,EAAE,MAAM,CAAA;IACtB,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAA;CACnE,CAAC,CAAA;AAEF;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,KAAK,CAAC,WAAW,CAAC,EAChC,sBAAsB,UAAQ,GAC7B,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,IAAI,CAyCtD"}
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
*/import { $isListItemNode, $isListNode } from '@lexical/list';
|
|
8
8
|
import { $isQuoteNode } from '@lexical/rich-text';
|
|
9
9
|
import { $findMatchingParent } from '@lexical/utils';
|
|
10
|
-
import { $
|
|
10
|
+
import { $createParagraphNode, $createTextNode, $getRoot, $getSelection, $isParagraphNode } from 'lexical';
|
|
11
11
|
import { importTextTransformers } from './importTextTransformers.js';
|
|
12
|
+
import { $createMarkdownLineBreakNode } from './MarkdownTransformers.js';
|
|
12
13
|
import { isEmptyParagraph, transformersByType } from './utils.js';
|
|
13
14
|
/**
|
|
14
15
|
* Renders markdown from a string. The selection is moved to the start after the operation.
|
|
@@ -161,7 +162,7 @@ function $importBlocks(lineText, rootNode, elementTransformers, textFormatTransf
|
|
|
161
162
|
}
|
|
162
163
|
}
|
|
163
164
|
if (targetNode != null && targetNode.getTextContentSize() > 0) {
|
|
164
|
-
targetNode.splice(targetNode.getChildrenSize(), 0, [$
|
|
165
|
+
targetNode.splice(targetNode.getChildrenSize(), 0, [$createMarkdownLineBreakNode(targetNode), ...elementNode.getChildren()]);
|
|
165
166
|
elementNode.remove();
|
|
166
167
|
}
|
|
167
168
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarkdownImport.js","names":["$isListItemNode","$isListNode","$isQuoteNode","$findMatchingParent","$createLineBreakNode","$createParagraphNode","$createTextNode","$getRoot","$getSelection","$isParagraphNode","importTextTransformers","isEmptyParagraph","transformersByType","createMarkdownImport","transformers","shouldPreserveNewLines","byType","textFormatTransformersIndex","createTextFormatTransformersIndex","textFormat","markdownString","node","lines","split","linesLength","length","root","clear","i","lineText","imported","shiftedIndex","$importMultiline","multilineElement","$importBlocks","element","textMatch","children","getChildren","child","getChildrenSize","remove","selectStart","startLineIndex","multilineElementTransformers","rootNode","transformer","handleImportAfterStartMatch","regExpEnd","regExpStart","replace","startMatch","match","result","regexpEndRegex","regExp","isEndOptional","optional","endLineIndex","endMatch","index","linesInBetween","push","slice","line","text","elementTransformers","textMatchTransformers","textNode","elementNode","append","setTextContent","isAttached","previousNode","getPreviousSibling","targetNode","lastDescendant","getLastDescendant","getTextContentSize","splice","textTransformers","transformersByTag","fullMatchRegExpByTag","openTagsRegExp","escapeRegExp","tag","tagRegExp","RegExp","join"],"sources":["../../../../src/packages/@lexical/markdown/MarkdownImport.ts"],"sourcesContent":["/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport type { ListItemNode } from '@lexical/list'\nimport type { ElementNode } from 'lexical'\n\nimport { $isListItemNode, $isListNode } from '@lexical/list'\nimport { $isQuoteNode } from '@lexical/rich-text'\nimport { $findMatchingParent } from '@lexical/utils'\nimport {\n $createLineBreakNode,\n $createParagraphNode,\n $createTextNode,\n $getRoot,\n $getSelection,\n $isParagraphNode,\n} from 'lexical'\n\nimport type {\n ElementTransformer,\n MultilineElementTransformer,\n TextFormatTransformer,\n TextMatchTransformer,\n Transformer,\n} from './MarkdownTransformers.js'\n\nimport { importTextTransformers } from './importTextTransformers.js'\nimport { isEmptyParagraph, transformersByType } from './utils.js'\n\nexport type TextFormatTransformersIndex = Readonly<{\n fullMatchRegExpByTag: Readonly<Record<string, RegExp>>\n openTagsRegExp: RegExp\n transformersByTag: Readonly<Record<string, TextFormatTransformer>>\n}>\n\n/**\n * Renders markdown from a string. The selection is moved to the start after the operation.\n */\nexport function createMarkdownImport(\n transformers: Array<Transformer>,\n shouldPreserveNewLines = false,\n): (markdownString: string, node?: ElementNode) => void {\n const byType = transformersByType(transformers)\n const textFormatTransformersIndex = createTextFormatTransformersIndex(byType.textFormat)\n\n return (markdownString, node) => {\n const lines = markdownString.split('\\n')\n const linesLength = lines.length\n const root = node || $getRoot()\n root.clear()\n\n for (let i = 0; i < linesLength; i++) {\n const lineText = lines[i]!\n\n const [imported, shiftedIndex] = $importMultiline(lines, i, byType.multilineElement, root)\n\n if (imported) {\n // If a multiline markdown element was imported, we don't want to process the lines that were part of it anymore.\n // There could be other sub-markdown elements (both multiline and normal ones) matching within this matched multiline element's children.\n // However, it would be the responsibility of the matched multiline transformer to decide how it wants to handle them.\n // We cannot handle those, as there is no way for us to know how to maintain the correct order of generated lexical nodes for possible children.\n i = shiftedIndex // Next loop will start from the line after the last line of the multiline element\n continue\n }\n\n $importBlocks(lineText, root, byType.element, textFormatTransformersIndex, byType.textMatch)\n }\n\n // By default, removing empty paragraphs as md does not really\n // allow empty lines and uses them as delimiter.\n // If you need empty lines set shouldPreserveNewLines = true.\n const children = root.getChildren()\n for (const child of children) {\n if (!shouldPreserveNewLines && isEmptyParagraph(child) && root.getChildrenSize() > 1) {\n child.remove()\n }\n }\n\n if ($getSelection() !== null) {\n root.selectStart()\n }\n }\n}\n\n/**\n *\n * @returns first element of the returned tuple is a boolean indicating if a multiline element was imported. The second element is the index of the last line that was processed.\n */\nfunction $importMultiline(\n lines: Array<string>,\n startLineIndex: number,\n multilineElementTransformers: Array<MultilineElementTransformer>,\n rootNode: ElementNode,\n): [boolean, number] {\n for (const transformer of multilineElementTransformers) {\n const { handleImportAfterStartMatch, regExpEnd, regExpStart, replace } = transformer\n\n const startMatch = lines[startLineIndex]?.match(regExpStart)\n if (!startMatch) {\n continue // Try next transformer\n }\n\n if (handleImportAfterStartMatch) {\n const result = handleImportAfterStartMatch({\n lines,\n rootNode,\n startLineIndex,\n startMatch,\n transformer,\n })\n if (result === null) {\n continue\n } else if (result) {\n return result\n }\n }\n\n const regexpEndRegex: RegExp | undefined =\n typeof regExpEnd === 'object' && 'regExp' in regExpEnd ? regExpEnd.regExp : regExpEnd\n\n const isEndOptional =\n regExpEnd && typeof regExpEnd === 'object' && 'optional' in regExpEnd\n ? regExpEnd.optional\n : !regExpEnd\n\n let endLineIndex = startLineIndex\n const linesLength = lines.length\n\n // check every single line for the closing match. It could also be on the same line as the opening match.\n while (endLineIndex < linesLength) {\n const endMatch = regexpEndRegex ? lines[endLineIndex]?.match(regexpEndRegex) : null\n if (!endMatch) {\n if (\n !isEndOptional ||\n (isEndOptional && endLineIndex < linesLength - 1) // Optional end, but didn't reach the end of the document yet => continue searching for potential closing match\n ) {\n endLineIndex++\n continue // Search next line for closing match\n }\n }\n\n // Now, check if the closing match matched is the same as the opening match.\n // If it is, we need to continue searching for the actual closing match.\n if (endMatch && startLineIndex === endLineIndex && endMatch.index === startMatch.index) {\n endLineIndex++\n continue // Search next line for closing match\n }\n\n // At this point, we have found the closing match. Next: calculate the lines in between open and closing match\n // This should not include the matches themselves, and be split up by lines\n const linesInBetween: string[] = []\n\n if (endMatch && startLineIndex === endLineIndex) {\n linesInBetween.push(lines[startLineIndex]!.slice(startMatch[0].length, -endMatch[0].length))\n } else {\n for (let i = startLineIndex; i <= endLineIndex; i++) {\n const line = lines[i]!\n if (i === startLineIndex) {\n const text = line.slice(startMatch[0].length)\n linesInBetween.push(text) // Also include empty text\n } else if (i === endLineIndex && endMatch) {\n const text = line.slice(0, -endMatch[0].length)\n linesInBetween.push(text) // Also include empty text\n } else {\n linesInBetween.push(line)\n }\n }\n }\n\n if (replace(rootNode, null, startMatch, endMatch!, linesInBetween, true) !== false) {\n // Return here. This $importMultiline function is run line by line and should only process a single multiline element at a time.\n return [true, endLineIndex]\n }\n\n // The replace function returned false, despite finding the matching open and close tags => this transformer does not want to handle it.\n // Thus, we continue letting the remaining transformers handle the passed lines of text from the beginning\n break\n }\n }\n\n // No multiline transformer handled this line successfully\n return [false, startLineIndex]\n}\n\nfunction $importBlocks(\n lineText: string,\n rootNode: ElementNode,\n elementTransformers: Array<ElementTransformer>,\n textFormatTransformersIndex: TextFormatTransformersIndex,\n textMatchTransformers: Array<TextMatchTransformer>,\n) {\n const textNode = $createTextNode(lineText)\n const elementNode = $createParagraphNode()\n elementNode.append(textNode)\n rootNode.append(elementNode)\n\n for (const { regExp, replace } of elementTransformers) {\n const match = lineText.match(regExp)\n\n if (match) {\n textNode.setTextContent(lineText.slice(match[0].length))\n if (replace(elementNode, [textNode], match, true) !== false) {\n break\n }\n }\n }\n\n importTextTransformers(textNode, textFormatTransformersIndex, textMatchTransformers)\n\n // If no transformer found and we left with original paragraph node\n // can check if its content can be appended to the previous node\n // if it's a paragraph, quote or list\n if (elementNode.isAttached() && lineText.length > 0) {\n const previousNode = elementNode.getPreviousSibling()\n if ($isParagraphNode(previousNode) || $isQuoteNode(previousNode) || $isListNode(previousNode)) {\n let targetNode: ListItemNode | null | typeof previousNode = previousNode\n\n if ($isListNode(previousNode)) {\n const lastDescendant = previousNode.getLastDescendant()\n if (lastDescendant == null) {\n targetNode = null\n } else {\n targetNode = $findMatchingParent(lastDescendant, $isListItemNode)\n }\n }\n\n if (targetNode != null && targetNode.getTextContentSize() > 0) {\n targetNode.splice(targetNode.getChildrenSize(), 0, [\n $createLineBreakNode(),\n ...elementNode.getChildren(),\n ])\n elementNode.remove()\n }\n }\n }\n}\n\nfunction createTextFormatTransformersIndex(\n textTransformers: Array<TextFormatTransformer>,\n): TextFormatTransformersIndex {\n const transformersByTag: Record<string, TextFormatTransformer> = {}\n const fullMatchRegExpByTag: Record<string, RegExp> = {}\n const openTagsRegExp: string[] = []\n const escapeRegExp = `(?<![\\\\\\\\])`\n\n for (const transformer of textTransformers) {\n const { tag } = transformer\n transformersByTag[tag] = transformer\n const tagRegExp = tag.replace(/([*^+])/g, '\\\\$1')\n openTagsRegExp.push(tagRegExp)\n\n // Single-char tag (e.g. \"*\"),\n if (tag.length === 1) {\n fullMatchRegExpByTag[tag] = new RegExp(\n `(?<![\\\\\\\\${tagRegExp}])(${tagRegExp})((\\\\\\\\${tagRegExp})?.*?[^${tagRegExp}\\\\s](\\\\\\\\${tagRegExp})?)((?<!\\\\\\\\)|(?<=\\\\\\\\\\\\\\\\))(${tagRegExp})(?![\\\\\\\\${tagRegExp}])`,\n )\n } else {\n // Multi‐char tags (e.g. \"**\")\n fullMatchRegExpByTag[tag] = new RegExp(\n `(?<!\\\\\\\\)(${tagRegExp})((\\\\\\\\${tagRegExp})?.*?[^\\\\s](\\\\\\\\${tagRegExp})?)((?<!\\\\\\\\)|(?<=\\\\\\\\\\\\\\\\))(${tagRegExp})(?!\\\\\\\\)`,\n )\n }\n }\n\n return {\n // Reg exp to find open tag + content + close tag\n fullMatchRegExpByTag,\n\n // Regexp to locate *any* potential opening tag (longest first).\n openTagsRegExp: new RegExp(`${escapeRegExp}(${openTagsRegExp.join('|')})`, 'g'),\n transformersByTag,\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"MarkdownImport.js","names":["$isListItemNode","$isListNode","$isQuoteNode","$findMatchingParent","$createParagraphNode","$createTextNode","$getRoot","$getSelection","$isParagraphNode","importTextTransformers","$createMarkdownLineBreakNode","isEmptyParagraph","transformersByType","createMarkdownImport","transformers","shouldPreserveNewLines","byType","textFormatTransformersIndex","createTextFormatTransformersIndex","textFormat","markdownString","node","lines","split","linesLength","length","root","clear","i","lineText","imported","shiftedIndex","$importMultiline","multilineElement","$importBlocks","element","textMatch","children","getChildren","child","getChildrenSize","remove","selectStart","startLineIndex","multilineElementTransformers","rootNode","transformer","handleImportAfterStartMatch","regExpEnd","regExpStart","replace","startMatch","match","result","regexpEndRegex","regExp","isEndOptional","optional","endLineIndex","endMatch","index","linesInBetween","push","slice","line","text","elementTransformers","textMatchTransformers","textNode","elementNode","append","setTextContent","isAttached","previousNode","getPreviousSibling","targetNode","lastDescendant","getLastDescendant","getTextContentSize","splice","textTransformers","transformersByTag","fullMatchRegExpByTag","openTagsRegExp","escapeRegExp","tag","tagRegExp","RegExp","join"],"sources":["../../../../src/packages/@lexical/markdown/MarkdownImport.ts"],"sourcesContent":["/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport type { ListItemNode } from '@lexical/list'\nimport type { ElementNode } from 'lexical'\n\nimport { $isListItemNode, $isListNode } from '@lexical/list'\nimport { $isQuoteNode } from '@lexical/rich-text'\nimport { $findMatchingParent } from '@lexical/utils'\nimport {\n $createParagraphNode,\n $createTextNode,\n $getRoot,\n $getSelection,\n $isParagraphNode,\n} from 'lexical'\n\nimport type {\n ElementTransformer,\n MultilineElementTransformer,\n TextFormatTransformer,\n TextMatchTransformer,\n Transformer,\n} from './MarkdownTransformers.js'\n\nimport { importTextTransformers } from './importTextTransformers.js'\nimport { $createMarkdownLineBreakNode } from './MarkdownTransformers.js'\nimport { isEmptyParagraph, transformersByType } from './utils.js'\n\nexport type TextFormatTransformersIndex = Readonly<{\n fullMatchRegExpByTag: Readonly<Record<string, RegExp>>\n openTagsRegExp: RegExp\n transformersByTag: Readonly<Record<string, TextFormatTransformer>>\n}>\n\n/**\n * Renders markdown from a string. The selection is moved to the start after the operation.\n */\nexport function createMarkdownImport(\n transformers: Array<Transformer>,\n shouldPreserveNewLines = false,\n): (markdownString: string, node?: ElementNode) => void {\n const byType = transformersByType(transformers)\n const textFormatTransformersIndex = createTextFormatTransformersIndex(byType.textFormat)\n\n return (markdownString, node) => {\n const lines = markdownString.split('\\n')\n const linesLength = lines.length\n const root = node || $getRoot()\n root.clear()\n\n for (let i = 0; i < linesLength; i++) {\n const lineText = lines[i]!\n\n const [imported, shiftedIndex] = $importMultiline(lines, i, byType.multilineElement, root)\n\n if (imported) {\n // If a multiline markdown element was imported, we don't want to process the lines that were part of it anymore.\n // There could be other sub-markdown elements (both multiline and normal ones) matching within this matched multiline element's children.\n // However, it would be the responsibility of the matched multiline transformer to decide how it wants to handle them.\n // We cannot handle those, as there is no way for us to know how to maintain the correct order of generated lexical nodes for possible children.\n i = shiftedIndex // Next loop will start from the line after the last line of the multiline element\n continue\n }\n\n $importBlocks(lineText, root, byType.element, textFormatTransformersIndex, byType.textMatch)\n }\n\n // By default, removing empty paragraphs as md does not really\n // allow empty lines and uses them as delimiter.\n // If you need empty lines set shouldPreserveNewLines = true.\n const children = root.getChildren()\n for (const child of children) {\n if (!shouldPreserveNewLines && isEmptyParagraph(child) && root.getChildrenSize() > 1) {\n child.remove()\n }\n }\n\n if ($getSelection() !== null) {\n root.selectStart()\n }\n }\n}\n\n/**\n *\n * @returns first element of the returned tuple is a boolean indicating if a multiline element was imported. The second element is the index of the last line that was processed.\n */\nfunction $importMultiline(\n lines: Array<string>,\n startLineIndex: number,\n multilineElementTransformers: Array<MultilineElementTransformer>,\n rootNode: ElementNode,\n): [boolean, number] {\n for (const transformer of multilineElementTransformers) {\n const { handleImportAfterStartMatch, regExpEnd, regExpStart, replace } = transformer\n\n const startMatch = lines[startLineIndex]?.match(regExpStart)\n if (!startMatch) {\n continue // Try next transformer\n }\n\n if (handleImportAfterStartMatch) {\n const result = handleImportAfterStartMatch({\n lines,\n rootNode,\n startLineIndex,\n startMatch,\n transformer,\n })\n if (result === null) {\n continue\n } else if (result) {\n return result\n }\n }\n\n const regexpEndRegex: RegExp | undefined =\n typeof regExpEnd === 'object' && 'regExp' in regExpEnd ? regExpEnd.regExp : regExpEnd\n\n const isEndOptional =\n regExpEnd && typeof regExpEnd === 'object' && 'optional' in regExpEnd\n ? regExpEnd.optional\n : !regExpEnd\n\n let endLineIndex = startLineIndex\n const linesLength = lines.length\n\n // check every single line for the closing match. It could also be on the same line as the opening match.\n while (endLineIndex < linesLength) {\n const endMatch = regexpEndRegex ? lines[endLineIndex]?.match(regexpEndRegex) : null\n if (!endMatch) {\n if (\n !isEndOptional ||\n (isEndOptional && endLineIndex < linesLength - 1) // Optional end, but didn't reach the end of the document yet => continue searching for potential closing match\n ) {\n endLineIndex++\n continue // Search next line for closing match\n }\n }\n\n // Now, check if the closing match matched is the same as the opening match.\n // If it is, we need to continue searching for the actual closing match.\n if (endMatch && startLineIndex === endLineIndex && endMatch.index === startMatch.index) {\n endLineIndex++\n continue // Search next line for closing match\n }\n\n // At this point, we have found the closing match. Next: calculate the lines in between open and closing match\n // This should not include the matches themselves, and be split up by lines\n const linesInBetween: string[] = []\n\n if (endMatch && startLineIndex === endLineIndex) {\n linesInBetween.push(lines[startLineIndex]!.slice(startMatch[0].length, -endMatch[0].length))\n } else {\n for (let i = startLineIndex; i <= endLineIndex; i++) {\n const line = lines[i]!\n if (i === startLineIndex) {\n const text = line.slice(startMatch[0].length)\n linesInBetween.push(text) // Also include empty text\n } else if (i === endLineIndex && endMatch) {\n const text = line.slice(0, -endMatch[0].length)\n linesInBetween.push(text) // Also include empty text\n } else {\n linesInBetween.push(line)\n }\n }\n }\n\n if (replace(rootNode, null, startMatch, endMatch!, linesInBetween, true) !== false) {\n // Return here. This $importMultiline function is run line by line and should only process a single multiline element at a time.\n return [true, endLineIndex]\n }\n\n // The replace function returned false, despite finding the matching open and close tags => this transformer does not want to handle it.\n // Thus, we continue letting the remaining transformers handle the passed lines of text from the beginning\n break\n }\n }\n\n // No multiline transformer handled this line successfully\n return [false, startLineIndex]\n}\n\nfunction $importBlocks(\n lineText: string,\n rootNode: ElementNode,\n elementTransformers: Array<ElementTransformer>,\n textFormatTransformersIndex: TextFormatTransformersIndex,\n textMatchTransformers: Array<TextMatchTransformer>,\n) {\n const textNode = $createTextNode(lineText)\n const elementNode = $createParagraphNode()\n elementNode.append(textNode)\n rootNode.append(elementNode)\n\n for (const { regExp, replace } of elementTransformers) {\n const match = lineText.match(regExp)\n\n if (match) {\n textNode.setTextContent(lineText.slice(match[0].length))\n if (replace(elementNode, [textNode], match, true) !== false) {\n break\n }\n }\n }\n\n importTextTransformers(textNode, textFormatTransformersIndex, textMatchTransformers)\n\n // If no transformer found and we left with original paragraph node\n // can check if its content can be appended to the previous node\n // if it's a paragraph, quote or list\n if (elementNode.isAttached() && lineText.length > 0) {\n const previousNode = elementNode.getPreviousSibling()\n if ($isParagraphNode(previousNode) || $isQuoteNode(previousNode) || $isListNode(previousNode)) {\n let targetNode: ListItemNode | null | typeof previousNode = previousNode\n\n if ($isListNode(previousNode)) {\n const lastDescendant = previousNode.getLastDescendant()\n if (lastDescendant == null) {\n targetNode = null\n } else {\n targetNode = $findMatchingParent(lastDescendant, $isListItemNode)\n }\n }\n\n if (targetNode != null && targetNode.getTextContentSize() > 0) {\n targetNode.splice(targetNode.getChildrenSize(), 0, [\n $createMarkdownLineBreakNode(targetNode),\n ...elementNode.getChildren(),\n ])\n elementNode.remove()\n }\n }\n }\n}\n\nfunction createTextFormatTransformersIndex(\n textTransformers: Array<TextFormatTransformer>,\n): TextFormatTransformersIndex {\n const transformersByTag: Record<string, TextFormatTransformer> = {}\n const fullMatchRegExpByTag: Record<string, RegExp> = {}\n const openTagsRegExp: string[] = []\n const escapeRegExp = `(?<![\\\\\\\\])`\n\n for (const transformer of textTransformers) {\n const { tag } = transformer\n transformersByTag[tag] = transformer\n const tagRegExp = tag.replace(/([*^+])/g, '\\\\$1')\n openTagsRegExp.push(tagRegExp)\n\n // Single-char tag (e.g. \"*\"),\n if (tag.length === 1) {\n fullMatchRegExpByTag[tag] = new RegExp(\n `(?<![\\\\\\\\${tagRegExp}])(${tagRegExp})((\\\\\\\\${tagRegExp})?.*?[^${tagRegExp}\\\\s](\\\\\\\\${tagRegExp})?)((?<!\\\\\\\\)|(?<=\\\\\\\\\\\\\\\\))(${tagRegExp})(?![\\\\\\\\${tagRegExp}])`,\n )\n } else {\n // Multi‐char tags (e.g. \"**\")\n fullMatchRegExpByTag[tag] = new RegExp(\n `(?<!\\\\\\\\)(${tagRegExp})((\\\\\\\\${tagRegExp})?.*?[^\\\\s](\\\\\\\\${tagRegExp})?)((?<!\\\\\\\\)|(?<=\\\\\\\\\\\\\\\\))(${tagRegExp})(?!\\\\\\\\)`,\n )\n }\n }\n\n return {\n // Reg exp to find open tag + content + close tag\n fullMatchRegExpByTag,\n\n // Regexp to locate *any* potential opening tag (longest first).\n openTagsRegExp: new RegExp(`${escapeRegExp}(${openTagsRegExp.join('|')})`, 'g'),\n transformersByTag,\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
|
-
import type { ElementNode, Klass, LexicalNode, TextFormatType, TextNode } from 'lexical';
|
|
8
|
+
import type { ElementNode, Klass, LexicalNode, LineBreakNode, TextFormatType, TextNode } from 'lexical';
|
|
9
9
|
export type Transformer = ElementTransformer | MultilineElementTransformer | TextFormatTransformer | TextMatchTransformer;
|
|
10
10
|
export type ElementTransformer = {
|
|
11
11
|
dependencies: Array<Klass<LexicalNode>>;
|
|
@@ -129,6 +129,32 @@ export type TextMatchTransformer = Readonly<{
|
|
|
129
129
|
trigger?: string;
|
|
130
130
|
type: 'text-match';
|
|
131
131
|
}>;
|
|
132
|
+
/**
|
|
133
|
+
* A CommonMark hard line break marker: either a single backslash (`\`) or two or
|
|
134
|
+
* more trailing spaces, both placed at the end of a line before the newline.
|
|
135
|
+
* See https://spec.commonmark.org/0.31.2/#hard-line-breaks
|
|
136
|
+
*/
|
|
137
|
+
export type MarkdownHardLineBreak = string;
|
|
138
|
+
/**
|
|
139
|
+
* Stores the original hard line break marker on a `LineBreakNode` so it can be
|
|
140
|
+
* preserved across a markdown import/export round trip instead of being
|
|
141
|
+
* collapsed into a soft line break.
|
|
142
|
+
*
|
|
143
|
+
* Note: unlike upstream Lexical (which sets `resetOnCopyNode: true`), that option
|
|
144
|
+
* does not exist in the `lexical` version pinned here (0.41.0), so it is omitted.
|
|
145
|
+
*/
|
|
146
|
+
export declare const hardLineBreakState: import("lexical").StateConfig<"mdHardLineBreak", string>;
|
|
147
|
+
/**
|
|
148
|
+
* Detects a CommonMark hard line break marker at the end of a raw line.
|
|
149
|
+
*
|
|
150
|
+
* @returns a tuple of `[textWithoutMarker, marker]` when a marker is found, otherwise null.
|
|
151
|
+
*/
|
|
152
|
+
export declare function parseMarkdownHardLineBreak(line: string): [string, MarkdownHardLineBreak] | null;
|
|
153
|
+
/**
|
|
154
|
+
* Creates a `LineBreakNode` for a markdown line join, preserving any CommonMark
|
|
155
|
+
* hard line break marker found at the end of `previousNode` via node state.
|
|
156
|
+
*/
|
|
157
|
+
export declare function $createMarkdownLineBreakNode(previousNode: ElementNode): LineBreakNode;
|
|
132
158
|
export declare const HEADING: ElementTransformer;
|
|
133
159
|
export declare const QUOTE: ElementTransformer;
|
|
134
160
|
export declare const UNORDERED_LIST: ElementTransformer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarkdownTransformers.d.ts","sourceRoot":"","sources":["../../../../src/packages/@lexical/markdown/MarkdownTransformers.ts"],"names":[],"mappings":"AACA;;;;;;GAMG;AAIH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"MarkdownTransformers.d.ts","sourceRoot":"","sources":["../../../../src/packages/@lexical/markdown/MarkdownTransformers.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/import { $createListItemNode, $createListNode, $isListItemNode, $isListNode, ListItemNode, ListNode } from '@lexical/list';
|
|
8
8
|
import { $createHeadingNode, $createQuoteNode, $isHeadingNode, $isQuoteNode, HeadingNode, QuoteNode } from '@lexical/rich-text';
|
|
9
|
-
import { $createLineBreakNode } from 'lexical';
|
|
9
|
+
import { $createLineBreakNode, $isLineBreakNode, $isTextNode, $setState, createState } from 'lexical';
|
|
10
10
|
const EMPTY_OR_WHITESPACE_ONLY = /^[\t ]*$/;
|
|
11
11
|
const ORDERED_LIST_REGEX = /^(\s*)(\d+)\.\s/;
|
|
12
12
|
const UNORDERED_LIST_REGEX = /^(\s*)[-*+]\s/;
|
|
@@ -20,6 +20,84 @@ const TABLE_ROW_REG_EXP = /^\|(.+)\|\s?$/;
|
|
|
20
20
|
const TABLE_ROW_DIVIDER_REG_EXP = /^(\| ?:?-*:? ?)+\|\s?$/;
|
|
21
21
|
const TAG_START_REGEX = /^[ \t]*<[a-z_][\w-]*(?:\s[^<>]*)?\/?>/i;
|
|
22
22
|
const TAG_END_REGEX = /^[ \t]*<\/[a-z_][\w-]*\s*>/i;
|
|
23
|
+
/**
|
|
24
|
+
* Stores the original hard line break marker on a `LineBreakNode` so it can be
|
|
25
|
+
* preserved across a markdown import/export round trip instead of being
|
|
26
|
+
* collapsed into a soft line break.
|
|
27
|
+
*
|
|
28
|
+
* Note: unlike upstream Lexical (which sets `resetOnCopyNode: true`), that option
|
|
29
|
+
* does not exist in the `lexical` version pinned here (0.41.0), so it is omitted.
|
|
30
|
+
*/
|
|
31
|
+
export const hardLineBreakState = createState('mdHardLineBreak', {
|
|
32
|
+
parse: val => {
|
|
33
|
+
if (typeof val === 'string' && /^(\\| {2,})$/.test(val)) {
|
|
34
|
+
return val;
|
|
35
|
+
}
|
|
36
|
+
return '';
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* Detects a CommonMark hard line break marker at the end of a raw line.
|
|
41
|
+
*
|
|
42
|
+
* @returns a tuple of `[textWithoutMarker, marker]` when a marker is found, otherwise null.
|
|
43
|
+
*/
|
|
44
|
+
export function parseMarkdownHardLineBreak(line) {
|
|
45
|
+
if (line.endsWith('\\')) {
|
|
46
|
+
return [line.slice(0, -1), '\\'];
|
|
47
|
+
}
|
|
48
|
+
const spaces = line.match(/^(.*?\S)( {2,})$/);
|
|
49
|
+
return spaces ? [spaces[1], spaces[2]] : null;
|
|
50
|
+
}
|
|
51
|
+
function hasNonWhitespaceContentOnLine(children, endIndex) {
|
|
52
|
+
for (let i = endIndex - 1; i >= 0; i--) {
|
|
53
|
+
if ($isLineBreakNode(children[i])) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
if (/\S/.test(children[i].getTextContent())) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Removes a trailing hard line break marker from the last text node of `previousNode`
|
|
64
|
+
* (if present) and returns the marker, so it can be carried onto a `LineBreakNode`.
|
|
65
|
+
*/
|
|
66
|
+
function $extractMarkdownHardLineBreakMarker(previousNode) {
|
|
67
|
+
const children = previousNode.getChildren();
|
|
68
|
+
const lastChildIndex = children.length - 1;
|
|
69
|
+
const lastChild = children[lastChildIndex];
|
|
70
|
+
if (!$isTextNode(lastChild)) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const lastText = lastChild.getTextContent();
|
|
74
|
+
const hardLineBreak = parseMarkdownHardLineBreak(lastText);
|
|
75
|
+
if (hardLineBreak !== null) {
|
|
76
|
+
const [text, marker] = hardLineBreak;
|
|
77
|
+
lastChild.setTextContent(text);
|
|
78
|
+
return marker;
|
|
79
|
+
}
|
|
80
|
+
// Trailing whitespace may have been split into its own text node by inline
|
|
81
|
+
// transformers (e.g. after formatted or linked content). Only treat it as a
|
|
82
|
+
// hard break when there is preceding non-whitespace content on the same line.
|
|
83
|
+
if (/^ {2,}$/.test(lastText) && hasNonWhitespaceContentOnLine(children, lastChildIndex)) {
|
|
84
|
+
lastChild.setTextContent('');
|
|
85
|
+
return lastText;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Creates a `LineBreakNode` for a markdown line join, preserving any CommonMark
|
|
91
|
+
* hard line break marker found at the end of `previousNode` via node state.
|
|
92
|
+
*/
|
|
93
|
+
export function $createMarkdownLineBreakNode(previousNode) {
|
|
94
|
+
const lineBreakNode = $createLineBreakNode();
|
|
95
|
+
const hardLineBreak = $extractMarkdownHardLineBreakMarker(previousNode);
|
|
96
|
+
if (hardLineBreak !== null) {
|
|
97
|
+
$setState(lineBreakNode, hardLineBreakState, hardLineBreak);
|
|
98
|
+
}
|
|
99
|
+
return lineBreakNode;
|
|
100
|
+
}
|
|
23
101
|
const createBlockNode = createNode => {
|
|
24
102
|
return (parentNode, children, match) => {
|
|
25
103
|
const node = createNode(match);
|
|
@@ -130,7 +208,7 @@ export const QUOTE = {
|
|
|
130
208
|
if (isImport) {
|
|
131
209
|
const previousNode = parentNode.getPreviousSibling();
|
|
132
210
|
if ($isQuoteNode(previousNode)) {
|
|
133
|
-
previousNode.splice(previousNode.getChildrenSize(), 0, [$
|
|
211
|
+
previousNode.splice(previousNode.getChildrenSize(), 0, [$createMarkdownLineBreakNode(previousNode), ...children]);
|
|
134
212
|
previousNode.select(0, 0);
|
|
135
213
|
parentNode.remove();
|
|
136
214
|
return;
|
|
@@ -225,6 +303,9 @@ export function normalizeMarkdown(input, shouldMergeAdjacentLines) {
|
|
|
225
303
|
for (let i = 0; i < lines.length; i++) {
|
|
226
304
|
const line = lines[i];
|
|
227
305
|
const lastLine = sanitizedLines[sanitizedLines.length - 1];
|
|
306
|
+
// A hard line break marker on the current line only matters if another line follows it.
|
|
307
|
+
const hardLineBreak = i < lines.length - 1 ? parseMarkdownHardLineBreak(line) : null;
|
|
308
|
+
const lastLineHasHardLineBreak = lastLine !== undefined && parseMarkdownHardLineBreak(lastLine) !== null;
|
|
228
309
|
// Code blocks of ```single line``` don't toggle the inCodeBlock flag
|
|
229
310
|
if (CODE_SINGLE_LINE_REGEX.test(line)) {
|
|
230
311
|
sanitizedLines.push(line);
|
|
@@ -257,10 +338,13 @@ export function normalizeMarkdown(input, shouldMergeAdjacentLines) {
|
|
|
257
338
|
}
|
|
258
339
|
// In markdown the concept of "empty paragraphs" does not exist.
|
|
259
340
|
// Blocks must be separated by an empty line. Non-empty adjacent lines must be merged.
|
|
260
|
-
if (EMPTY_OR_WHITESPACE_ONLY.test(line) || EMPTY_OR_WHITESPACE_ONLY.test(lastLine) || !lastLine || HEADING_REGEX.test(lastLine) || HEADING_REGEX.test(line) || QUOTE_REGEX.test(line) || ORDERED_LIST_REGEX.test(line) || UNORDERED_LIST_REGEX.test(line) || CHECK_LIST_REGEX.test(line) || TABLE_ROW_REG_EXP.test(line) || TABLE_ROW_DIVIDER_REG_EXP.test(line) ||
|
|
341
|
+
if (EMPTY_OR_WHITESPACE_ONLY.test(line) || EMPTY_OR_WHITESPACE_ONLY.test(lastLine) || !lastLine || HEADING_REGEX.test(lastLine) || HEADING_REGEX.test(line) || QUOTE_REGEX.test(line) || ORDERED_LIST_REGEX.test(line) || UNORDERED_LIST_REGEX.test(line) || CHECK_LIST_REGEX.test(line) || TABLE_ROW_REG_EXP.test(line) || TABLE_ROW_DIVIDER_REG_EXP.test(line) ||
|
|
342
|
+
// Don't merge the next line into a line that ends with a hard line break marker.
|
|
343
|
+
lastLineHasHardLineBreak || !shouldMergeAdjacentLines || TAG_START_REGEX.test(line) || TAG_END_REGEX.test(line) || TAG_START_REGEX.test(lastLine) || TAG_END_REGEX.test(lastLine) || CODE_END_REGEX.test(lastLine)) {
|
|
261
344
|
sanitizedLines.push(line);
|
|
262
345
|
} else {
|
|
263
|
-
|
|
346
|
+
// Preserve a trailing hard line break marker on the merged line so it survives import.
|
|
347
|
+
sanitizedLines[sanitizedLines.length - 1] = lastLine + ' ' + (hardLineBreak === null ? line.trim() : line.trimStart());
|
|
264
348
|
}
|
|
265
349
|
}
|
|
266
350
|
return sanitizedLines.join('\n');
|