From f1ef885663807cc0f4e2a24dc2e7aeeae616a51d Mon Sep 17 00:00:00 2001 From: tak-amboss Date: Wed, 2 Apr 2025 10:41:28 +0200 Subject: [PATCH] add missing line-breaks in lexical to plaintext conversion --- .../convertLexicalToPlaintext.spec.ts | 148 +++++++++++++++++- .../lexicalToPlaintext/sync/index.ts | 13 +- 2 files changed, 158 insertions(+), 3 deletions(-) diff --git a/packages/richtext-lexical/src/features/converters/lexicalToPlaintext/convertLexicalToPlaintext.spec.ts b/packages/richtext-lexical/src/features/converters/lexicalToPlaintext/convertLexicalToPlaintext.spec.ts index eaf53b96f9f..8559b78712a 100644 --- a/packages/richtext-lexical/src/features/converters/lexicalToPlaintext/convertLexicalToPlaintext.spec.ts +++ b/packages/richtext-lexical/src/features/converters/lexicalToPlaintext/convertLexicalToPlaintext.spec.ts @@ -5,6 +5,12 @@ import type { SerializedParagraphNode, SerializedTextNode, SerializedLineBreakNode, + SerializedHeadingNode, + SerializedListItemNode, + SerializedListNode, + SerializedTableRowNode, + SerializedTableNode, + SerializedTableCellNode, } from '../../../nodeTypes.js' import { convertLexicalToPlaintext } from './sync/index.js' @@ -51,7 +57,83 @@ function paragraphNode(children: DefaultNodeTypes[]): SerializedParagraphNode { } } -function rootNode(nodes: DefaultNodeTypes[]): DefaultTypedEditorState { +function headingNode(children: DefaultNodeTypes[]): SerializedHeadingNode { + return { + type: 'heading', + children, + direction: 'ltr', + format: '', + indent: 0, + textFormat: 0, + tag: 'h1', + version: 1, + } +} + +function listItemNode(children: DefaultNodeTypes[]): SerializedListItemNode { + return { + type: 'listitem', + children, + checked: false, + direction: 'ltr', + format: '', + indent: 0, + value: 0, + version: 1, + } +} + +function listNode(children: DefaultNodeTypes[]): SerializedListNode { + return { + type: 'list', + children, + direction: 'ltr', + format: '', + indent: 0, + listType: 'bullet', + start: 0, + tag: 'ul', + version: 1, + } +} + +function tableNode(children: (DefaultNodeTypes | SerializedTableRowNode)[]): SerializedTableNode { + return { + type: 'table', + children, + direction: 'ltr', + format: '', + indent: 0, + version: 1, + } +} + +function tableRowNode( + children: (DefaultNodeTypes | SerializedTableCellNode)[], +): SerializedTableRowNode { + return { + type: 'tablerow', + children, + direction: 'ltr', + format: '', + indent: 0, + version: 1, + } +} + +function tableCellNode(children: DefaultNodeTypes[]): SerializedTableCellNode { + return { + type: 'tablecell', + children, + direction: 'ltr', + format: '', + indent: 0, + headerState: 0, + version: 1, + } +} + +function rootNode(nodes: (DefaultNodeTypes | SerializedTableNode)[]): DefaultTypedEditorState { return { root: { type: 'root', @@ -72,7 +154,6 @@ describe('convertLexicalToPlaintext', () => { data, }) - console.log('plaintext', plaintext) expect(plaintext).toBe('Basic Text') }) @@ -111,4 +192,67 @@ describe('convertLexicalToPlaintext', () => { expect(plaintext).toBe('Basic Text\tNext Line') }) + + it('ensure new lines are added between paragraphs', () => { + const data: DefaultTypedEditorState = rootNode([ + paragraphNode([textNode('Basic text')]), + paragraphNode([textNode('Next block-node')]), + ]) + + const plaintext = convertLexicalToPlaintext({ + data, + }) + + expect(plaintext).toBe('Basic text\n\nNext block-node') + }) + + it('ensure new lines are added between heading nodes', () => { + const data: DefaultTypedEditorState = rootNode([ + headingNode([textNode('Basic text')]), + headingNode([textNode('Next block-node')]), + ]) + + const plaintext = convertLexicalToPlaintext({ + data, + }) + + expect(plaintext).toBe('Basic text\n\nNext block-node') + }) + + it('ensure new lines are added between list items and lists', () => { + const data: DefaultTypedEditorState = rootNode([ + listNode([listItemNode([textNode('First item')]), listItemNode([textNode('Second item')])]), + listNode([listItemNode([textNode('Next list')])]), + ]) + + const plaintext = convertLexicalToPlaintext({ + data, + }) + + expect(plaintext).toBe('First item\nSecond item\n\nNext list') + }) + + it('ensure new lines are added between tables, table rows, and table cells', () => { + const data: DefaultTypedEditorState = rootNode([ + tableNode([ + tableRowNode([ + tableCellNode([textNode('Cell 1, Row 1')]), + tableCellNode([textNode('Cell 2, Row 1')]), + ]), + tableRowNode([ + tableCellNode([textNode('Cell 1, Row 2')]), + tableCellNode([textNode('Cell 2, Row 2')]), + ]), + ]), + tableNode([tableRowNode([tableCellNode([textNode('Cell in Table 2')])])]), + ]) + + const plaintext = convertLexicalToPlaintext({ + data, + }) + + expect(plaintext).toBe( + 'Cell 1, Row 1\nCell 2, Row 1\n\nCell 1, Row 2\nCell 2, Row 2\n\nCell in Table 2', + ) + }) }) diff --git a/packages/richtext-lexical/src/features/converters/lexicalToPlaintext/sync/index.ts b/packages/richtext-lexical/src/features/converters/lexicalToPlaintext/sync/index.ts index c29049d6b23..81159391a2d 100644 --- a/packages/richtext-lexical/src/features/converters/lexicalToPlaintext/sync/index.ts +++ b/packages/richtext-lexical/src/features/converters/lexicalToPlaintext/sync/index.ts @@ -86,11 +86,22 @@ export function convertLexicalNodesToPlaintext({ } } else { // Default plaintext converter heuristic - if (node.type === 'paragraph') { + if ( + node.type === 'paragraph' || + node.type === 'heading' || + node.type === 'list' || + node.type === 'table' || + node.type === 'tablerow' + ) { if (plainTextArray?.length) { // Only add a new line if there is already text in the array plainTextArray.push('\n\n') } + } else if (node.type === 'listitem' || node.type === 'tablecell') { + if (plainTextArray?.length) { + // Only add a new line if there is already text in the array + plainTextArray.push('\n') + } } else if (node.type === 'linebreak') { plainTextArray.push('\n') } else if (node.type === 'tab') {