Skip to content

Commit 16500d2

Browse files
authored
🐛(frontend) convert newlines to <br> in styled text (#577)
BlockNote represents Shift+Enter hard breaks as \n characters within StyledText.text rather than as separate inline content nodes. The email exporter was rendering text as-is, causing line breaks to be lost in the exported HTML.
1 parent b6e76e2 commit 16500d2

File tree

2 files changed

+35
-2
lines changed

2 files changed

+35
-2
lines changed

src/frontend/src/features/blocknote/email-exporter/index.test.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,25 @@ describe('EmailExporter', () => {
260260
]);
261261
expect(html).toContain('background-color:#fbf3db');
262262
});
263+
264+
it('renders hard breaks (Shift+Enter) as <br>', () => {
265+
const html = exportBlocks([
266+
paragraph([styledText('Line one\nLine two')]),
267+
]);
268+
expect(html).toContain('Line one');
269+
expect(html).toContain('<br/>');
270+
expect(html).toContain('Line two');
271+
});
272+
273+
it('renders hard breaks within styled text as <br>', () => {
274+
const html = exportBlocks([
275+
paragraph([styledText('Bold line one\nBold line two', { bold: true })]),
276+
]);
277+
expect(html).toContain('font-weight:bold');
278+
expect(html).toContain('Bold line one');
279+
expect(html).toContain('<br/>');
280+
expect(html).toContain('Bold line two');
281+
});
263282
});
264283

265284
// -----------------------------------------------------------------------

src/frontend/src/features/blocknote/email-exporter/index.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,26 @@ function styleOrUndefined(style: CSSProperties): CSSProperties | undefined {
117117
// Inline content rendering
118118
// ---------------------------------------------------------------------------
119119

120+
function textWithBreaks(text: string): React.ReactNode {
121+
if (!text.includes('\n')) {
122+
return text;
123+
}
124+
const parts = text.split('\n');
125+
return parts.map((part, i) => (
126+
<React.Fragment key={i}>
127+
{part}
128+
{i < parts.length - 1 && <br />}
129+
</React.Fragment>
130+
));
131+
}
132+
120133
function renderStyledText(st: AnyStyledText, key: number): React.ReactNode {
121134
const style = inlineStylesToCSS(st.styles);
135+
const content = textWithBreaks(st.text);
122136
if (Object.keys(style).length === 0) {
123-
return st.text;
137+
return <React.Fragment key={key}>{content}</React.Fragment>;
124138
}
125-
return <span key={key} style={style}>{st.text}</span>;
139+
return <span key={key} style={style}>{content}</span>;
126140
}
127141

128142
function renderInlineContent(content: AnyInlineContent[]): React.ReactNode[] {

0 commit comments

Comments
 (0)