Skip to content

Commit ddf40d5

Browse files
authored
fix(richtext-lexical): add missing line-breaks to plaintext conversion (#11951)
### What? Adds line-breaks after headings, lists, list items, tables, table rows, and table cells when converting lexical content to plaintext. ### Why? Currently text from those nodes is concatenated without a separator. ### How? Adds handling for these nodes to the plain text converter.
1 parent 1ef1c55 commit ddf40d5

File tree

2 files changed

+161
-3
lines changed

2 files changed

+161
-3
lines changed

packages/richtext-lexical/src/features/converters/lexicalToPlaintext/convertLexicalToPlaintext.spec.ts

Lines changed: 146 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ import type {
55
SerializedParagraphNode,
66
SerializedTextNode,
77
SerializedLineBreakNode,
8+
SerializedHeadingNode,
9+
SerializedListItemNode,
10+
SerializedListNode,
11+
SerializedTableRowNode,
12+
SerializedTableNode,
13+
SerializedTableCellNode,
814
} from '../../../nodeTypes.js'
915
import { convertLexicalToPlaintext } from './sync/index.js'
1016

@@ -51,7 +57,83 @@ function paragraphNode(children: DefaultNodeTypes[]): SerializedParagraphNode {
5157
}
5258
}
5359

54-
function rootNode(nodes: DefaultNodeTypes[]): DefaultTypedEditorState {
60+
function headingNode(children: DefaultNodeTypes[]): SerializedHeadingNode {
61+
return {
62+
type: 'heading',
63+
children,
64+
direction: 'ltr',
65+
format: '',
66+
indent: 0,
67+
textFormat: 0,
68+
tag: 'h1',
69+
version: 1,
70+
}
71+
}
72+
73+
function listItemNode(children: DefaultNodeTypes[]): SerializedListItemNode {
74+
return {
75+
type: 'listitem',
76+
children,
77+
checked: false,
78+
direction: 'ltr',
79+
format: '',
80+
indent: 0,
81+
value: 0,
82+
version: 1,
83+
}
84+
}
85+
86+
function listNode(children: DefaultNodeTypes[]): SerializedListNode {
87+
return {
88+
type: 'list',
89+
children,
90+
direction: 'ltr',
91+
format: '',
92+
indent: 0,
93+
listType: 'bullet',
94+
start: 0,
95+
tag: 'ul',
96+
version: 1,
97+
}
98+
}
99+
100+
function tableNode(children: (DefaultNodeTypes | SerializedTableRowNode)[]): SerializedTableNode {
101+
return {
102+
type: 'table',
103+
children,
104+
direction: 'ltr',
105+
format: '',
106+
indent: 0,
107+
version: 1,
108+
}
109+
}
110+
111+
function tableRowNode(
112+
children: (DefaultNodeTypes | SerializedTableCellNode)[],
113+
): SerializedTableRowNode {
114+
return {
115+
type: 'tablerow',
116+
children,
117+
direction: 'ltr',
118+
format: '',
119+
indent: 0,
120+
version: 1,
121+
}
122+
}
123+
124+
function tableCellNode(children: DefaultNodeTypes[]): SerializedTableCellNode {
125+
return {
126+
type: 'tablecell',
127+
children,
128+
direction: 'ltr',
129+
format: '',
130+
indent: 0,
131+
headerState: 0,
132+
version: 1,
133+
}
134+
}
135+
136+
function rootNode(nodes: (DefaultNodeTypes | SerializedTableNode)[]): DefaultTypedEditorState {
55137
return {
56138
root: {
57139
type: 'root',
@@ -72,7 +154,6 @@ describe('convertLexicalToPlaintext', () => {
72154
data,
73155
})
74156

75-
console.log('plaintext', plaintext)
76157
expect(plaintext).toBe('Basic Text')
77158
})
78159

@@ -111,4 +192,67 @@ describe('convertLexicalToPlaintext', () => {
111192

112193
expect(plaintext).toBe('Basic Text\tNext Line')
113194
})
195+
196+
it('ensure new lines are added between paragraphs', () => {
197+
const data: DefaultTypedEditorState = rootNode([
198+
paragraphNode([textNode('Basic text')]),
199+
paragraphNode([textNode('Next block-node')]),
200+
])
201+
202+
const plaintext = convertLexicalToPlaintext({
203+
data,
204+
})
205+
206+
expect(plaintext).toBe('Basic text\n\nNext block-node')
207+
})
208+
209+
it('ensure new lines are added between heading nodes', () => {
210+
const data: DefaultTypedEditorState = rootNode([
211+
headingNode([textNode('Basic text')]),
212+
headingNode([textNode('Next block-node')]),
213+
])
214+
215+
const plaintext = convertLexicalToPlaintext({
216+
data,
217+
})
218+
219+
expect(plaintext).toBe('Basic text\n\nNext block-node')
220+
})
221+
222+
it('ensure new lines are added between list items and lists', () => {
223+
const data: DefaultTypedEditorState = rootNode([
224+
listNode([listItemNode([textNode('First item')]), listItemNode([textNode('Second item')])]),
225+
listNode([listItemNode([textNode('Next list')])]),
226+
])
227+
228+
const plaintext = convertLexicalToPlaintext({
229+
data,
230+
})
231+
232+
expect(plaintext).toBe('First item\nSecond item\n\nNext list')
233+
})
234+
235+
it('ensure new lines are added between tables, table rows, and table cells', () => {
236+
const data: DefaultTypedEditorState = rootNode([
237+
tableNode([
238+
tableRowNode([
239+
tableCellNode([textNode('Cell 1, Row 1')]),
240+
tableCellNode([textNode('Cell 2, Row 1')]),
241+
]),
242+
tableRowNode([
243+
tableCellNode([textNode('Cell 1, Row 2')]),
244+
tableCellNode([textNode('Cell 2, Row 2')]),
245+
]),
246+
]),
247+
tableNode([tableRowNode([tableCellNode([textNode('Cell in Table 2')])])]),
248+
])
249+
250+
const plaintext = convertLexicalToPlaintext({
251+
data,
252+
})
253+
254+
expect(plaintext).toBe(
255+
'Cell 1, Row 1 | Cell 2, Row 1\nCell 1, Row 2 | Cell 2, Row 2\n\nCell in Table 2',
256+
)
257+
})
114258
})

packages/richtext-lexical/src/features/converters/lexicalToPlaintext/sync/index.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,25 @@ export function convertLexicalNodesToPlaintext({
8686
}
8787
} else {
8888
// Default plaintext converter heuristic
89-
if (node.type === 'paragraph') {
89+
if (
90+
node.type === 'paragraph' ||
91+
node.type === 'heading' ||
92+
node.type === 'list' ||
93+
node.type === 'table'
94+
) {
9095
if (plainTextArray?.length) {
9196
// Only add a new line if there is already text in the array
9297
plainTextArray.push('\n\n')
9398
}
99+
} else if (node.type === 'listitem' || node.type === 'tablerow') {
100+
if (plainTextArray?.length) {
101+
// Only add a new line if there is already text in the array
102+
plainTextArray.push('\n')
103+
}
104+
} else if (node.type === 'tablecell') {
105+
if (plainTextArray?.length) {
106+
plainTextArray.push(' | ')
107+
}
94108
} else if (node.type === 'linebreak') {
95109
plainTextArray.push('\n')
96110
} else if (node.type === 'tab') {

0 commit comments

Comments
 (0)